mirror of
https://github.com/zhigang1992/mitmproxy.git
synced 2026-01-12 22:48:54 +08:00
@@ -243,7 +243,6 @@ class FlowHandler(RequestHandler):
|
||||
flow = self.flow
|
||||
flow.backup()
|
||||
for a, b in six.iteritems(self.json):
|
||||
|
||||
if a == "request":
|
||||
request = flow.request
|
||||
for k, v in six.iteritems(b):
|
||||
@@ -260,7 +259,6 @@ class FlowHandler(RequestHandler):
|
||||
|
||||
elif a == "response":
|
||||
response = flow.response
|
||||
|
||||
for k, v in six.iteritems(b):
|
||||
if k == "msg":
|
||||
response.msg = str(v)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4
web/src/css/dropdown.less
Normal file
4
web/src/css/dropdown.less
Normal file
@@ -0,0 +1,4 @@
|
||||
hr.divider {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { render } from 'react-dom';
|
||||
import React, {PropTypes} from 'react'
|
||||
import Codemirror from 'react-codemirror';
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@ function Edit({ content, onChange }) {
|
||||
Edit = ContentLoader(Edit)
|
||||
|
||||
class ViewServer extends Component {
|
||||
static propTypes = {
|
||||
showFullContent: PropTypes.bool.isRequired,
|
||||
maxLines: PropTypes.number.isRequired,
|
||||
setContentViewDescription : PropTypes.func.isRequired,
|
||||
setContent: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
this.setContentView(this.props)
|
||||
@@ -40,6 +46,7 @@ class ViewServer extends Component {
|
||||
this.setContentView(nextProps)
|
||||
}
|
||||
}
|
||||
|
||||
setContentView(props){
|
||||
try {
|
||||
this.data = JSON.parse(props.content)
|
||||
@@ -50,25 +57,31 @@ class ViewServer extends Component {
|
||||
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>
|
||||
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>
|
||||
)}
|
||||
{line.map((element, j) => {
|
||||
let [style, text] = element
|
||||
return (
|
||||
<span key={`tuple${j}`} className={style}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</pre>
|
||||
{ViewImage.matches(message) &&
|
||||
<ViewImage {...this.props} />
|
||||
}
|
||||
</div>
|
||||
{ViewImage.matches(message) &&
|
||||
<ViewImage {...this.props} />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
import { PropTypes } from 'react'
|
||||
import FileChooser from '../common/FileChooser'
|
||||
|
||||
UploadContentButton.propTypes = {
|
||||
uploadContent: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default function UploadContentButton({ uploadContent }) {
|
||||
|
||||
let fileInput;
|
||||
|
||||
|
||||
return (
|
||||
<a className="btn btn-default btn-xs"
|
||||
onClick={() => fileInput.click()}
|
||||
title="Upload a file to replace the content.">
|
||||
<i className="fa fa-upload"/>
|
||||
<input
|
||||
ref={ref => fileInput = ref}
|
||||
className="hidden"
|
||||
type="file"
|
||||
onChange={e => {
|
||||
if (e.target.files.length > 0) uploadContent(e.target.files[0])
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<FileChooser
|
||||
icon="fa-upload"
|
||||
title="Upload a file to replace the content."
|
||||
onOpenFile={uploadContent}
|
||||
className="btn btn-default btn-xs"/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,72 +1,36 @@
|
||||
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 ViewItem({ name, setContentView, children }) {
|
||||
return (
|
||||
<li>
|
||||
<a href="#" onClick={() => setContentView(name)}>
|
||||
{children}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
import { setContentView } from '../../ducks/ui/flow';
|
||||
import Dropdown from '../common/Dropdown'
|
||||
|
||||
|
||||
/*ViewSelector.propTypes = {
|
||||
ViewSelector.propTypes = {
|
||||
contentViews: PropTypes.array.isRequired,
|
||||
activeView: PropTypes.string.isRequired,
|
||||
isEdit: PropTypes.bool.isRequired,
|
||||
isContentViewSelectorOpen: PropTypes.bool.isRequired,
|
||||
setContentViewSelectorOpen: PropTypes.func.isRequired
|
||||
}*/
|
||||
setContentView: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
function ViewSelector ({contentViews, activeView, isEdit, setContentView}){
|
||||
let edit = ContentViews.Edit.displayName
|
||||
let inner = <span> <b>View:</b> {activeView}<span className="caret"></span> </span>
|
||||
|
||||
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>
|
||||
return (
|
||||
<Dropdown dropup className="pull-left" btnClass="btn btn-default btn-xs" text={inner}>
|
||||
{contentViews.map(name =>
|
||||
<a href="#" key={name} onClick={e => {e.preventDefault(); setContentView(name)}}>
|
||||
{name.toLowerCase().replace('_', ' ')}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
{isEdit &&
|
||||
<a href="#" onClick={e => {e.preventDefault(); setContentView(edit)}}>
|
||||
{edit.toLowerCase()}
|
||||
</a>
|
||||
}
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect (
|
||||
|
||||
@@ -7,40 +7,41 @@ Footer.propTypes = {
|
||||
}
|
||||
|
||||
function Footer({ settings }) {
|
||||
let {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickyauth, stickycookie, stream} = settings;
|
||||
return (
|
||||
<footer>
|
||||
{settings.mode && settings.mode != "regular" && (
|
||||
<span className="label label-success">{settings.mode} mode</span>
|
||||
{mode && mode != "regular" && (
|
||||
<span className="label label-success">{mode} mode</span>
|
||||
)}
|
||||
{settings.intercept && (
|
||||
<span className="label label-success">Intercept: {settings.intercept}</span>
|
||||
{intercept && (
|
||||
<span className="label label-success">Intercept: {intercept}</span>
|
||||
)}
|
||||
{settings.showhost && (
|
||||
{showhost && (
|
||||
<span className="label label-success">showhost</span>
|
||||
)}
|
||||
{settings.no_upstream_cert && (
|
||||
{no_upstream_cert && (
|
||||
<span className="label label-success">no-upstream-cert</span>
|
||||
)}
|
||||
{settings.rawtcp && (
|
||||
{rawtcp && (
|
||||
<span className="label label-success">raw-tcp</span>
|
||||
)}
|
||||
{!settings.http2 && (
|
||||
{!http2 && (
|
||||
<span className="label label-success">no-http2</span>
|
||||
)}
|
||||
{settings.anticache && (
|
||||
{anticache && (
|
||||
<span className="label label-success">anticache</span>
|
||||
)}
|
||||
{settings.anticomp && (
|
||||
{anticomp && (
|
||||
<span className="label label-success">anticomp</span>
|
||||
)}
|
||||
{settings.stickyauth && (
|
||||
<span className="label label-success">stickyauth: {settings.stickyauth}</span>
|
||||
{stickyauth && (
|
||||
<span className="label label-success">stickyauth: {stickyauth}</span>
|
||||
)}
|
||||
{settings.stickycookie && (
|
||||
<span className="label label-success">stickycookie: {settings.stickycookie}</span>
|
||||
{stickycookie && (
|
||||
<span className="label label-success">stickycookie: {stickycookie}</span>
|
||||
)}
|
||||
{settings.stream && (
|
||||
<span className="label label-success">stream: {formatSize(settings.stream)}</span>
|
||||
{stream && (
|
||||
<span className="label label-success">stream: {formatSize(stream)}</span>
|
||||
)}
|
||||
</footer>
|
||||
)
|
||||
|
||||
@@ -1,103 +1,46 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import classnames from 'classnames'
|
||||
import FileChooser from '../common/FileChooser'
|
||||
import Dropdown, {Divider} from '../common/Dropdown'
|
||||
import * as flowsActions from '../../ducks/flows'
|
||||
|
||||
class FileMenu extends Component {
|
||||
FileMenu.propTypes = {
|
||||
clearFlows: PropTypes.func.isRequired,
|
||||
loadFlows: PropTypes.func.isRequired,
|
||||
saveFlows: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = { show: false }
|
||||
FileMenu.onNewClick = (e, clearFlows) => {
|
||||
e.preventDefault();
|
||||
if (confirm('Delete all flows?'))
|
||||
clearFlows()
|
||||
}
|
||||
|
||||
this.close = this.close.bind(this)
|
||||
this.onFileClick = this.onFileClick.bind(this)
|
||||
this.onNewClick = this.onNewClick.bind(this)
|
||||
this.onOpenClick = this.onOpenClick.bind(this)
|
||||
this.onOpenFile = this.onOpenFile.bind(this)
|
||||
this.onSaveClick = this.onSaveClick.bind(this)
|
||||
}
|
||||
function FileMenu ({clearFlows, loadFlows, saveFlows}) {
|
||||
return (
|
||||
<Dropdown className="pull-left" btnClass="special" text="mitmproxy">
|
||||
<a href="#" onClick={e => FileMenu.onNewClick(e, clearFlows)}>
|
||||
<i className="fa fa-fw fa-file"></i>
|
||||
New
|
||||
</a>
|
||||
<FileChooser
|
||||
icon="fa-folder-open"
|
||||
text="Open..."
|
||||
onOpenFile={file => loadFlows(file)}
|
||||
/>
|
||||
<a href="#" onClick={e =>{ e.preventDefault(); saveFlows();}}>
|
||||
<i className="fa fa-fw fa-floppy-o"></i>
|
||||
Save...
|
||||
</a>
|
||||
|
||||
close() {
|
||||
this.setState({ show: false })
|
||||
document.removeEventListener('click', this.close)
|
||||
}
|
||||
<Divider/>
|
||||
|
||||
onFileClick(e) {
|
||||
e.preventDefault()
|
||||
|
||||
if (this.state.show) {
|
||||
return
|
||||
}
|
||||
|
||||
document.addEventListener('click', this.close)
|
||||
this.setState({ show: true })
|
||||
}
|
||||
|
||||
onNewClick(e) {
|
||||
e.preventDefault()
|
||||
if (confirm('Delete all flows?')) {
|
||||
this.props.clearFlows()
|
||||
}
|
||||
}
|
||||
|
||||
onOpenClick(e) {
|
||||
e.preventDefault()
|
||||
this.fileInput.click()
|
||||
}
|
||||
|
||||
onOpenFile(e) {
|
||||
e.preventDefault()
|
||||
if (e.target.files.length > 0) {
|
||||
this.props.loadFlows(e.target.files[0])
|
||||
this.fileInput.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
onSaveClick(e) {
|
||||
e.preventDefault()
|
||||
this.props.saveFlows()
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={classnames('dropdown pull-left', { open: this.state.show })}>
|
||||
<a href="#" className="special" onClick={this.onFileClick}>mitmproxy</a>
|
||||
<ul className="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="#" onClick={this.onNewClick}>
|
||||
<i className="fa fa-fw fa-file"></i>
|
||||
New
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" onClick={this.onOpenClick}>
|
||||
<i className="fa fa-fw fa-folder-open"></i>
|
||||
Open...
|
||||
</a>
|
||||
<input
|
||||
ref={ref => this.fileInput = ref}
|
||||
className="hidden"
|
||||
type="file"
|
||||
onChange={this.onOpenFile}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" onClick={this.onSaveClick}>
|
||||
<i className="fa fa-fw fa-floppy-o"></i>
|
||||
Save...
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" className="divider"></li>
|
||||
<li>
|
||||
<a href="http://mitm.it/" target="_blank">
|
||||
<i className="fa fa-fw fa-external-link"></i>
|
||||
Install Certificates...
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<a href="http://mitm.it/" target="_blank">
|
||||
<i className="fa fa-fw fa-external-link"></i>
|
||||
Install Certificates...
|
||||
</a>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -8,10 +8,14 @@ FlowMenu.title = 'Flow'
|
||||
|
||||
FlowMenu.propTypes = {
|
||||
flow: PropTypes.object.isRequired,
|
||||
acceptFlow: PropTypes.func.isRequired,
|
||||
replayFlow: PropTypes.func.isRequired,
|
||||
duplicateFlow: PropTypes.func.isRequired,
|
||||
removeFlow: PropTypes.func.isRequired,
|
||||
revertFlow: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
function FlowMenu({ flow, acceptFlow, replayFlow, duplicateFlow, removeFlow, revertFlow }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="menu-row">
|
||||
|
||||
@@ -41,17 +41,17 @@ function OptionMenu({ settings, updateSettings }) {
|
||||
/>
|
||||
<ToggleInputButton name="stickyauth" placeholder="Sticky auth filter"
|
||||
checked={!!settings.stickyauth}
|
||||
txt={settings.stickyauth || ''}
|
||||
txt={settings.stickyauth}
|
||||
onToggleChanged={txt => updateSettings({ stickyauth: !settings.stickyauth ? txt : null })}
|
||||
/>
|
||||
<ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter"
|
||||
checked={!!settings.stickycookie}
|
||||
txt={settings.stickycookie || ''}
|
||||
txt={settings.stickycookie}
|
||||
onToggleChanged={txt => updateSettings({ stickycookie: !settings.stickycookie ? txt : null })}
|
||||
/>
|
||||
<ToggleInputButton name="stream" placeholder="stream..."
|
||||
checked={!!settings.stream}
|
||||
txt={settings.stream || ''}
|
||||
txt={settings.stream}
|
||||
inputType="number"
|
||||
onToggleChanged={txt => updateSettings({ stream: !settings.stream ? txt : null })}
|
||||
/>
|
||||
|
||||
53
web/src/js/components/common/Dropdown.jsx
Normal file
53
web/src/js/components/common/Dropdown.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export const Divider = () => <hr className="divider"/>
|
||||
|
||||
export default class Dropdown extends Component {
|
||||
|
||||
static propTypes = {
|
||||
dropup: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
btnClass: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
dropup: false
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = { open: false }
|
||||
this.close = this.close.bind(this)
|
||||
this.open = this.open.bind(this)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.setState({ open: false })
|
||||
document.removeEventListener('click', this.close)
|
||||
}
|
||||
|
||||
open(e){
|
||||
e.preventDefault()
|
||||
if (this.state.open) {
|
||||
return
|
||||
}
|
||||
this.setState({open: !this.state.open})
|
||||
document.addEventListener('click', this.close)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {dropup, className, btnClass, text, children} = this.props
|
||||
return (
|
||||
<div className={classnames( (dropup ? 'dropup' : 'dropdown'), className, { open: this.state.open })}>
|
||||
<a href='#' className={btnClass}
|
||||
onClick={this.open}>
|
||||
{text}
|
||||
</a>
|
||||
<ul className="dropdown-menu" role="menu">
|
||||
{children.map ( (item, i) => <li key={i}> {item} </li> )}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
27
web/src/js/components/common/FileChooser.jsx
Normal file
27
web/src/js/components/common/FileChooser.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
FileChooser.propTypes = {
|
||||
icon: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
onOpenFile: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default function FileChooser({ icon, text, className, title, onOpenFile }) {
|
||||
let fileInput;
|
||||
return (
|
||||
<a href='#' onClick={() => fileInput.click()}
|
||||
className={className}
|
||||
title={title}>
|
||||
<i className={'fa fa-fw ' + icon}></i>
|
||||
{text}
|
||||
<input
|
||||
ref={ref => fileInput = ref}
|
||||
className="hidden"
|
||||
type="file"
|
||||
onChange={e => { e.preventDefault(); if(e.target.files.length > 0) onOpenFile(e.target.files[0]); fileInput = "";}}
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -6,17 +6,16 @@ export default class ToggleInputButton extends Component {
|
||||
|
||||
static propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
txt: PropTypes.string.isRequired,
|
||||
onToggleChanged: PropTypes.func.isRequired
|
||||
txt: PropTypes.string,
|
||||
onToggleChanged: PropTypes.func.isRequired,
|
||||
checked: PropTypes.bool.isRequired,
|
||||
placeholder: PropTypes.string.isRequired,
|
||||
inputType: PropTypes.string
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { txt: props.txt }
|
||||
}
|
||||
|
||||
onChange(e) {
|
||||
this.setState({ txt: e.target.value })
|
||||
this.state = { txt: props.txt || '' }
|
||||
}
|
||||
|
||||
onKeyDown(e) {
|
||||
@@ -27,23 +26,24 @@ export default class ToggleInputButton extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {checked, onToggleChanged, name, inputType, placeholder} = this.props
|
||||
return (
|
||||
<div className="input-group toggle-input-btn">
|
||||
<span className="input-group-btn"
|
||||
onClick={() => this.props.onToggleChanged(this.state.txt)}>
|
||||
<div className={classnames('btn', this.props.checked ? 'btn-primary' : 'btn-default')}>
|
||||
<span className={classnames('fa', this.props.checked ? 'fa-check-square-o' : 'fa-square-o')}/>
|
||||
onClick={() => onToggleChanged(this.state.txt)}>
|
||||
<div className={classnames('btn', checked ? 'btn-primary' : 'btn-default')}>
|
||||
<span className={classnames('fa', checked ? 'fa-check-square-o' : 'fa-square-o')}/>
|
||||
|
||||
{this.props.name}
|
||||
{name}
|
||||
</div>
|
||||
</span>
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder={this.props.placeholder}
|
||||
disabled={this.props.checked}
|
||||
placeholder={placeholder}
|
||||
disabled={checked}
|
||||
value={this.state.txt}
|
||||
type={this.props.inputType}
|
||||
onChange={e => this.onChange(e)}
|
||||
type={inputType || 'text'}
|
||||
onChange={e => this.setState({ txt: e.target.value })}
|
||||
onKeyDown={e => this.onKeyDown(e)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fetchApi } from '../utils'
|
||||
import reduceList, * as listActions from './utils/list'
|
||||
import { selectRelative } from './flowView'
|
||||
|
||||
import * as msgQueueActions from './msgQueue'
|
||||
import * as websocketActions from './websocket'
|
||||
@@ -210,5 +211,14 @@ export function updateFlow(item) {
|
||||
* @private
|
||||
*/
|
||||
export function removeFlow(id) {
|
||||
return { type: REMOVE, id }
|
||||
return (dispatch, getState) => {
|
||||
let currentIndex = getState().flowView.indexOf[getState().flows.selected[0]]
|
||||
let maxIndex = getState().flowView.data.length - 1
|
||||
let deleteLastEntry = maxIndex == 0
|
||||
if (deleteLastEntry)
|
||||
dispatch(select())
|
||||
else
|
||||
dispatch(selectRelative(currentIndex == maxIndex ? -1 : 1) )
|
||||
dispatch({ type: REMOVE, id })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,5 @@ export function setContent(content){
|
||||
}
|
||||
|
||||
export function stopEdit(flow, modifiedFlow) {
|
||||
let diff = getDiff(flow, modifiedFlow)
|
||||
return flowsActions.update(flow, diff)
|
||||
return flowsActions.update(flow, getDiff(flow, modifiedFlow))
|
||||
}
|
||||
|
||||
@@ -107,14 +107,15 @@ fetchApi.put = (url, json, options) => fetchApi(
|
||||
...options
|
||||
}
|
||||
)
|
||||
|
||||
// deep comparison of two json objects (dicts). arrays are handeled as a single value.
|
||||
// return: json object including only the changed keys value pairs.
|
||||
export function getDiff(obj1, obj2) {
|
||||
let result = {...obj2};
|
||||
for(let key in obj1) {
|
||||
if(_.isEqual(obj2[key], obj1[key]))
|
||||
result[key] = undefined
|
||||
else if(!(Array.isArray(obj2[key]) && Array.isArray(obj1[key])) &&
|
||||
typeof obj2[key] == 'object' && typeof obj1[key] == 'object')
|
||||
else if(Object.prototype.toString.call(obj2[key]) === '[object Object]' &&
|
||||
Object.prototype.toString.call(obj1[key]) === '[object Object]' )
|
||||
result[key] = getDiff(obj1[key], obj2[key])
|
||||
}
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user