aboutsummaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2015-03-22 00:21:38 +0100
committerMaximilian Hils <git@maximilianhils.com>2015-03-22 00:21:38 +0100
commit1143552e1690f8b96b3d95381f7f06cbb46ead59 (patch)
treec7ef194db2d47b0a923538759cc589879ab8c24c /web
parent02a61ea45dc1ca6d0c88b44adf83f68b791130e7 (diff)
downloadmitmproxy-1143552e1690f8b96b3d95381f7f06cbb46ead59.tar.gz
mitmproxy-1143552e1690f8b96b3d95381f7f06cbb46ead59.tar.bz2
mitmproxy-1143552e1690f8b96b3d95381f7f06cbb46ead59.zip
web: add content views
Diffstat (limited to 'web')
-rw-r--r--web/src/css/app.less1
-rw-r--r--web/src/css/flowdetail.less3
-rw-r--r--web/src/css/flowview.less9
-rw-r--r--web/src/js/components/flowview/contentview.js158
-rw-r--r--web/src/js/components/flowview/messages.js17
5 files changed, 174 insertions, 14 deletions
diff --git a/web/src/css/app.less b/web/src/css/app.less
index 26f22572..ecec3d9c 100644
--- a/web/src/css/app.less
+++ b/web/src/css/app.less
@@ -13,5 +13,6 @@ html {
@import (less) "header.less";
@import (less) "flowtable.less";
@import (less) "flowdetail.less";
+@import (less) "flowview.less";
@import (less) "eventlog.less";
@import (less) "footer.less"; \ No newline at end of file
diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less
index 7649057f..093ee14f 100644
--- a/web/src/css/flowdetail.less
+++ b/web/src/css/flowdetail.less
@@ -29,6 +29,9 @@
}
}
+.view-selector {
+ margin-top: 10px;
+}
.flow-detail table {
.monospace();
diff --git a/web/src/css/flowview.less b/web/src/css/flowview.less
new file mode 100644
index 00000000..44ae8ac2
--- /dev/null
+++ b/web/src/css/flowview.less
@@ -0,0 +1,9 @@
+.flowview-image {
+
+ text-align: center;
+
+ img {
+ max-width: 100%;
+ max-height: 100%;
+ }
+} \ No newline at end of file
diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js
new file mode 100644
index 00000000..09a64bb2
--- /dev/null
+++ b/web/src/js/components/flowview/contentview.js
@@ -0,0 +1,158 @@
+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 Image = React.createClass({
+ statics: {
+ matches: function (message) {
+ return image_regex.test(MessageUtils.getContentType(message));
+ }
+ },
+ render: function () {
+ var message_name = this.props.flow.request === this.props.message ? "request" : "response";
+ var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content";
+ return <div className="flowview-image">
+ <img src={url} alt="preview" className="img-thumbnail"/>
+ </div>;
+ }
+});
+
+var Raw = React.createClass({
+ statics: {
+ matches: function (message) {
+ return true;
+ }
+ },
+ render: function () {
+ //FIXME
+ return <div>raw</div>;
+ }
+});
+
+
+var Auto = React.createClass({
+ 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 = Auto.findView(this.props.message);
+ return <View {...this.props}/>;
+ }
+});
+
+var all = [Auto, Image, Raw];
+
+
+var ContentEmpty = React.createClass({
+ render: function () {
+ var message_name = this.props.flow.request === this.props.message ? "request" : "response";
+ return <div className="alert alert-info">No {message_name} content.</div>;
+ }
+});
+
+var ContentMissing = React.createClass({
+ render: function () {
+ var message_name = this.props.flow.request === this.props.message ? "Request" : "Response";
+ return <div className="alert alert-info">{message_name} content missing.</div>;
+ }
+});
+
+var TooLarge = React.createClass({
+ render: function () {
+ var size = utils.formatSize(this.props.message.contentLength);
+ return <div className="alert alert-warning">
+ <button onClick={this.props.onClick} className="btn btn-xs btn-warning pull-right">Display anyway</button>
+ {size} content size.
+ </div>;
+ }
+});
+
+var ViewSelector = React.createClass({
+ 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 === Auto) {
+ text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase();
+ } else {
+ text = view.displayName.toLowerCase();
+ }
+ views.push(
+ <button
+ key={view.displayName}
+ onClick={this.props.selectView.bind(null, view)}
+ className={className}>
+ {text}
+ </button>
+ );
+ }
+
+ return <div className="view-selector btn-group btn-group-xs">{views}</div>;
+ }
+});
+
+var ContentView = React.createClass({
+ getInitialState: function () {
+ return {
+ displayLarge: false,
+ View: Auto
+ };
+ },
+ 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 <ContentEmpty {...this.props}/>;
+ } else if (message.contentLength === null) {
+ return <ContentMissing {...this.props}/>;
+ } else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) {
+ return <TooLarge {...this.props} onClick={this.displayLarge}/>;
+ }
+
+ return <div>
+ <this.state.View {...this.props} />
+ <div className="text-center">
+ <ViewSelector selectView={this.selectView} active={this.state.View} message={message}/>
+ </div>
+ </div>;
+ }
+});
+
+module.exports = ContentView; \ No newline at end of file
diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js
index ffbfff43..fe8fa200 100644
--- a/web/src/js/components/flowview/messages.js
+++ b/web/src/js/components/flowview/messages.js
@@ -2,6 +2,7 @@ var React = require("react");
var flowutils = require("../../flow/utils.js");
var utils = require("../../utils.js");
+var ContentView = require("./contentview.js");
var Headers = React.createClass({
render: function () {
@@ -31,12 +32,6 @@ var Request = React.createClass({
flowutils.RequestUtils.pretty_url(flow.request),
"HTTP/" + flow.request.httpversion.join(".")
].join(" ");
- var content = null;
- if (flow.request.contentLength > 0) {
- content = "Request Content Size: " + utils.formatSize(flow.request.contentLength);
- } else {
- content = <div className="alert alert-info">No Content</div>;
- }
//TODO: Styling
@@ -45,7 +40,7 @@ var Request = React.createClass({
<div className="first-line">{ first_line }</div>
<Headers message={flow.request}/>
<hr/>
- {content}
+ <ContentView flow={flow} message={flow.request}/>
</section>
);
}
@@ -59,12 +54,6 @@ var Response = React.createClass({
flow.response.code,
flow.response.msg
].join(" ");
- var content = null;
- if (flow.response.contentLength > 0) {
- content = "Response Content Size: " + utils.formatSize(flow.response.contentLength);
- } else {
- content = <div className="alert alert-info">No Content</div>;
- }
//TODO: Styling
@@ -73,7 +62,7 @@ var Response = React.createClass({
<div className="first-line">{ first_line }</div>
<Headers message={flow.response}/>
<hr/>
- {content}
+ <ContentView flow={flow} message={flow.response}/>
</section>
);
}