aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/web/static/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/web/static/app.js')
-rw-r--r--libmproxy/web/static/app.js1363
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