mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-01-30 14:58:38 +00:00
Merge pull request #1441 from mitmproxy/integrate_mitmproxy_contentviews
Integrate mitmproxy contentviews
This commit is contained in:
commit
b39c6e0883
@ -16,9 +16,11 @@ from mitmproxy.flow import FlowWriter, FlowReader
|
||||
|
||||
from mitmproxy import filt
|
||||
from mitmproxy import models
|
||||
from mitmproxy import contentviews
|
||||
from netlib import version
|
||||
|
||||
|
||||
|
||||
def convert_flow_to_json_dict(flow):
|
||||
# type: (models.Flow) -> dict
|
||||
"""
|
||||
@ -338,6 +340,22 @@ class FlowContent(RequestHandler):
|
||||
self.set_header("X-Frame-Options", "DENY")
|
||||
self.write(message.raw_content)
|
||||
|
||||
class FlowContentView(RequestHandler):
|
||||
|
||||
def get(self, flow_id, message, content_view):
|
||||
message = getattr(self.flow, message)
|
||||
|
||||
description, lines, error = contentviews.get_message_content_view(
|
||||
contentviews.get(content_view.replace('_', ' ')), message
|
||||
)
|
||||
# if error:
|
||||
# add event log
|
||||
|
||||
self.write(dict(
|
||||
lines=list(lines),
|
||||
description=description
|
||||
))
|
||||
|
||||
|
||||
class Events(RequestHandler):
|
||||
|
||||
@ -364,7 +382,8 @@ class Settings(RequestHandler):
|
||||
anticomp=self.master.options.anticomp,
|
||||
stickyauth=self.master.options.stickyauth,
|
||||
stickycookie=self.master.options.stickycookie,
|
||||
stream= self.master.options.stream_large_bodies
|
||||
stream= self.master.options.stream_large_bodies,
|
||||
contentViews= [v.name.replace(' ', '_') for v in contentviews.views]
|
||||
)
|
||||
))
|
||||
|
||||
@ -429,6 +448,7 @@ class Application(tornado.web.Application):
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/replay", ReplayFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/revert", RevertFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content", FlowContent),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content/(?P<content_view>[0-9a-zA-Z\-\_]+)", FlowContentView),
|
||||
(r"/settings", Settings),
|
||||
(r"/clear", ClearAll),
|
||||
]
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -18,3 +18,4 @@ html {
|
||||
@import (less) "eventlog.less";
|
||||
@import (less) "footer.less";
|
||||
@import (less) "codemirror.less";
|
||||
@import (less) "contentview.less";
|
||||
|
17
web/src/css/contentview.less
Normal file
17
web/src/css/contentview.less
Normal file
@ -0,0 +1,17 @@
|
||||
.contentview {
|
||||
.header {
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight{
|
||||
font-weight: bold;
|
||||
}
|
||||
.offset{
|
||||
color: blue
|
||||
}
|
||||
.text{
|
||||
|
||||
}
|
||||
.codeeditor{
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
@ -5,17 +5,35 @@
|
||||
|
||||
.flow-detail {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
overflow:hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
nav {
|
||||
background-color: #F2F2F2;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 5px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
>article{
|
||||
overflow: auto;
|
||||
padding: 5px 12px 0;
|
||||
}
|
||||
>footer {
|
||||
box-shadow: 0 0 3px gray;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
height:23px;
|
||||
}
|
||||
}
|
||||
|
||||
section.detail, section.error{
|
||||
overflow: auto;
|
||||
padding: 5px 12px 0;
|
||||
}
|
||||
|
||||
|
||||
.first-line {
|
||||
.monospace();
|
||||
background-color: #428bca;
|
||||
@ -38,9 +56,9 @@
|
||||
hr {
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.inline-input {
|
||||
display: inline;
|
||||
margin: 0 -3px;
|
||||
@ -64,8 +82,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.view-options {
|
||||
margin-top: 10px;
|
||||
.view-all-content-btn{
|
||||
float: right;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.flow-detail table {
|
||||
|
@ -2,11 +2,10 @@ import React, { Component, PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import * as ContentViews from './ContentView/ContentViews'
|
||||
import * as MetaViews from './ContentView/MetaViews'
|
||||
import ViewSelector from './ContentView/ViewSelector'
|
||||
import UploadContentButton from './ContentView/UploadContentButton'
|
||||
import DownloadContentButton from './ContentView/DownloadContentButton'
|
||||
import ShowFullContentButton from './ContentView/ShowFullContentButton'
|
||||
|
||||
import { setContentView, displayLarge, updateEdit } from '../ducks/ui/flow'
|
||||
|
||||
import { displayLarge, updateEdit } from '../ducks/ui/flow'
|
||||
|
||||
ContentView.propTypes = {
|
||||
// It may seem a bit weird at the first glance:
|
||||
@ -19,7 +18,7 @@ ContentView.propTypes = {
|
||||
ContentView.isContentTooLarge = msg => msg.contentLength > 1024 * 1024 * (ContentViews.ViewImage.matches(msg) ? 10 : 0.2)
|
||||
|
||||
function ContentView(props) {
|
||||
const { flow, message, contentView, isDisplayLarge, displayLarge, uploadContent, onContentChange, readonly } = props
|
||||
const { flow, message, contentView, isDisplayLarge, displayLarge, onContentChange, readonly } = props
|
||||
|
||||
if (message.contentLength === 0 && readonly) {
|
||||
return <MetaViews.ContentEmpty {...props}/>
|
||||
@ -33,18 +32,11 @@ function ContentView(props) {
|
||||
return <MetaViews.ContentTooLarge {...props} onClick={displayLarge}/>
|
||||
}
|
||||
|
||||
const View = ContentViews[contentView]
|
||||
const View = ContentViews[contentView] || ContentViews['ViewServer']
|
||||
return (
|
||||
<div>
|
||||
<View flow={flow} message={message} readonly={readonly} onChange={onContentChange}/>
|
||||
|
||||
<div className="view-options text-center">
|
||||
<ViewSelector message={message}/>
|
||||
|
||||
<DownloadContentButton flow={flow} message={message}/>
|
||||
|
||||
<UploadContentButton uploadContent={uploadContent}/>
|
||||
</div>
|
||||
<div className="contentview">
|
||||
<View flow={flow} message={message} contentView={contentView} readonly={readonly} onChange={onContentChange}/>
|
||||
<ShowFullContentButton/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export default function CodeEditor ( { content, onChange} ){
|
||||
lineNumbers: true
|
||||
};
|
||||
return (
|
||||
<div onKeyDown={e => e.stopPropagation()}>
|
||||
<div className="codeeditor" onKeyDown={e => e.stopPropagation()}>
|
||||
<Codemirror value={content} onChange={onChange} options={options}/>
|
||||
</div>
|
||||
)
|
||||
|
@ -28,7 +28,8 @@ export default View => class extends React.Component {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
nextProps.message.content !== this.props.message.content ||
|
||||
nextProps.message.contentHash !== this.props.message.contentHash
|
||||
nextProps.message.contentHash !== this.props.message.contentHash ||
|
||||
nextProps.contentView !== this.props.contentView
|
||||
) {
|
||||
this.updateContent(nextProps)
|
||||
}
|
||||
@ -52,7 +53,7 @@ export default View => class extends React.Component {
|
||||
return this.setState({request: undefined, content: ""})
|
||||
}
|
||||
|
||||
let requestUrl = MessageUtils.getContentURL(props.flow, props.message)
|
||||
let requestUrl = MessageUtils.getContentURL(props.flow, props.message, (View.name == 'ViewServer' ? props.contentView : undefined))
|
||||
|
||||
// We use XMLHttpRequest instead of fetch() because fetch() is not (yet) abortable.
|
||||
let request = new XMLHttpRequest();
|
||||
|
31
web/src/js/components/ContentView/ContentViewOptions.jsx
Normal file
31
web/src/js/components/ContentView/ContentViewOptions.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import ViewSelector from './ViewSelector'
|
||||
import UploadContentButton from './UploadContentButton'
|
||||
import DownloadContentButton from './DownloadContentButton'
|
||||
|
||||
ContentViewOptions.propTypes = {
|
||||
flow: React.PropTypes.object.isRequired,
|
||||
message: React.PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
function ContentViewOptions(props) {
|
||||
const { flow, message, uploadContent, readonly, contentViewDescription } = props
|
||||
return (
|
||||
<div className="view-options">
|
||||
<ViewSelector message={message}/>
|
||||
|
||||
<DownloadContentButton flow={flow} message={message}/>
|
||||
|
||||
<UploadContentButton uploadContent={uploadContent}/>
|
||||
|
||||
<span>{contentViewDescription}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
contentViewDescription: state.ui.flow.viewDescription
|
||||
})
|
||||
)(ContentViewOptions)
|
@ -1,4 +1,6 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React, { PropTypes, Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { setContentViewDescription, setContent } from '../../ducks/ui/flow'
|
||||
import ContentLoader from './ContentLoader'
|
||||
import { MessageUtils } from '../../flow/utils'
|
||||
import CodeEditor from './CodeEditor'
|
||||
@ -18,43 +20,68 @@ function ViewImage({ flow, message }) {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
ViewRaw.matches = () => true
|
||||
ViewRaw.propTypes = {
|
||||
Edit.propTypes = {
|
||||
content: React.PropTypes.string.isRequired,
|
||||
}
|
||||
function ViewRaw({ content, readonly, onChange }) {
|
||||
return readonly ? <pre>{content}</pre> : <CodeEditor content={content} onChange={onChange}/>
|
||||
}
|
||||
ViewRaw = ContentLoader(ViewRaw)
|
||||
|
||||
|
||||
const isJSON = /^application\/json$/i
|
||||
ViewJSON.matches = msg => isJSON.test(MessageUtils.getContentType(msg))
|
||||
ViewJSON.propTypes = {
|
||||
content: React.PropTypes.string.isRequired,
|
||||
function Edit({ content, onChange }) {
|
||||
return <CodeEditor content={content} onChange={onChange}/>
|
||||
}
|
||||
function ViewJSON({ content }) {
|
||||
let json = content
|
||||
try {
|
||||
json = JSON.stringify(JSON.parse(content), null, 2);
|
||||
} catch (e) {
|
||||
// @noop
|
||||
Edit = ContentLoader(Edit)
|
||||
|
||||
class ViewServer extends Component {
|
||||
|
||||
componentWillMount(){
|
||||
this.setContentView(this.props)
|
||||
}
|
||||
return <pre>{json}</pre>
|
||||
}
|
||||
ViewJSON = ContentLoader(ViewJSON)
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
if (nextProps.content != this.props.content) {
|
||||
this.setContentView(nextProps)
|
||||
}
|
||||
}
|
||||
setContentView(props){
|
||||
try {
|
||||
this.data = JSON.parse(props.content)
|
||||
}catch(err) {
|
||||
this.data = {lines: [], description: err.message}
|
||||
}
|
||||
|
||||
props.setContentViewDescription(props.contentView != this.data.description ? this.data.description : '')
|
||||
props.setContent(this.data.lines)
|
||||
}
|
||||
render() {
|
||||
const {content, contentView, message, maxLines} = this.props
|
||||
let lines = this.props.showFullContent ? this.data.lines : this.data.lines.slice(0, maxLines)
|
||||
return <div>
|
||||
<pre>
|
||||
{lines.map((line, i) =>
|
||||
<div key={`line${i}`}>
|
||||
{line.map((tuple, j) =>
|
||||
<span key={`tuple${j}`} className={tuple[0]}>
|
||||
{tuple[1]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</pre>
|
||||
{ViewImage.matches(message) &&
|
||||
<ViewImage {...this.props} />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
ViewAuto.matches = () => false
|
||||
ViewAuto.findView = msg => [ViewImage, ViewJSON, ViewRaw].find(v => v.matches(msg)) || ViewRaw
|
||||
ViewAuto.propTypes = {
|
||||
message: React.PropTypes.object.isRequired,
|
||||
flow: React.PropTypes.object.isRequired,
|
||||
}
|
||||
function ViewAuto({ message, flow, readonly, onChange }) {
|
||||
const View = ViewAuto.findView(message)
|
||||
return <View message={message} flow={flow} readonly={readonly} onChange={onChange}/>
|
||||
}
|
||||
|
||||
export { ViewImage, ViewRaw, ViewAuto, ViewJSON }
|
||||
ViewServer = connect(
|
||||
state => ({
|
||||
showFullContent: state.ui.flow.showFullContent,
|
||||
maxLines: state.ui.flow.maxContentLines
|
||||
}),
|
||||
{
|
||||
setContentViewDescription,
|
||||
setContent
|
||||
}
|
||||
)(ContentLoader(ViewServer))
|
||||
|
||||
export { Edit, ViewServer, ViewImage }
|
||||
|
36
web/src/js/components/ContentView/ShowFullContentButton.jsx
Normal file
36
web/src/js/components/ContentView/ShowFullContentButton.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { render } from 'react-dom';
|
||||
import Button from '../common/Button';
|
||||
import { setShowFullContent } from '../../ducks/ui/flow'
|
||||
|
||||
|
||||
|
||||
ShowFullContentButton.propTypes = {
|
||||
setShowFullContent: PropTypes.func.isRequired,
|
||||
showFullContent: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
function ShowFullContentButton ( {setShowFullContent, showFullContent, visibleLines, contentLines} ){
|
||||
|
||||
return (
|
||||
!showFullContent &&
|
||||
<div>
|
||||
<Button className="view-all-content-btn btn-xs" onClick={() => setShowFullContent(true)} text="Show full content"/>
|
||||
<span className="pull-right"> {visibleLines}/{contentLines} are visible </span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
showFullContent: state.ui.flow.showFullContent,
|
||||
visibleLines: state.ui.flow.maxContentLines,
|
||||
contentLines: state.ui.flow.content.length
|
||||
|
||||
}),
|
||||
{
|
||||
setShowFullContent
|
||||
}
|
||||
)(ShowFullContentButton)
|
||||
|
@ -1,47 +1,80 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React, { PropTypes, Component } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { connect } from 'react-redux'
|
||||
import * as ContentViews from './ContentViews'
|
||||
import { setContentView } from "../../ducks/ui/flow";
|
||||
|
||||
|
||||
function ViewButton({ name, setContentView, children, activeView }) {
|
||||
function ViewItem({ name, setContentView, children }) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => setContentView(name)}
|
||||
className={classnames('btn btn-default', { active: name === activeView })}>
|
||||
{children}
|
||||
</button>
|
||||
<li>
|
||||
<a href="#" onClick={() => setContentView(name)}>
|
||||
{children}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
ViewButton = connect(state => ({
|
||||
activeView: state.ui.flow.contentView
|
||||
}), {
|
||||
setContentView
|
||||
})(ViewButton)
|
||||
|
||||
|
||||
ViewSelector.propTypes = {
|
||||
message: PropTypes.object.isRequired,
|
||||
/*ViewSelector.propTypes = {
|
||||
contentViews: PropTypes.array.isRequired,
|
||||
activeView: PropTypes.string.isRequired,
|
||||
isEdit: PropTypes.bool.isRequired,
|
||||
isContentViewSelectorOpen: PropTypes.bool.isRequired,
|
||||
setContentViewSelectorOpen: PropTypes.func.isRequired
|
||||
}*/
|
||||
|
||||
|
||||
class ViewSelector extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.close = this.close.bind(this)
|
||||
this.state = {open: false}
|
||||
}
|
||||
close() {
|
||||
this.setState({open: false})
|
||||
document.removeEventListener('click', this.close)
|
||||
}
|
||||
|
||||
onDropdown(e){
|
||||
e.preventDefault()
|
||||
this.setState({open: !this.state.open})
|
||||
document.addEventListener('click', this.close)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {contentViews, activeView, isEdit, setContentView} = this.props
|
||||
let edit = ContentViews.Edit.displayName
|
||||
|
||||
return (
|
||||
<div className={classnames('dropup pull-left', { open: this.state.open })}>
|
||||
<a className="btn btn-default btn-xs"
|
||||
onClick={ e => this.onDropdown(e) }
|
||||
href="#">
|
||||
<b>View:</b> {activeView}<span className="caret"></span>
|
||||
</a>
|
||||
<ul className="dropdown-menu" role="menu">
|
||||
{contentViews.map(name =>
|
||||
<ViewItem key={name} setContentView={setContentView} name={name}>
|
||||
{name.toLowerCase().replace('_', ' ')}
|
||||
</ViewItem>
|
||||
)}
|
||||
{isEdit &&
|
||||
<ViewItem key={edit} setContentView={setContentView} name={edit}>
|
||||
{edit.toLowerCase()}
|
||||
</ViewItem>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default function ViewSelector({ message }) {
|
||||
|
||||
let autoView = ContentViews.ViewAuto.findView(message)
|
||||
let autoViewName = (autoView.displayName || autoView.name)
|
||||
.toLowerCase()
|
||||
.replace('view', '')
|
||||
.replace(/ContentLoader\((.+)\)/,"$1")
|
||||
|
||||
return (
|
||||
<div className="view-selector btn-group btn-group-xs">
|
||||
|
||||
<ViewButton name="ViewAuto">auto: {autoViewName}</ViewButton>
|
||||
|
||||
{Object.keys(ContentViews).map(name =>
|
||||
name !== "ViewAuto" &&
|
||||
<ViewButton key={name} name={name}>{name.toLowerCase().replace('view', '')}</ViewButton>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default connect (
|
||||
state => ({
|
||||
contentViews: state.settings.contentViews,
|
||||
activeView: state.ui.flow.contentView,
|
||||
isEdit: !!state.ui.flow.modifiedFlow,
|
||||
}), {
|
||||
setContentView,
|
||||
}
|
||||
)(ViewSelector)
|
||||
|
@ -71,7 +71,7 @@ export default class FlowView extends Component {
|
||||
const Tab = FlowView.allTabs[_.capitalize(active)]
|
||||
|
||||
return (
|
||||
<div className="flow-detail" onScroll={this.adjustHead}>
|
||||
<div className="flow-detail">
|
||||
<Nav
|
||||
flow={flow}
|
||||
tabs={tabs}
|
||||
|
@ -114,7 +114,7 @@ export function Timing({ flow }) {
|
||||
|
||||
export default function Details({ flow }) {
|
||||
return (
|
||||
<section>
|
||||
<section className="detail">
|
||||
<h4>Client Connection</h4>
|
||||
<ConnectionInfo conn={flow.client_conn}/>
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux'
|
||||
import { RequestUtils, isValidHttpVersion, parseUrl } from '../../flow/utils.js'
|
||||
import { formatTimeStamp } from '../../utils.js'
|
||||
import ContentView from '../ContentView'
|
||||
import ContentViewOptions from '../ContentView/ContentViewOptions'
|
||||
import ValidateEditor from '../ValueEditor/ValidateEditor'
|
||||
import ValueEditor from '../ValueEditor/ValueEditor'
|
||||
|
||||
@ -81,27 +82,37 @@ const Message = connect(
|
||||
export class Request extends Component {
|
||||
render() {
|
||||
const { flow, isEdit, updateFlow, uploadContent } = this.props
|
||||
|
||||
let noContent = !isEdit && (flow.request.contentLength == 0 || flow.request.contentLength == null)
|
||||
return (
|
||||
<section className="request">
|
||||
<ToggleEdit/>
|
||||
<RequestLine
|
||||
flow={flow}
|
||||
readonly={!isEdit}
|
||||
updateFlow={updateFlow}/>
|
||||
<Headers
|
||||
message={flow.request}
|
||||
readonly={!isEdit}
|
||||
onChange={headers => updateFlow({ request: { headers } })}
|
||||
/>
|
||||
<article>
|
||||
<ToggleEdit/>
|
||||
<RequestLine
|
||||
flow={flow}
|
||||
readonly={!isEdit}
|
||||
updateFlow={updateFlow}/>
|
||||
<Headers
|
||||
message={flow.request}
|
||||
readonly={!isEdit}
|
||||
onChange={headers => updateFlow({ request: { headers } })}
|
||||
/>
|
||||
|
||||
<hr/>
|
||||
<ContentView
|
||||
readonly={!isEdit}
|
||||
flow={flow}
|
||||
onContentChange={content => updateFlow({ request: {content}})}
|
||||
uploadContent={content => uploadContent(flow, content, "request")}
|
||||
message={flow.request}/>
|
||||
<hr/>
|
||||
<ContentView
|
||||
readonly={!isEdit}
|
||||
flow={flow}
|
||||
onContentChange={content => updateFlow({ request: {content}})}
|
||||
message={flow.request}/>
|
||||
</article>
|
||||
{!noContent &&
|
||||
<footer>
|
||||
<ContentViewOptions
|
||||
flow={flow}
|
||||
readonly={!isEdit}
|
||||
message={flow.request}
|
||||
uploadContent={content => uploadContent(flow, content, "request")}/>
|
||||
</footer>
|
||||
}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@ -137,27 +148,38 @@ Request = Message(Request)
|
||||
export class Response extends Component {
|
||||
render() {
|
||||
const { flow, isEdit, updateFlow, uploadContent } = this.props
|
||||
let noContent = !isEdit && (flow.response.contentLength == 0 || flow.response.contentLength == null)
|
||||
|
||||
return (
|
||||
<section className="response">
|
||||
<ToggleEdit/>
|
||||
<ResponseLine
|
||||
flow={flow}
|
||||
readonly={!isEdit}
|
||||
updateFlow={updateFlow}/>
|
||||
<Headers
|
||||
message={flow.response}
|
||||
readonly={!isEdit}
|
||||
onChange={headers => updateFlow({ response: { headers } })}
|
||||
/>
|
||||
<hr/>
|
||||
<ContentView
|
||||
readonly={!isEdit}
|
||||
flow={flow}
|
||||
onContentChange={content => updateFlow({ response: {content}})}
|
||||
uploadContent={content => uploadContent(flow, content, "response")}
|
||||
message={flow.response}
|
||||
/>
|
||||
<article>
|
||||
<ToggleEdit/>
|
||||
<ResponseLine
|
||||
flow={flow}
|
||||
readonly={!isEdit}
|
||||
updateFlow={updateFlow}/>
|
||||
<Headers
|
||||
message={flow.response}
|
||||
readonly={!isEdit}
|
||||
onChange={headers => updateFlow({ response: { headers } })}
|
||||
/>
|
||||
<hr/>
|
||||
<ContentView
|
||||
readonly={!isEdit}
|
||||
flow={flow}
|
||||
onContentChange={content => updateFlow({ response: {content}})}
|
||||
message={flow.response}
|
||||
/>
|
||||
</article>
|
||||
{!noContent &&
|
||||
<footer >
|
||||
<ContentViewOptions
|
||||
flow={flow}
|
||||
message={flow.response}
|
||||
uploadContent={content => uploadContent(flow, content, "response")}
|
||||
readonly={!isEdit}/>
|
||||
</footer>
|
||||
}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@ -194,7 +216,7 @@ ErrorView.propTypes = {
|
||||
|
||||
export function ErrorView({ flow }) {
|
||||
return (
|
||||
<section>
|
||||
<section className="error">
|
||||
<div className="alert alert-warning">
|
||||
{flow.error.msg}
|
||||
<div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
Button.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
@ -6,9 +7,9 @@ Button.propTypes = {
|
||||
icon: PropTypes.string
|
||||
}
|
||||
|
||||
export default function Button({ onClick, text, icon, disabled }) {
|
||||
export default function Button({ onClick, text, icon, disabled, className }) {
|
||||
return (
|
||||
<div className={"btn btn-default"}
|
||||
<div className={classnames(className, 'btn btn-default')}
|
||||
onClick={onClick}
|
||||
disabled={disabled}>
|
||||
{icon && (<i className={"fa fa-fw " + icon}/> )}
|
||||
|
@ -3,28 +3,38 @@ import { getDiff } from "../../utils"
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
export const SET_CONTENT_VIEW = 'UI_FLOWVIEW_SET_CONTENT_VIEW',
|
||||
DISPLAY_LARGE = 'UI_FLOWVIEW_DISPLAY_LARGE',
|
||||
SET_TAB = "UI_FLOWVIEW_SET_TAB",
|
||||
START_EDIT = 'UI_FLOWVIEW_START_EDIT',
|
||||
UPDATE_EDIT = 'UI_FLOWVIEW_UPDATE_EDIT',
|
||||
UPLOAD_CONTENT = 'UI_FLOWVIEW_UPLOAD_CONTENT'
|
||||
export const SET_CONTENT_VIEW = 'UI_FLOWVIEW_SET_CONTENT_VIEW',
|
||||
DISPLAY_LARGE = 'UI_FLOWVIEW_DISPLAY_LARGE',
|
||||
SET_TAB = "UI_FLOWVIEW_SET_TAB",
|
||||
START_EDIT = 'UI_FLOWVIEW_START_EDIT',
|
||||
UPDATE_EDIT = 'UI_FLOWVIEW_UPDATE_EDIT',
|
||||
UPLOAD_CONTENT = 'UI_FLOWVIEW_UPLOAD_CONTENT',
|
||||
SET_SHOW_FULL_CONTENT = 'UI_SET_SHOW_FULL_CONTENT',
|
||||
SET_CONTENT_VIEW_DESCRIPTION = "UI_SET_CONTENT_VIEW_DESCRIPTION",
|
||||
SET_CONTENT = "UI_SET_CONTENT"
|
||||
|
||||
|
||||
const defaultState = {
|
||||
displayLarge: false,
|
||||
contentViewDescription: '',
|
||||
showFullContent: false,
|
||||
modifiedFlow: false,
|
||||
contentView: 'ViewAuto',
|
||||
contentView: 'Auto',
|
||||
tab: 'request',
|
||||
content: [],
|
||||
maxContentLines: 80,
|
||||
}
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
let wasInEditMode = !!(state.modifiedFlow)
|
||||
switch (action.type) {
|
||||
|
||||
case START_EDIT:
|
||||
return {
|
||||
...state,
|
||||
modifiedFlow: action.flow,
|
||||
contentView: 'Edit',
|
||||
showFullContent: true
|
||||
}
|
||||
|
||||
case UPDATE_EDIT:
|
||||
@ -38,6 +48,9 @@ export default function reducer(state = defaultState, action) {
|
||||
...state,
|
||||
modifiedFlow: false,
|
||||
displayLarge: false,
|
||||
contentView: (wasInEditMode ? 'Auto' : state.contentView),
|
||||
viewDescription: '',
|
||||
showFullContent: false,
|
||||
}
|
||||
|
||||
case flowsActions.UPDATE:
|
||||
@ -49,23 +62,47 @@ export default function reducer(state = defaultState, action) {
|
||||
...state,
|
||||
modifiedFlow: false,
|
||||
displayLarge: false,
|
||||
contentView: (wasInEditMode ? 'Auto' : state.contentView),
|
||||
viewDescription: '',
|
||||
showFullContent: false
|
||||
}
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
|
||||
case SET_CONTENT_VIEW_DESCRIPTION:
|
||||
return {
|
||||
...state,
|
||||
viewDescription: action.description
|
||||
}
|
||||
|
||||
case SET_SHOW_FULL_CONTENT:
|
||||
return {
|
||||
...state,
|
||||
showFullContent: action.show
|
||||
}
|
||||
|
||||
case SET_TAB:
|
||||
return {
|
||||
...state,
|
||||
tab: action.tab,
|
||||
displayLarge: false,
|
||||
showFullContent: false
|
||||
}
|
||||
|
||||
case SET_CONTENT_VIEW:
|
||||
return {
|
||||
...state,
|
||||
contentView: action.contentView,
|
||||
showFullContent: action.contentView == 'Edit'
|
||||
}
|
||||
|
||||
case SET_CONTENT:
|
||||
let isFullContentShown = action.content.length < state.maxContentLines
|
||||
return {
|
||||
...state,
|
||||
content: action.content,
|
||||
showFullContent: isFullContentShown
|
||||
}
|
||||
|
||||
case DISPLAY_LARGE:
|
||||
@ -98,6 +135,22 @@ export function updateEdit(update) {
|
||||
return { type: UPDATE_EDIT, update }
|
||||
}
|
||||
|
||||
export function setContentViewDescription(description) {
|
||||
return { type: SET_CONTENT_VIEW_DESCRIPTION, description }
|
||||
}
|
||||
|
||||
export function setShowFullContent(show) {
|
||||
return { type: SET_SHOW_FULL_CONTENT, show }
|
||||
}
|
||||
|
||||
export function updateEdit(update) {
|
||||
return { type: UPDATE_EDIT, update }
|
||||
}
|
||||
|
||||
export function setContent(content){
|
||||
return { type: SET_CONTENT, content}
|
||||
}
|
||||
|
||||
export function stopEdit(flow, modifiedFlow) {
|
||||
let diff = getDiff(flow, modifiedFlow)
|
||||
return flowsActions.update(flow, diff)
|
||||
|
@ -43,14 +43,14 @@ export var MessageUtils = {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getContentURL: function (flow, message) {
|
||||
getContentURL: function (flow, message, view) {
|
||||
if (message === flow.request) {
|
||||
message = "request";
|
||||
} else if (message === flow.response) {
|
||||
message = "response";
|
||||
}
|
||||
return "/flows/" + flow.id + "/" + message + "/content";
|
||||
},
|
||||
return `/flows/${flow.id}/${message}/content` + (view ? `/${view}` : '');
|
||||
}
|
||||
};
|
||||
|
||||
export var RequestUtils = _.extend(MessageUtils, {
|
||||
|
Loading…
Reference in New Issue
Block a user