aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js/components/ContentView
diff options
context:
space:
mode:
authorJason <jason.daurus@gmail.com>2016-06-14 23:52:00 +0800
committerJason <jason.daurus@gmail.com>2016-06-17 04:37:57 +0800
commite5bf1e930a5b6ba0b3300b02daf792d65d795202 (patch)
treef032716e6f31d204ee3b781279c5a9e8444e1e49 /web/src/js/components/ContentView
parent1fc2db85fa339f9b134d45c15d2ad4cf3d681070 (diff)
downloadmitmproxy-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.jsx28
-rw-r--r--web/src/js/components/ContentView/ContentLoader.jsx67
-rw-r--r--web/src/js/components/ContentView/ContentViews.jsx70
-rw-r--r--web/src/js/components/ContentView/ViewSelector.jsx28
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>
+ )
+}