diff options
author | Jason <jason.daurus@gmail.com> | 2016-06-14 23:52:00 +0800 |
---|---|---|
committer | Jason <jason.daurus@gmail.com> | 2016-06-17 04:37:57 +0800 |
commit | e5bf1e930a5b6ba0b3300b02daf792d65d795202 (patch) | |
tree | f032716e6f31d204ee3b781279c5a9e8444e1e49 /web/src/js/components/ContentView | |
parent | 1fc2db85fa339f9b134d45c15d2ad4cf3d681070 (diff) | |
download | mitmproxy-e5bf1e930a5b6ba0b3300b02daf792d65d795202.tar.gz mitmproxy-e5bf1e930a5b6ba0b3300b02daf792d65d795202.tar.bz2 mitmproxy-e5bf1e930a5b6ba0b3300b02daf792d65d795202.zip |
[web] FlowView and ContentView
Diffstat (limited to 'web/src/js/components/ContentView')
-rw-r--r-- | web/src/js/components/ContentView/ContentErrors.jsx | 28 | ||||
-rw-r--r-- | web/src/js/components/ContentView/ContentLoader.jsx | 67 | ||||
-rw-r--r-- | web/src/js/components/ContentView/ContentViews.jsx | 70 | ||||
-rw-r--r-- | web/src/js/components/ContentView/ViewSelector.jsx | 28 |
4 files changed, 193 insertions, 0 deletions
diff --git a/web/src/js/components/ContentView/ContentErrors.jsx b/web/src/js/components/ContentView/ContentErrors.jsx new file mode 100644 index 00000000..11594c7f --- /dev/null +++ b/web/src/js/components/ContentView/ContentErrors.jsx @@ -0,0 +1,28 @@ +import React from 'react' +import { ViewImage } from './ContentViews' +import {formatSize} from '../../utils.js' + +export function ContentEmpty({ flow, message }) { + return ( + <div className="alert alert-info"> + No {flow.request === message ? 'request' : 'response'} content. + </div> + ) +} + +export function ContentMissing({ flow, message }) { + return ( + <div className="alert alert-info"> + {flow.request === message ? 'Request' : 'Response'} content missing. + </div> + ) +} + +export function ContentTooLarge({ message, onClick }) { + return ( + <div className="alert alert-warning"> + <button onClick={onClick} className="btn btn-xs btn-warning pull-right">Display anyway</button> + {formatSize(message.contentLength)} content size. + </div> + ) +} diff --git a/web/src/js/components/ContentView/ContentLoader.jsx b/web/src/js/components/ContentView/ContentLoader.jsx new file mode 100644 index 00000000..f346dc01 --- /dev/null +++ b/web/src/js/components/ContentView/ContentLoader.jsx @@ -0,0 +1,67 @@ +import React, { Component, PropTypes } from 'react' +import { MessageUtils } from '../../flow/utils.js' + +export default class ContentLoader extends Component { + + static propTypes = { + flow: PropTypes.object.isRequired, + message: PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context) + this.state = { content: null, request: null } + } + + requestContent(nextProps) { + if (this.state.request) { + this.state.request.abort() + } + + const request = MessageUtils.getContent(nextProps.flow, nextProps.message) + + this.setState({ content: null, request }) + + request + .done(content => { + this.setState({ content }) + }) + .fail((xhr, textStatus, errorThrown) => { + if (textStatus === 'abort') { + return + } + this.setState({ content: `AJAX Error: ${textStatus}\r\n${errorThrown}` }) + }) + .always(() => { + this.setState({ request: null }) + }) + } + + componentWillMount() { + this.requestContent(this.props) + } + + componentWillReceiveProps(nextProps) { + if (nextProps.message !== this.props.message) { + this.requestContent(nextProps) + } + } + + componentWillUnmount() { + if (this.state.request) { + this.state.request.abort() + } + } + + render() { + return this.state.content ? ( + React.cloneElement(this.props.children, { + content: this.state.content + }) + ) : ( + <div className="text-center"> + <i className="fa fa-spinner fa-spin"></i> + </div> + ) + } +} diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx new file mode 100644 index 00000000..b0297dcc --- /dev/null +++ b/web/src/js/components/ContentView/ContentViews.jsx @@ -0,0 +1,70 @@ +import React, { PropTypes } from 'react' +import ContentLoader from './ContentLoader' +import { MessageUtils } from '../../flow/utils.js' + +const views = [ViewAuto, ViewImage, ViewJSON, ViewRaw] + +ViewImage.regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i +ViewImage.matches = msg => ViewImage.regex.test(MessageUtils.getContentType(msg)) + +ViewImage.propTypes = { + flow: PropTypes.object.isRequired, + message: PropTypes.object.isRequired, +} + +export function ViewImage({ flow, message }) { + return ( + <div className="flowview-image"> + <img src={MessageUtils.getContentURL(flow, message)} alt="preview" className="img-thumbnail"/> + </div> + ) +} + +ViewRaw.textView = true +ViewRaw.matches = () => true + +ViewRaw.propTypes = { + content: React.PropTypes.string.isRequired, +} + +export function ViewRaw({ content }) { + return <pre>{content}</pre> +} + +ViewJSON.textView = true +ViewJSON.regex = /^application\/json$/i +ViewJSON.matches = msg => ViewJSON.regex.test(MessageUtils.getContentType(msg)) + +ViewJSON.propTypes = { + content: React.PropTypes.string.isRequired, +} + +export function ViewJSON({ content }) { + let json = content + try { + json = JSON.stringify(JSON.parse(content), null, 2); + } catch (e) { + // @noop + } + return <pre>{json}</pre> +} + + +ViewAuto.matches = () => false +ViewAuto.findView = msg => views.find(v => v.matches(msg)) || views[views.length - 1] + +ViewAuto.propTypes = { + message: React.PropTypes.object.isRequired, + flow: React.PropTypes.object.isRequired, +} + +export function ViewAuto({ message, flow }) { + const View = ViewAuto.findView(message) + if (View.textView) { + return <ContentLoader message={message} flow={flow}><View content="" /></ContentLoader> + } else { + return <View message={message} flow={flow} /> + } +} + +export default views diff --git a/web/src/js/components/ContentView/ViewSelector.jsx b/web/src/js/components/ContentView/ViewSelector.jsx new file mode 100644 index 00000000..df3a5b83 --- /dev/null +++ b/web/src/js/components/ContentView/ViewSelector.jsx @@ -0,0 +1,28 @@ +import React, { PropTypes } from 'react' +import classnames from 'classnames' +import views, { ViewAuto } from './ContentViews' + +ViewSelector.propTypes = { + active: PropTypes.func.isRequired, + message: PropTypes.object.isRequired, + onSelectView: PropTypes.func.isRequired, +} + +export default function ViewSelector({ active, message, onSelectView }) { + return ( + <div className="view-selector btn-group btn-group-xs"> + {views.map(View => ( + <button + key={View.name} + onClick={() => onSelectView(View)} + className={classnames('btn btn-default', { active: View === active })}> + {View === ViewAuto ? ( + `auto: ${ViewAuto.findView(message).name.toLowerCase().replace('view', '')}` + ) : ( + View.name.toLowerCase().replace('view', '') + )} + </button> + ))} + </div> + ) +} |