diff options
author | Maximilian Hils <git@maximilianhils.com> | 2015-03-22 00:21:38 +0100 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2015-03-22 00:21:38 +0100 |
commit | 1143552e1690f8b96b3d95381f7f06cbb46ead59 (patch) | |
tree | c7ef194db2d47b0a923538759cc589879ab8c24c /web/src | |
parent | 02a61ea45dc1ca6d0c88b44adf83f68b791130e7 (diff) | |
download | mitmproxy-1143552e1690f8b96b3d95381f7f06cbb46ead59.tar.gz mitmproxy-1143552e1690f8b96b3d95381f7f06cbb46ead59.tar.bz2 mitmproxy-1143552e1690f8b96b3d95381f7f06cbb46ead59.zip |
web: add content views
Diffstat (limited to 'web/src')
-rw-r--r-- | web/src/css/app.less | 1 | ||||
-rw-r--r-- | web/src/css/flowdetail.less | 3 | ||||
-rw-r--r-- | web/src/css/flowview.less | 9 | ||||
-rw-r--r-- | web/src/js/components/flowview/contentview.js | 158 | ||||
-rw-r--r-- | web/src/js/components/flowview/messages.js | 17 |
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> ); } |