diff options
Diffstat (limited to 'libmproxy/web/static/app.js')
-rw-r--r-- | libmproxy/web/static/app.js | 1363 |
1 files changed, 871 insertions, 492 deletions
diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index f8f14fcc..83bb99ab 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -303,6 +303,7 @@ function isUndefined(arg) { },{}],2:[function(require,module,exports){ var $ = require("jquery"); +var AppDispatcher = require("./dispatcher.js").AppDispatcher; var ActionTypes = { // Connection @@ -410,7 +411,7 @@ var FlowActions = { } }; -Query = { +var Query = { FILTER: "f", HIGHLIGHT: "h", SHOW_EVENTLOG: "e" @@ -420,29 +421,31 @@ module.exports = { ActionTypes: ActionTypes, ConnectionActions: ConnectionActions, FlowActions: FlowActions, - StoreCmds: StoreCmds + StoreCmds: StoreCmds, + SettingsActions: SettingsActions, + EventLogActions: EventLogActions, + Query: Query }; -},{"jquery":"jquery"}],3:[function(require,module,exports){ +},{"./dispatcher.js":19,"jquery":"jquery"}],3:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var $ = require("jquery"); - var Connection = require("./connection"); var proxyapp = require("./components/proxyapp.js"); $(function () { window.ws = new Connection("/updates"); - ReactRouter.run(proxyapp.routes, function (Handler) { + ReactRouter.run(proxyapp.routes, function (Handler, state) { React.render(React.createElement(Handler, null), document.body); }); }); -},{"./components/proxyapp.js":12,"./connection":14,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ +},{"./components/proxyapp.js":16,"./connection":18,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -477,51 +480,41 @@ var StickyHeadMixin = { var Navigation = _.extend({}, ReactRouter.Navigation, { setQuery: function (dict) { - var q = this.context.getCurrentQuery(); + var q = this.context.router.getCurrentQuery(); for(var i in dict){ if(dict.hasOwnProperty(i)){ q[i] = dict[i] || undefined; //falsey values shall be removed. } } - q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599 - this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); + q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/957 + this.replaceWith(this.context.router.getCurrentPath(), this.context.router.getCurrentParams(), q); }, replaceWith: function(routeNameOrPath, params, query) { if(routeNameOrPath === undefined){ - routeNameOrPath = this.context.getCurrentPath(); + routeNameOrPath = this.context.router.getCurrentPath(); } if(params === undefined){ - params = this.context.getCurrentParams(); + params = this.context.router.getCurrentParams(); } - if(query === undefined){ - query = this.context.getCurrentQuery(); + if(query === undefined) { + query = this.context.router.getCurrentQuery(); } - ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); + + // FIXME: react-router is just broken, + // we hopefully just need to wait for the next release with https://github.com/rackt/react-router/pull/957. + this.context.router.replaceWith(routeNameOrPath, params, query); } }); -_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); +// react-router is fairly good at changing its API regularly. +// We keep the old method for now - if it should turn out that their changes are permanent, +// we may remove this mixin and access react-router directly again. var State = _.extend({}, ReactRouter.State, { - getInitialState: function () { - this._query = this.context.getCurrentQuery(); - this._queryWatches = []; - return null; - }, - onQueryChange: function (key, callback) { - this._queryWatches.push({ - key: key, - callback: callback - }); + getQuery: function(){ + return this.context.router.getCurrentQuery(); }, - componentWillReceiveProps: function (nextProps, nextState) { - var q = this.context.getCurrentQuery(); - for (var i = 0; i < this._queryWatches.length; i++) { - var watch = this._queryWatches[i]; - if (this._query[watch.key] !== q[watch.key]) { - watch.callback(this._query[watch.key], q[watch.key], watch.key); - } - } - this._query = q; + getParams: function(){ + return this.context.router.getCurrentParams(); } }); @@ -636,13 +629,15 @@ module.exports = { StickyHeadMixin: StickyHeadMixin, AutoScrollMixin: AutoScrollMixin, Splitter: Splitter -} +}; },{"lodash":"lodash","react":"react","react-router":"react-router"}],5:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); +var Query = require("../actions.js").Query; var VirtualScrollMixin = require("./virtualscroll.js"); var views = require("../store/view.js"); +var _ = require("lodash"); var LogMessage = React.createClass({displayName: "LogMessage", render: function () { @@ -796,156 +791,399 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":23,"./common.js":4,"./virtualscroll.js":17,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); -var _ = require("lodash"); - -var common = require("./common.js"); -var actions = require("../actions.js"); -var flowutils = require("../flow/utils.js"); -var toputils = require("../utils.js"); +var RequestUtils = require("../flow/utils.js").RequestUtils; +var ResponseUtils = require("../flow/utils.js").ResponseUtils; +var utils = require("../utils.js"); -var NavAction = React.createClass({displayName: "NavAction", - onClick: function (e) { - e.preventDefault(); - this.props.onClick(); +var TLSColumn = React.createClass({displayName: "TLSColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-tls " + (this.props.className || "") })); + } + }), + sortKeyFun: function(flow){ + return flow.request.scheme; + } }, render: function () { - return ( - React.createElement("a", {title: this.props.title, - href: "#", - className: "nav-action", - onClick: this.onClick}, - React.createElement("i", {className: "fa fa-fw " + this.props.icon}) - ) - ); + var flow = this.props.flow; + var ssl = (flow.request.scheme == "https"); + var classes; + if (ssl) { + classes = "col-tls col-tls-https"; + } else { + classes = "col-tls col-tls-http"; + } + return React.createElement("td", {className: classes}); } }); -var FlowDetailNav = React.createClass({displayName: "FlowDetailNav", + +var IconColumn = React.createClass({displayName: "IconColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-icon " + (this.props.className || "") })); + } + }) + }, render: function () { var flow = this.props.flow; - var tabs = 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 (event) { - this.props.selectTab(e); - event.preventDefault(); - }.bind(this); - return React.createElement("a", {key: e, - href: "#", - className: className, - onClick: onClick}, str); - }.bind(this)); + var icon; + if (flow.response) { + var contentType = ResponseUtils.getContentType(flow.response); - var acceptButton = null; - if(flow.intercepted){ - acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)}); + //TODO: We should assign a type to the flow somewhere else. + if (flow.response.code == 304) { + icon = "resource-icon-not-modified"; + } else if (300 <= flow.response.code && flow.response.code < 400) { + icon = "resource-icon-redirect"; + } else if (contentType && contentType.indexOf("image") >= 0) { + icon = "resource-icon-image"; + } else if (contentType && contentType.indexOf("javascript") >= 0) { + icon = "resource-icon-js"; + } else if (contentType && contentType.indexOf("css") >= 0) { + icon = "resource-icon-css"; + } else if (contentType && contentType.indexOf("html") >= 0) { + icon = "resource-icon-document"; + } } - var revertButton = null; - if(flow.modified){ - revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)}); + if (!icon) { + icon = "resource-icon-plain"; } - return ( - React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, - tabs, - React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}), - React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}), - React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}), - acceptButton, - revertButton - ) + + icon += " resource-icon"; + return React.createElement("td", {className: "col-icon"}, + React.createElement("div", {className: icon}) ); } }); -var Headers = React.createClass({displayName: "Headers", +var PathColumn = React.createClass({displayName: "PathColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-path " + (this.props.className || "") }), "Path"); + } + }), + sortKeyFun: function(flow){ + return RequestUtils.pretty_url(flow.request); + } + }, render: function () { - var rows = this.props.message.headers.map(function (header, i) { - return ( - React.createElement("tr", {key: i}, - React.createElement("td", {className: "header-name"}, header[0] + ":"), - React.createElement("td", {className: "header-value"}, header[1]) - ) - ); - }); - return ( - React.createElement("table", {className: "header-table"}, - React.createElement("tbody", null, - rows - ) - ) + var flow = this.props.flow; + return React.createElement("td", {className: "col-path"}, + flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, + flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, + RequestUtils.pretty_url(flow.request) ); } }); -var FlowDetailRequest = React.createClass({displayName: "FlowDetailRequest", + +var MethodColumn = React.createClass({displayName: "MethodColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-method " + (this.props.className || "") }), "Method"); + } + }), + sortKeyFun: function(flow){ + return flow.request.method; + } + }, render: function () { var flow = this.props.flow; - var first_line = [ - flow.request.method, - flowutils.RequestUtils.pretty_url(flow.request), - "HTTP/" + flow.request.httpversion.join(".") - ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength); + return React.createElement("td", {className: "col-method"}, flow.request.method); + } +}); + + +var StatusColumn = React.createClass({displayName: "StatusColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-status " + (this.props.className || "") }), "Status"); + } + }), + sortKeyFun: function(flow){ + return flow.response ? flow.response.code : undefined; + } + }, + render: function () { + var flow = this.props.flow; + var status; + if (flow.response) { + status = flow.response.code; } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + status = null; } + return React.createElement("td", {className: "col-status"}, status); + } +}); - //TODO: Styling - return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), - React.createElement(Headers, {message: flow.request}), - React.createElement("hr", null), - content - ) - ); +var SizeColumn = React.createClass({displayName: "SizeColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-size " + (this.props.className || "") }), "Size"); + } + }), + sortKeyFun: function(flow){ + var total = flow.request.contentLength; + if (flow.response) { + total += flow.response.contentLength || 0; + } + return total; + } + }, + render: function () { + var flow = this.props.flow; + + var total = flow.request.contentLength; + if (flow.response) { + total += flow.response.contentLength || 0; + } + var size = utils.formatSize(total); + return React.createElement("td", {className: "col-size"}, size); } }); -var FlowDetailResponse = React.createClass({displayName: "FlowDetailResponse", + +var TimeColumn = React.createClass({displayName: "TimeColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-time " + (this.props.className || "") }), "Time"); + } + }), + sortKeyFun: function(flow){ + if(flow.response) { + return flow.response.timestamp_end - flow.request.timestamp_start; + } + } + }, render: function () { var flow = this.props.flow; - var first_line = [ - "HTTP/" + flow.response.httpversion.join("."), - flow.response.code, - flow.response.msg - ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength); + var time; + if (flow.response) { + time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + time = "..."; } + return React.createElement("td", {className: "col-time"}, time); + } +}); - //TODO: Styling + +var all_columns = [ + TLSColumn, + IconColumn, + PathColumn, + MethodColumn, + StatusColumn, + SizeColumn, + TimeColumn +]; + +module.exports = all_columns; + +},{"../flow/utils.js":21,"../utils.js":24,"react":"react"}],7:[function(require,module,exports){ +var React = require("react"); +var common = require("./common.js"); +var utils = require("../utils.js"); +var _ = require("lodash"); + +var VirtualScrollMixin = require("./virtualscroll.js"); +var flowtable_columns = require("./flowtable-columns.js"); + +var FlowRow = React.createClass({displayName: "FlowRow", + render: function () { + var flow = this.props.flow; + var columns = this.props.columns.map(function (Column) { + return React.createElement(Column, {key: Column.displayName, flow: flow}); + }.bind(this)); + var className = ""; + if (this.props.selected) { + className += " selected"; + } + if (this.props.highlighted) { + className += " highlighted"; + } + if (flow.intercepted) { + className += " intercepted"; + } + if (flow.request) { + className += " has-request"; + } + if (flow.response) { + className += " has-response"; + } return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), - React.createElement(Headers, {message: flow.response}), - React.createElement("hr", null), - content - ) + React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, + columns + )); + }, + shouldComponentUpdate: function (nextProps) { + return true; + // Further optimization could be done here + // by calling forceUpdate on flow updates, selection changes and column changes. + //return ( + //(this.props.columns.length !== nextProps.columns.length) || + //(this.props.selected !== nextProps.selected) + //); + } +}); + +var FlowTableHead = React.createClass({displayName: "FlowTableHead", + getInitialState: function(){ + return { + sortColumn: undefined, + sortDesc: false + }; + }, + onClick: function(Column){ + var sortDesc = this.state.sortDesc; + var hasSort = Column.sortKeyFun; + if(Column === this.state.sortColumn){ + sortDesc = !sortDesc; + this.setState({ + sortDesc: sortDesc + }); + } else { + this.setState({ + sortColumn: hasSort && Column, + sortDesc: false + }) + } + var sortKeyFun; + if(!sortDesc){ + sortKeyFun = Column.sortKeyFun; + } else { + sortKeyFun = hasSort && function(){ + var k = Column.sortKeyFun.apply(this, arguments); + if(_.isString(k)){ + return utils.reverseString(""+k); + } else { + return -k; + } + } + } + this.props.setSortKeyFun(sortKeyFun); + }, + render: function () { + var columns = this.props.columns.map(function (Column) { + var onClick = this.onClick.bind(this, Column); + var className; + if(this.state.sortColumn === Column) { + if(this.state.sortDesc){ + className = "sort-desc"; + } else { + className = "sort-asc"; + } + } + return React.createElement(Column.Title, { + key: Column.displayName, + onClick: onClick, + className: className}); + }.bind(this)); + return React.createElement("thead", null, + React.createElement("tr", null, columns) ); } }); -var FlowDetailError = React.createClass({displayName: "FlowDetailError", + +var ROW_HEIGHT = 32; + +var FlowTable = React.createClass({displayName: "FlowTable", + mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], + getInitialState: function () { + return { + columns: flowtable_columns + }; + }, + _listen: function(view){ + if(!view){ + return; + } + view.addListener("add", this.onChange); + view.addListener("update", this.onChange); + view.addListener("remove", this.onChange); + view.addListener("recalculate", this.onChange); + }, + componentWillMount: function () { + this._listen(this.props.view); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.view !== this.props.view) { + if (this.props.view) { + this.props.view.removeListener("add"); + this.props.view.removeListener("update"); + this.props.view.removeListener("remove"); + this.props.view.removeListener("recalculate"); + } + this._listen(nextProps.view); + } + }, + getDefaultProps: function () { + return { + rowHeight: ROW_HEIGHT + }; + }, + onScrollFlowTable: function () { + this.adjustHead(); + this.onScroll(); + }, + onChange: function () { + this.forceUpdate(); + }, + scrollIntoView: function (flow) { + this.scrollRowIntoView( + this.props.view.index(flow), + this.refs.body.getDOMNode().offsetTop + ); + }, + renderRow: function (flow) { + var selected = (flow === this.props.selected); + var highlighted = + ( + this.props.view._highlight && + this.props.view._highlight[flow.id] + ); + + return React.createElement(FlowRow, {key: flow.id, + ref: flow.id, + flow: flow, + columns: this.state.columns, + selected: selected, + highlighted: highlighted, + selectFlow: this.props.selectFlow} + ); + }, render: function () { - var flow = this.props.flow; + //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); + var flows = this.props.view ? this.props.view.list : []; + + var rows = this.renderRows(flows); + return ( - React.createElement("section", null, - React.createElement("div", {className: "alert alert-warning"}, - flow.error.msg, - React.createElement("div", null, - React.createElement("small", null, toputils.formatTimeStamp(flow.error.timestamp) ) + React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, + React.createElement("table", null, + React.createElement(FlowTableHead, {ref: "head", + columns: this.state.columns, + setSortKeyFun: this.props.setSortKeyFun}), + React.createElement("tbody", {ref: "body"}, + this.getPlaceholderTop(flows.length), + rows, + this.getPlaceholderBottom(flows.length) ) ) ) @@ -953,6 +1191,252 @@ var FlowDetailError = React.createClass({displayName: "FlowDetailError", } }); +module.exports = FlowTable; + + +},{"../utils.js":24,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":17,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var MessageUtils = require("../../flow/utils.js").MessageUtils; +var utils = require("../../utils.js"); + +var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; +var ViewImage = React.createClass({displayName: "ViewImage", + statics: { + matches: function (message) { + return image_regex.test(MessageUtils.getContentType(message)); + } + }, + render: function () { + var url = MessageUtils.getContentURL(this.props.flow, this.props.message); + return React.createElement("div", {className: "flowview-image"}, + React.createElement("img", {src: url, alt: "preview", className: "img-thumbnail"}) + ); + } +}); + +var RawMixin = { + getInitialState: function () { + return { + content: undefined, + request: undefined + } + }, + requestContent: function (nextProps) { + if(this.state.request){ + this.state.request.abort(); + } + var request = MessageUtils.getContent(nextProps.flow, nextProps.message); + this.setState({ + content: undefined, + request: request + }); + request.done(function (data) { + this.setState({content: data}); + }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { + if(textStatus === "abort"){ + return; + } + this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown}); + }.bind(this)).always(function(){ + this.setState({request: undefined}); + }.bind(this)); + + }, + componentWillMount: function () { + this.requestContent(this.props); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.requestContent(nextProps); + } + }, + componentWillUnmount: function(){ + if(this.state.request){ + this.state.request.abort(); + } + }, + render: function () { + if (!this.state.content) { + return React.createElement("div", {className: "text-center"}, + React.createElement("i", {className: "fa fa-spinner fa-spin"}) + ); + } + return this.renderContent(); + } +}; + +var ViewRaw = React.createClass({displayName: "ViewRaw", + mixins: [RawMixin], + statics: { + matches: function (message) { + return true; + } + }, + renderContent: function () { + return React.createElement("pre", null, this.state.content); + } +}); + +var json_regex = /^application\/json$/i; +var ViewJSON = React.createClass({displayName: "ViewJSON", + mixins: [RawMixin], + statics: { + matches: function (message) { + return json_regex.test(MessageUtils.getContentType(message)); + } + }, + renderContent: function () { + var json = this.state.content; + try { + json = JSON.stringify(JSON.parse(json), null, 2); + } catch(e) { + } + return React.createElement("pre", null, json); + } +}); + +var ViewAuto = React.createClass({displayName: "ViewAuto", + statics: { + matches: function () { + return false; // don't match itself + }, + findView: function (message) { + for (var i = 0; i < all.length; i++) { + if (all[i].matches(message)) { + return all[i]; + } + } + return all[all.length - 1]; + } + }, + render: function () { + var View = ViewAuto.findView(this.props.message); + return React.createElement(View, React.__spread({}, this.props)); + } +}); + +var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw]; + + +var ContentEmpty = React.createClass({displayName: "ContentEmpty", + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + return React.createElement("div", {className: "alert alert-info"}, "No ", message_name, " content."); + } +}); + +var ContentMissing = React.createClass({displayName: "ContentMissing", + render: function () { + var message_name = this.props.flow.request === this.props.message ? "Request" : "Response"; + return React.createElement("div", {className: "alert alert-info"}, message_name, " content missing."); + } +}); + +var TooLarge = React.createClass({displayName: "TooLarge", + statics: { + isTooLarge: function(message){ + var max_mb = ViewImage.matches(message) ? 10 : 0.2; + return message.contentLength > 1024 * 1024 * max_mb; + } + }, + render: function () { + var size = utils.formatSize(this.props.message.contentLength); + return React.createElement("div", {className: "alert alert-warning"}, + React.createElement("button", {onClick: this.props.onClick, className: "btn btn-xs btn-warning pull-right"}, "Display anyway"), + size, " content size." + ); + } +}); + +var ViewSelector = React.createClass({displayName: "ViewSelector", + render: function () { + var views = []; + for (var i = 0; i < all.length; i++) { + var view = all[i]; + var className = "btn btn-default"; + if (view === this.props.active) { + className += " active"; + } + var text; + if (view === ViewAuto) { + text = "auto: " + ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view", ""); + } else { + text = view.displayName.toLowerCase().replace("view", ""); + } + views.push( + React.createElement("button", { + key: view.displayName, + onClick: this.props.selectView.bind(null, view), + className: className}, + text + ) + ); + } + + return React.createElement("div", {className: "view-selector btn-group btn-group-xs"}, views); + } +}); + +var ContentView = React.createClass({displayName: "ContentView", + getInitialState: function () { + return { + displayLarge: false, + View: ViewAuto + }; + }, + propTypes: { + // It may seem a bit weird at the first glance: + // Every view takes the flow and the message as props, e.g. + // <Auto flow={flow} message={flow.request}/> + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, + selectView: function (view) { + this.setState({ + View: view + }); + }, + displayLarge: function () { + this.setState({displayLarge: true}); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.setState(this.getInitialState()); + } + }, + render: function () { + var message = this.props.message; + if (message.contentLength === 0) { + return React.createElement(ContentEmpty, React.__spread({}, this.props)); + } else if (message.contentLength === null) { + return React.createElement(ContentMissing, React.__spread({}, this.props)); + } else if (!this.state.displayLarge && TooLarge.isTooLarge(message)) { + return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge})); + } + + var downloadUrl = MessageUtils.getContentURL(this.props.flow, message); + + return React.createElement("div", null, + React.createElement(this.state.View, React.__spread({}, this.props)), + React.createElement("div", {className: "view-options text-center"}, + React.createElement(ViewSelector, {selectView: this.selectView, active: this.state.View, message: message}), + " ", + React.createElement("a", {className: "btn btn-default btn-xs", href: downloadUrl}, React.createElement("i", {className: "fa fa-download"})) + ) + ); + } +}); + +module.exports = ContentView; + +},{"../../flow/utils.js":21,"../../utils.js":24,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var utils = require("../../utils.js"); + var TimeStamp = React.createClass({displayName: "TimeStamp", render: function () { @@ -961,11 +1445,11 @@ var TimeStamp = React.createClass({displayName: "TimeStamp", return React.createElement("tr", null); } - var ts = toputils.formatTimeStamp(this.props.t); + var ts = utils.formatTimeStamp(this.props.t); var delta; if (this.props.deltaTo) { - delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); + delta = utils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); delta = React.createElement("span", {className: "text-muted"}, "(" + delta + ")"); } else { delta = null; @@ -1105,7 +1589,7 @@ var Timing = React.createClass({displayName: "Timing", } }); -var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnectionInfo", +var Details = React.createClass({displayName: "Details", render: function () { var flow = this.props.flow; var client_conn = flow.client_conn; @@ -1128,14 +1612,25 @@ var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnec } }); +module.exports = Details; + +},{"../../utils.js":24,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var common = require("../common.js"); +var Nav = require("./nav.js"); +var Messages = require("./messages.js"); +var Details = require("./details.js"); + var allTabs = { - request: FlowDetailRequest, - response: FlowDetailResponse, - error: FlowDetailError, - details: FlowDetailConnectionInfo + request: Messages.Request, + response: Messages.Response, + error: Messages.Error, + details: Details }; -var FlowDetail = React.createClass({displayName: "FlowDetail", +var FlowView = React.createClass({displayName: "FlowView", mixins: [common.StickyHeadMixin, common.Navigation, common.State], getTabs: function (flow) { var tabs = []; @@ -1182,7 +1677,7 @@ var FlowDetail = React.createClass({displayName: "FlowDetail", var Tab = allTabs[active]; return ( React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead}, - React.createElement(FlowDetailNav, {ref: "head", + React.createElement(Nav, {ref: "head", flow: flow, tabs: tabs, active: active, @@ -1193,326 +1688,165 @@ var FlowDetail = React.createClass({displayName: "FlowDetail", } }); -module.exports = { - FlowDetail: FlowDetail -}; +module.exports = FlowView; -},{"../actions.js":2,"../flow/utils.js":17,"../utils.js":20,"./common.js":4,"lodash":"lodash","react":"react"}],7:[function(require,module,exports){ +},{"../common.js":4,"./details.js":9,"./messages.js":11,"./nav.js":12,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){ var React = require("react"); -var flowutils = require("../flow/utils.js"); -var utils = require("../utils.js"); -var TLSColumn = React.createClass({displayName: "TLSColumn", - statics: { - renderTitle: function () { - return React.createElement("th", {key: "tls", className: "col-tls"}); - } - }, - render: function () { - var flow = this.props.flow; - var ssl = (flow.request.scheme == "https"); - var classes; - if (ssl) { - classes = "col-tls col-tls-https"; - } else { - classes = "col-tls col-tls-http"; - } - return React.createElement("td", {className: classes}); - } -}); +var flowutils = require("../../flow/utils.js"); +var utils = require("../../utils.js"); +var ContentView = require("./contentview.js"); - -var IconColumn = React.createClass({displayName: "IconColumn", - statics: { - renderTitle: function () { - return React.createElement("th", {key: "icon", className: "col-icon"}); - } - }, +var Headers = React.createClass({displayName: "Headers", render: function () { - var flow = this.props.flow; - - var icon; - if (flow.response) { - var contentType = flowutils.ResponseUtils.getContentType(flow.response); - - //TODO: We should assign a type to the flow somewhere else. - if (flow.response.code == 304) { - icon = "resource-icon-not-modified"; - } else if (300 <= flow.response.code && flow.response.code < 400) { - icon = "resource-icon-redirect"; - } else if (contentType && contentType.indexOf("image") >= 0) { - icon = "resource-icon-image"; - } else if (contentType && contentType.indexOf("javascript") >= 0) { - icon = "resource-icon-js"; - } else if (contentType && contentType.indexOf("css") >= 0) { - icon = "resource-icon-css"; - } else if (contentType && contentType.indexOf("html") >= 0) { - icon = "resource-icon-document"; - } - } - if (!icon) { - icon = "resource-icon-plain"; - } - - - icon += " resource-icon"; - return React.createElement("td", {className: "col-icon"}, - React.createElement("div", {className: icon}) + var rows = this.props.message.headers.map(function (header, i) { + return ( + React.createElement("tr", {key: i}, + React.createElement("td", {className: "header-name"}, header[0] + ":"), + React.createElement("td", {className: "header-value"}, header[1]) + ) + ); + }); + return ( + React.createElement("table", {className: "header-table"}, + React.createElement("tbody", null, + rows + ) + ) ); } }); -var PathColumn = React.createClass({displayName: "PathColumn", - statics: { - renderTitle: function () { - return React.createElement("th", {key: "path", className: "col-path"}, "Path"); - } - }, +var Request = React.createClass({displayName: "Request", render: function () { var flow = this.props.flow; - return React.createElement("td", {className: "col-path"}, - flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, - flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, - flow.request.scheme + "://" + flow.request.host + flow.request.path - ); - } -}); + var first_line = [ + flow.request.method, + flowutils.RequestUtils.pretty_url(flow.request), + "HTTP/" + flow.request.httpversion.join(".") + ].join(" "); + //TODO: Styling -var MethodColumn = React.createClass({displayName: "MethodColumn", - statics: { - renderTitle: function () { - return React.createElement("th", {key: "method", className: "col-method"}, "Method"); - } - }, - render: function () { - var flow = this.props.flow; - return React.createElement("td", {className: "col-method"}, flow.request.method); + return ( + React.createElement("section", null, + React.createElement("div", {className: "first-line"}, first_line ), + React.createElement(Headers, {message: flow.request}), + React.createElement("hr", null), + React.createElement(ContentView, {flow: flow, message: flow.request}) + ) + ); } }); - -var StatusColumn = React.createClass({displayName: "StatusColumn", - statics: { - renderTitle: function () { - return React.createElement("th", {key: "status", className: "col-status"}, "Status"); - } - }, +var Response = React.createClass({displayName: "Response", render: function () { var flow = this.props.flow; - var status; - if (flow.response) { - status = flow.response.code; - } else { - status = null; - } - return React.createElement("td", {className: "col-status"}, status); - } -}); - + var first_line = [ + "HTTP/" + flow.response.httpversion.join("."), + flow.response.code, + flow.response.msg + ].join(" "); -var SizeColumn = React.createClass({displayName: "SizeColumn", - statics: { - renderTitle: function () { - return React.createElement("th", {key: "size", className: "col-size"}, "Size"); - } - }, - render: function () { - var flow = this.props.flow; + //TODO: Styling - var total = flow.request.contentLength; - if (flow.response) { - total += flow.response.contentLength || 0; - } - var size = utils.formatSize(total); - return React.createElement("td", {className: "col-size"}, size); + return ( + React.createElement("section", null, + React.createElement("div", {className: "first-line"}, first_line ), + React.createElement(Headers, {message: flow.response}), + React.createElement("hr", null), + React.createElement(ContentView, {flow: flow, message: flow.response}) + ) + ); } }); - -var TimeColumn = React.createClass({displayName: "TimeColumn", - statics: { - renderTitle: function () { - return React.createElement("th", {key: "time", className: "col-time"}, "Time"); - } - }, +var Error = React.createClass({displayName: "Error", render: function () { var flow = this.props.flow; - var time; - if (flow.response) { - time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); - } else { - time = "..."; - } - return React.createElement("td", {className: "col-time"}, time); + return ( + React.createElement("section", null, + React.createElement("div", {className: "alert alert-warning"}, + flow.error.msg, + React.createElement("div", null, + React.createElement("small", null, utils.formatTimeStamp(flow.error.timestamp) ) + ) + ) + ) + ); } }); +module.exports = { + Request: Request, + Response: Response, + Error: Error +}; -var all_columns = [ - TLSColumn, - IconColumn, - PathColumn, - MethodColumn, - StatusColumn, - SizeColumn, - TimeColumn]; - - -module.exports = all_columns; - - - - -},{"../flow/utils.js":17,"../utils.js":20,"react":"react"}],8:[function(require,module,exports){ +},{"../../flow/utils.js":21,"../../utils.js":24,"./contentview.js":8,"react":"react"}],12:[function(require,module,exports){ var React = require("react"); -var common = require("./common.js"); -var VirtualScrollMixin = require("./virtualscroll.js"); -var flowtable_columns = require("./flowtable-columns.js"); -var FlowRow = React.createClass({displayName: "FlowRow", - render: function () { - var flow = this.props.flow; - var columns = this.props.columns.map(function (Column) { - return React.createElement(Column, {key: Column.displayName, flow: flow}); - }.bind(this)); - var className = ""; - if (this.props.selected) { - className += " selected"; - } - if (this.props.highlighted) { - className += " highlighted"; - } - if (flow.intercepted) { - className += " intercepted"; - } - if (flow.request) { - className += " has-request"; - } - if (flow.response) { - className += " has-response"; - } +var actions = require("../../actions.js"); - return ( - React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, - columns - )); +var NavAction = React.createClass({displayName: "NavAction", + onClick: function (e) { + e.preventDefault(); + this.props.onClick(); }, - shouldComponentUpdate: function (nextProps) { - return true; - // Further optimization could be done here - // by calling forceUpdate on flow updates, selection changes and column changes. - //return ( - //(this.props.columns.length !== nextProps.columns.length) || - //(this.props.selected !== nextProps.selected) - //); - } -}); - -var FlowTableHead = React.createClass({displayName: "FlowTableHead", render: function () { - var columns = this.props.columns.map(function (column) { - return column.renderTitle(); - }.bind(this)); - return React.createElement("thead", null, - React.createElement("tr", null, columns) + return ( + React.createElement("a", {title: this.props.title, + href: "#", + className: "nav-action", + onClick: this.onClick}, + React.createElement("i", {className: "fa fa-fw " + this.props.icon}) + ) ); } }); +var Nav = React.createClass({displayName: "Nav", + render: function () { + var flow = this.props.flow; -var ROW_HEIGHT = 32; + var tabs = 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 (event) { + this.props.selectTab(e); + event.preventDefault(); + }.bind(this); + return React.createElement("a", {key: e, + href: "#", + className: className, + onClick: onClick}, str); + }.bind(this)); -var FlowTable = React.createClass({displayName: "FlowTable", - mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { - return { - columns: flowtable_columns - }; - }, - componentWillMount: function () { - if (this.props.view) { - this.props.view.addListener("add", this.onChange); - this.props.view.addListener("update", this.onChange); - this.props.view.addListener("remove", this.onChange); - this.props.view.addListener("recalculate", this.onChange); + var acceptButton = null; + if(flow.intercepted){ + acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)}); } - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.view !== this.props.view) { - if (this.props.view) { - this.props.view.removeListener("add"); - this.props.view.removeListener("update"); - this.props.view.removeListener("remove"); - this.props.view.removeListener("recalculate"); - } - nextProps.view.addListener("add", this.onChange); - nextProps.view.addListener("update", this.onChange); - nextProps.view.addListener("remove", this.onChange); - nextProps.view.addListener("recalculate", this.onChange); + var revertButton = null; + if(flow.modified){ + revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)}); } - }, - getDefaultProps: function () { - return { - rowHeight: ROW_HEIGHT - }; - }, - onScrollFlowTable: function () { - this.adjustHead(); - this.onScroll(); - }, - onChange: function () { - this.forceUpdate(); - }, - scrollIntoView: function (flow) { - this.scrollRowIntoView( - this.props.view.index(flow), - this.refs.body.getDOMNode().offsetTop - ); - }, - renderRow: function (flow) { - var selected = (flow === this.props.selected); - var highlighted = - ( - this.props.view._highlight && - this.props.view._highlight[flow.id] - ); - - return React.createElement(FlowRow, {key: flow.id, - ref: flow.id, - flow: flow, - columns: this.state.columns, - selected: selected, - highlighted: highlighted, - selectFlow: this.props.selectFlow} - ); - }, - render: function () { - //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); - var flows = this.props.view ? this.props.view.list : []; - - var rows = this.renderRows(flows); return ( - React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, - React.createElement("table", null, - React.createElement(FlowTableHead, {ref: "head", - columns: this.state.columns}), - React.createElement("tbody", {ref: "body"}, - this.getPlaceholderTop(flows.length), - rows, - this.getPlaceholderBottom(flows.length) - ) - ) + React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, + tabs, + React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}), + React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}), + React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}), + acceptButton, + revertButton ) ); } }); -module.exports = FlowTable; - +module.exports = Nav; -},{"./common.js":4,"./flowtable-columns.js":7,"./virtualscroll.js":13,"react":"react"}],9:[function(require,module,exports){ +},{"../../actions.js":2,"react":"react"}],13:[function(require,module,exports){ var React = require("react"); var Footer = React.createClass({displayName: "Footer", @@ -1531,15 +1865,15 @@ var Footer = React.createClass({displayName: "Footer", module.exports = Footer; -},{"react":"react"}],10:[function(require,module,exports){ +},{"react":"react"}],14:[function(require,module,exports){ var React = require("react"); var $ = require("jquery"); var Filt = require("../filt/filt.js"); var utils = require("../utils.js"); - var common = require("./common.js"); var actions = require("../actions.js"); +var Query = require("../actions.js").Query; var FilterDocs = React.createClass({displayName: "FilterDocs", statics: { @@ -1564,12 +1898,12 @@ var FilterDocs = React.createClass({displayName: "FilterDocs", return React.createElement("i", {className: "fa fa-spinner fa-spin"}); } else { var commands = FilterDocs.doc.commands.map(function (c) { - return React.createElement("tr", null, + return React.createElement("tr", {key: c[1]}, React.createElement("td", null, c[0].replace(" ", '\u00a0')), React.createElement("td", null, c[1]) ); }); - commands.push(React.createElement("tr", null, + commands.push(React.createElement("tr", {key: "docs-link"}, React.createElement("td", {colSpan: "2"}, React.createElement("a", {href: "https://mitmproxy.org/doc/features/filters.html", target: "_blank"}, @@ -1707,7 +2041,7 @@ var MainMenu = React.createClass({displayName: "MainMenu", this.setQuery(d); }, onInterceptChange: function (val) { - SettingsActions.update({intercept: val}); + actions.SettingsActions.update({intercept: val}); }, render: function () { var filter = this.getQuery()[Query.FILTER] || ""; @@ -1890,15 +2224,17 @@ var Header = React.createClass({displayName: "Header", }, render: function () { var header = header_entries.map(function (entry, i) { - var classes = React.addons.classSet({ - active: entry == this.state.active - }); + var className; + if(entry === this.state.active){ + className = "active"; + } else { + className = ""; + } return ( React.createElement("a", {key: i, href: "#", - className: classes, - onClick: this.handleClick.bind(this, entry) - }, + className: className, + onClick: this.handleClick.bind(this, entry)}, entry.title ) ); @@ -1923,29 +2259,24 @@ module.exports = { Header: Header } -},{"../actions.js":2,"../filt/filt.js":16,"../utils.js":20,"./common.js":4,"jquery":"jquery","react":"react"}],11:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":20,"../utils.js":24,"./common.js":4,"jquery":"jquery","react":"react"}],15:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); var actions = require("../actions.js"); +var Query = require("../actions.js").Query; var toputils = require("../utils.js"); var views = require("../store/view.js"); var Filt = require("../filt/filt.js"); FlowTable = require("./flowtable.js"); -var flowdetail = require("./flowdetail.js"); - +var FlowView = require("./flowview/index.js"); var MainView = React.createClass({displayName: "MainView", mixins: [common.Navigation, common.State], getInitialState: function () { - this.onQueryChange(Query.FILTER, function () { - this.state.view.recalculate(this.getViewFilt(), this.getViewSort()); - }.bind(this)); - this.onQueryChange(Query.HIGHLIGHT, function () { - this.state.view.recalculate(this.getViewFilt(), this.getViewSort()); - }.bind(this)); return { - flows: [] + flows: [], + sortKeyFun: false }; }, getViewFilt: function () { @@ -1965,16 +2296,20 @@ var MainView = React.createClass({displayName: "MainView", return filt(flow); }; }, - getViewSort: function () { - }, componentWillReceiveProps: function (nextProps) { if (nextProps.flowStore !== this.props.flowStore) { this.closeView(); this.openView(nextProps.flowStore); } + + var filterChanged = (this.props.query[Query.FILTER] !== nextProps.query[Query.FILTER]); + var highlightChanged = (this.props.query[Query.HIGHLIGHT] !== nextProps.query[Query.HIGHLIGHT]); + if (filterChanged || highlightChanged) { + this.state.view.recalculate(this.getViewFilt(), this.state.sortKeyFun); + } }, openView: function (store) { - var view = new views.StoreView(store, this.getViewFilt(), this.getViewSort()); + var view = new views.StoreView(store, this.getViewFilt(), this.state.sortKeyFun); this.setState({ view: view }); @@ -1999,7 +2334,7 @@ var MainView = React.createClass({displayName: "MainView", }, onRemove: function (flow_id, index) { if (flow_id === this.getParams().flowId) { - var flow_to_select = this.state.view.list[Math.min(index, this.state.view.list.length -1)]; + var flow_to_select = this.state.view.list[Math.min(index, this.state.view.list.length - 1)]; this.selectFlow(flow_to_select); } }, @@ -2012,6 +2347,12 @@ var MainView = React.createClass({displayName: "MainView", componentWillUnmount: function () { this.closeView(); }, + setSortKeyFun: function(sortKeyFun){ + this.setState({ + sortKeyFun: sortKeyFun + }); + this.state.view.recalculate(this.getViewFilt(), sortKeyFun); + }, selectFlow: function (flow) { if (flow) { this.replaceWith( @@ -2120,10 +2461,12 @@ var MainView = React.createClass({displayName: "MainView", } break; case toputils.Key.V: - if(e.shiftKey && flow && flow.modified) { + if (e.shiftKey && flow && flow.modified) { actions.FlowActions.revert(flow); } break; + case toputils.Key.SHIFT: + break; default: console.debug("keydown", e.keyCode); return; @@ -2140,7 +2483,7 @@ var MainView = React.createClass({displayName: "MainView", if (selected) { details = [ React.createElement(common.Splitter, {key: "splitter"}), - React.createElement(flowdetail.FlowDetail, {key: "flowDetails", ref: "flowDetails", flow: selected}) + React.createElement(FlowView, {key: "flowDetails", ref: "flowDetails", flow: selected}) ]; } else { details = null; @@ -2151,6 +2494,7 @@ var MainView = React.createClass({displayName: "MainView", React.createElement(FlowTable, {ref: "flowTable", view: this.state.view, selectFlow: this.selectFlow, + setSortKeyFun: this.setSortKeyFun, selected: selected}), details ) @@ -2161,7 +2505,7 @@ var MainView = React.createClass({displayName: "MainView", module.exports = MainView; -},{"../actions.js":2,"../filt/filt.js":16,"../store/view.js":19,"../utils.js":20,"./common.js":4,"./flowdetail.js":6,"./flowtable.js":8,"react":"react"}],12:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":20,"../store/view.js":23,"../utils.js":24,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":10,"react":"react"}],16:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -2172,6 +2516,7 @@ var Footer = require("./footer.js"); var header = require("./header.js"); var EventLog = require("./eventlog.js"); var store = require("../store/store.js"); +var Query = require("../actions.js").Query; //TODO: Move out of here, just a stub. @@ -2211,7 +2556,6 @@ var ProxyAppMain = React.createClass({displayName: "ProxyAppMain", }); }, render: function () { - var eventlog; if (this.getQuery()[Query.SHOW_EVENTLOG]) { eventlog = [ @@ -2221,11 +2565,13 @@ var ProxyAppMain = React.createClass({displayName: "ProxyAppMain", } else { eventlog = null; } - return ( React.createElement("div", {id: "container"}, React.createElement(header.Header, {settings: this.state.settings.dict}), - React.createElement(RouteHandler, {settings: this.state.settings.dict, flowStore: this.state.flowStore}), + React.createElement(RouteHandler, { + settings: this.state.settings.dict, + flowStore: this.state.flowStore, + query: this.getQuery()}), eventlog, React.createElement(Footer, {settings: this.state.settings.dict}) ) @@ -2254,9 +2600,7 @@ module.exports = { routes: routes }; - - -},{"../store/store.js":18,"./common.js":4,"./eventlog.js":5,"./footer.js":9,"./header.js":10,"./mainview.js":11,"lodash":"lodash","react":"react","react-router":"react-router"}],13:[function(require,module,exports){ +},{"../actions.js":2,"../store/store.js":22,"./common.js":4,"./eventlog.js":5,"./footer.js":13,"./header.js":14,"./mainview.js":15,"lodash":"lodash","react":"react","react-router":"react-router"}],17:[function(require,module,exports){ var React = require("react"); var VirtualScrollMixin = { @@ -2343,9 +2687,10 @@ var VirtualScrollMixin = { module.exports = VirtualScrollMixin; -},{"react":"react"}],14:[function(require,module,exports){ +},{"react":"react"}],18:[function(require,module,exports){ var actions = require("./actions.js"); +var AppDispatcher = require("./dispatcher.js").AppDispatcher; function Connection(url) { if (url[0] === "/") { @@ -2362,18 +2707,18 @@ function Connection(url) { }; ws.onerror = function () { actions.ConnectionActions.error(); - EventLogActions.add_event("WebSocket connection error."); + actions.EventLogActions.add_event("WebSocket connection error."); }; ws.onclose = function () { actions.ConnectionActions.close(); - EventLogActions.add_event("WebSocket connection closed."); + actions.EventLogActions.add_event("WebSocket connection closed."); }; return ws; } module.exports = Connection; -},{"./actions.js":2}],15:[function(require,module,exports){ +},{"./actions.js":2,"./dispatcher.js":19}],19:[function(require,module,exports){ var flux = require("flux"); @@ -2383,7 +2728,7 @@ const PayloadSources = { }; -AppDispatcher = new flux.Dispatcher(); +var AppDispatcher = new flux.Dispatcher(); AppDispatcher.dispatchViewAction = function (action) { action.source = PayloadSources.VIEW; this.dispatch(action); @@ -2397,7 +2742,7 @@ module.exports = { AppDispatcher: AppDispatcher }; -},{"flux":"flux"}],16:[function(require,module,exports){ +},{"flux":"flux"}],20:[function(require,module,exports){ module.exports = (function() { /* * Generated by PEG.js 0.8.0. @@ -4173,12 +4518,13 @@ module.exports = (function() { }; })(); -},{"../flow/utils.js":17}],17:[function(require,module,exports){ +},{"../flow/utils.js":21}],21:[function(require,module,exports){ var _ = require("lodash"); +var $ = require("jquery"); -var _MessageUtils = { +var MessageUtils = { getContentType: function (message) { - return this.get_first_header(message, /^Content-Type$/i); + return this.get_first_header(message, /^Content-Type$/i).split(";")[0].trim(); }, get_first_header: function (message, regex) { //FIXME: Cache Invalidation. @@ -4210,6 +4556,18 @@ var _MessageUtils = { } } return false; + }, + getContentURL: function(flow, message){ + if(message === flow.request){ + message = "request"; + } else if (message === flow.response){ + message = "response"; + } + return "/flows/" + flow.id + "/" + message + "/content"; + }, + getContent: function(flow, message){ + var url = MessageUtils.getContentURL(flow, message); + return $.get(url); } }; @@ -4218,7 +4576,7 @@ var defaultPorts = { "https": 443 }; -var RequestUtils = _.extend(_MessageUtils, { +var RequestUtils = _.extend(MessageUtils, { pretty_host: function (request) { //FIXME: Add hostheader return request.host; @@ -4232,16 +4590,16 @@ var RequestUtils = _.extend(_MessageUtils, { } }); -var ResponseUtils = _.extend(_MessageUtils, {}); +var ResponseUtils = _.extend(MessageUtils, {}); module.exports = { ResponseUtils: ResponseUtils, - RequestUtils: RequestUtils - -} + RequestUtils: RequestUtils, + MessageUtils: MessageUtils +}; -},{"lodash":"lodash"}],18:[function(require,module,exports){ +},{"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); @@ -4424,12 +4782,10 @@ module.exports = { FlowStore: FlowStore }; -},{"../actions.js":2,"../dispatcher.js":15,"../utils.js":20,"events":1,"jquery":"jquery","lodash":"lodash"}],19:[function(require,module,exports){ - +},{"../actions.js":2,"../dispatcher.js":19,"../utils.js":24,"events":1,"jquery":"jquery","lodash":"lodash"}],23:[function(require,module,exports){ var EventEmitter = require('events').EventEmitter; var _ = require("lodash"); - var utils = require("../utils.js"); function SortByStoreOrder(elem) { @@ -4437,14 +4793,12 @@ function SortByStoreOrder(elem) { } var default_sort = SortByStoreOrder; -var default_filt = function(elem){ +var default_filt = function (elem) { return true; }; function StoreView(store, filt, sortfun) { EventEmitter.call(this); - filt = filt || default_filt; - sortfun = sortfun || default_sort; this.store = store; @@ -4466,19 +4820,27 @@ _.extend(StoreView.prototype, EventEmitter.prototype, { this.store.removeListener("update", this.update); this.store.removeListener("remove", this.remove); this.store.removeListener("recalculate", this.recalculate); - }, - recalculate: function (filt, sortfun) { - if (filt) { - this.filt = filt.bind(this); - } - if (sortfun) { - this.sortfun = sortfun.bind(this); - } + }, + recalculate: function (filt, sortfun) { + filt = filt || this.filt || default_filt; + sortfun = sortfun || this.sortfun || default_sort; + filt = filt.bind(this); + sortfun = sortfun.bind(this); + this.filt = filt; + this.sortfun = sortfun; - this.list = this.store.list.filter(this.filt); + this.list = this.store.list.filter(filt); this.list.sort(function (a, b) { - return this.sortfun(a) - this.sortfun(b); - }.bind(this)); + var akey = sortfun(a); + var bkey = sortfun(b); + if(akey < bkey){ + return -1; + } else if(akey > bkey){ + return 1; + } else { + return 0; + } + }); this.emit("recalculate"); }, index: function (elem) { @@ -4536,9 +4898,10 @@ module.exports = { StoreView: StoreView }; -},{"../utils.js":20,"events":1,"lodash":"lodash"}],20:[function(require,module,exports){ +},{"../utils.js":24,"events":1,"lodash":"lodash"}],24:[function(require,module,exports){ var $ = require("jquery"); - +var _ = require("lodash"); +var actions = require("./actions.js"); var Key = { UP: 38, @@ -4554,6 +4917,7 @@ var Key = { TAB: 9, SPACE: 32, BACKSPACE: 8, + SHIFT: 16 }; // Add A-Z for (var i = 65; i <= 90; i++) { @@ -4598,8 +4962,20 @@ var formatTimeStamp = function (seconds) { }; +// At some places, we need to sort strings alphabetically descending, +// but we can only provide a key function. +// This beauty "reverses" a JS string. +var end = String.fromCharCode(0xffff); +function reverseString(s){ + return String.fromCharCode.apply(String, + _.map(s.split(""), function (c) { + return 0xffff - c.charCodeAt(0); + }) + ) + end; +} + function getCookie(name) { - var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); + var r = document.cookie.match(new RegExp("\\b" + name + "=([^;]*)\\b")); return r ? r[1] : undefined; } var xsrf = $.param({_xsrf: getCookie("_xsrf")}); @@ -4616,20 +4992,23 @@ $.ajaxPrefilter(function (options) { }); // Log AJAX Errors $(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { + if(thrownError === "abort"){ + return; + } var message = jqXHR.responseText; - console.error(message, arguments); - EventLogActions.add_event(thrownError + ": " + message); - window.alert(message); + console.error(thrownError, message, arguments); + actions.EventLogActions.add_event(thrownError + ": " + message); }); module.exports = { formatSize: formatSize, formatTimeDelta: formatTimeDelta, formatTimeStamp: formatTimeStamp, + reverseString: reverseString, Key: Key }; -},{"jquery":"jquery"}]},{},[3]) +},{"./actions.js":2,"jquery":"jquery","lodash":"lodash"}]},{},[3]) //# sourceMappingURL=app.js.map
\ No newline at end of file |