mirror of
https://github.com/zhigang1992/mitmproxy.git
synced 2026-03-26 08:54:48 +08:00
[web] various fixes
This commit is contained in:
@@ -327,6 +327,9 @@ class View(collections.Sequence):
|
||||
def resume(self, f):
|
||||
self.update(f)
|
||||
|
||||
def kill(self, f):
|
||||
self.update(f)
|
||||
|
||||
|
||||
class Focus:
|
||||
"""
|
||||
|
||||
@@ -169,6 +169,8 @@ class Flow(stateobject.StateObject):
|
||||
self.reply.take()
|
||||
self.reply.kill(force=True)
|
||||
self.reply.commit()
|
||||
self.live = False
|
||||
master.addons("kill", self)
|
||||
|
||||
def intercept(self, master):
|
||||
"""
|
||||
@@ -190,4 +192,4 @@ class Flow(stateobject.StateObject):
|
||||
self.intercepted = False
|
||||
self.reply.ack()
|
||||
self.reply.commit()
|
||||
master.addons("intercept", self)
|
||||
master.addons("resume", self)
|
||||
|
||||
@@ -31,7 +31,8 @@ def flow_to_json(flow: mitmproxy.flow.Flow) -> dict:
|
||||
"intercepted": flow.intercepted,
|
||||
"client_conn": flow.client_conn.get_state(),
|
||||
"server_conn": flow.server_conn.get_state(),
|
||||
"type": flow.type
|
||||
"type": flow.type,
|
||||
"modified": flow.modified(),
|
||||
}
|
||||
if flow.error:
|
||||
f["error"] = flow.error.get_state()
|
||||
@@ -222,17 +223,30 @@ class ClearAll(RequestHandler):
|
||||
self.master.events.clear()
|
||||
|
||||
|
||||
class AcceptFlows(RequestHandler):
|
||||
class ResumeFlows(RequestHandler):
|
||||
def post(self):
|
||||
for f in self.view:
|
||||
f.resume(self.master)
|
||||
|
||||
|
||||
class AcceptFlow(RequestHandler):
|
||||
class KillFlows(RequestHandler):
|
||||
def post(self):
|
||||
for f in self.view:
|
||||
if f.killable:
|
||||
f.kill(self.master)
|
||||
|
||||
|
||||
class ResumeFlow(RequestHandler):
|
||||
def post(self, flow_id):
|
||||
self.flow.resume(self.master)
|
||||
|
||||
|
||||
class KillFlow(RequestHandler):
|
||||
def post(self, flow_id):
|
||||
if self.flow.killable:
|
||||
self.flow.kill(self.master)
|
||||
|
||||
|
||||
class FlowHandler(RequestHandler):
|
||||
def delete(self, flow_id):
|
||||
if self.flow.killable:
|
||||
@@ -410,9 +424,11 @@ class Application(tornado.web.Application):
|
||||
(r"/events", Events),
|
||||
(r"/flows", Flows),
|
||||
(r"/flows/dump", DumpFlows),
|
||||
(r"/flows/accept", AcceptFlows),
|
||||
(r"/flows/resume", ResumeFlows),
|
||||
(r"/flows/kill", KillFlows),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)", FlowHandler),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/accept", AcceptFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/resume", ResumeFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/kill", KillFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/duplicate", DuplicateFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/replay", ReplayFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/revert", RevertFlow),
|
||||
|
||||
@@ -68,7 +68,7 @@ class WebMaster(master.Master):
|
||||
app.ClientConnection.broadcast(
|
||||
resource="flows",
|
||||
cmd="remove",
|
||||
data=dict(id=flow.id)
|
||||
data=flow.id
|
||||
)
|
||||
|
||||
def _sig_view_refresh(self, view):
|
||||
|
||||
@@ -130,28 +130,98 @@ body,
|
||||
margin: 1px 0 0px;
|
||||
}
|
||||
header {
|
||||
padding-top: 0.5em;
|
||||
padding-top: 6px;
|
||||
background-color: white;
|
||||
}
|
||||
header .menu {
|
||||
padding: 10px;
|
||||
header menu {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: solid #a6a6a6 1px;
|
||||
height: 85px;
|
||||
overflow: visible;
|
||||
}
|
||||
.menu-row {
|
||||
.menu-group {
|
||||
margin: 0 3px;
|
||||
display: inline-block;
|
||||
height: 85px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.menu-content {
|
||||
height: 69px;
|
||||
text-align: center;
|
||||
}
|
||||
.menu-content > .btn {
|
||||
height: 69px;
|
||||
text-align: center;
|
||||
margin: 0 1px;
|
||||
padding: 12px 5px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
.menu-content > .btn i {
|
||||
font-size: 20px;
|
||||
display: block;
|
||||
margin: 0 auto 5px;
|
||||
}
|
||||
.menu-entry {
|
||||
text-align: left;
|
||||
height: 23px;
|
||||
line-height: 1;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
.menu-entry label {
|
||||
font-size: 1.2rem;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
}
|
||||
.menu-entry input[type=checkbox] {
|
||||
margin: 0 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.menu-legend {
|
||||
height: 16px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
.menu-group + .menu-group:before {
|
||||
margin-left: -3px;
|
||||
content: " ";
|
||||
border-left: solid 1px #e6e6e6;
|
||||
margin-top: 10px;
|
||||
height: 65px;
|
||||
position: absolute;
|
||||
}
|
||||
.menu-main {
|
||||
margin-left: -2px;
|
||||
margin-right: -3px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
.filter-input {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
padding-left: 2.5px;
|
||||
padding-right: 2.5px;
|
||||
margin-bottom: 5px;
|
||||
padding: 2.5px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.filter-input {
|
||||
float: left;
|
||||
width: 25%;
|
||||
width: 41.66666667%;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.filter-input {
|
||||
padding: 2px 2.5px;
|
||||
}
|
||||
.filter-input > .form-control,
|
||||
.filter-input > .input-group-addon,
|
||||
.filter-input > .input-group-btn > .btn {
|
||||
height: 23.5px;
|
||||
padding: 1px 5px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
.filter-input .popover {
|
||||
@@ -258,6 +328,10 @@ header .menu {
|
||||
.flow-table .col-path .fa-pause {
|
||||
color: #ff8000;
|
||||
}
|
||||
.flow-table .col-path .fa-exclamation,
|
||||
.flow-table .col-path .fa-times {
|
||||
color: darkred;
|
||||
}
|
||||
.flow-table .col-method {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -80,17 +80,30 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
|
||||
self.view.add(f)
|
||||
self.events.data = events
|
||||
|
||||
def test_accept(self):
|
||||
def test_resume(self):
|
||||
for f in self.view:
|
||||
f.reply.handle()
|
||||
f.intercept(self.master)
|
||||
|
||||
assert self.fetch(
|
||||
"/flows/42/accept", method="POST").code == 200
|
||||
"/flows/42/resume", method="POST").code == 200
|
||||
assert sum(f.intercepted for f in self.view) == 1
|
||||
assert self.fetch("/flows/accept", method="POST").code == 200
|
||||
assert self.fetch("/flows/resume", method="POST").code == 200
|
||||
assert all(not f.intercepted for f in self.view)
|
||||
|
||||
def test_kill(self):
|
||||
for f in self.view:
|
||||
f.backup()
|
||||
f.reply.handle()
|
||||
f.intercept(self.master)
|
||||
|
||||
assert self.fetch("/flows/42/kill", method="POST").code == 200
|
||||
assert sum(f.killable for f in self.view) == 1
|
||||
assert self.fetch("/flows/kill", method="POST").code == 200
|
||||
assert all(not f.killable for f in self.view)
|
||||
for f in self.view:
|
||||
f.revert()
|
||||
|
||||
def test_flow_delete(self):
|
||||
f = self.view.get_by_id("42")
|
||||
assert f
|
||||
|
||||
@@ -109,6 +109,9 @@
|
||||
.fa-pause {
|
||||
color: @interceptorange;
|
||||
}
|
||||
.fa-exclamation, .fa-times {
|
||||
color: darkred;
|
||||
}
|
||||
}
|
||||
.col-method {
|
||||
width: 60px;
|
||||
@@ -125,4 +128,4 @@
|
||||
td.col-time, td.col-size {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ header {
|
||||
|
||||
|
||||
.menu-entry {
|
||||
text-align: left;
|
||||
height: (@menu-height - @menu-legend-height)/3;
|
||||
line-height: 1;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
@@ -16,7 +16,9 @@ function ShowFullContentButton ( {setShowFullContent, showFullContent, visibleLi
|
||||
return (
|
||||
!showFullContent &&
|
||||
<div>
|
||||
<Button className="view-all-content-btn btn-xs" onClick={() => setShowFullContent()} text="Show full content"/>
|
||||
<Button className="view-all-content-btn btn-xs" onClick={() => setShowFullContent()}>
|
||||
Show full content
|
||||
</Button>
|
||||
<span className="pull-right"> {visibleLines}/{contentLines} are visible </span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -54,6 +54,15 @@ IconColumn.getIcon = flow => {
|
||||
}
|
||||
|
||||
export function PathColumn({ flow }) {
|
||||
|
||||
let err;
|
||||
if(flow.error){
|
||||
if (flow.error.msg === "Connection killed"){
|
||||
err = <i className="fa fa-fw fa-times pull-right"></i>
|
||||
} else {
|
||||
err = <i className="fa fa-fw fa-exclamation pull-right"></i>
|
||||
}
|
||||
}
|
||||
return (
|
||||
<td className="col-path">
|
||||
{flow.request.is_replay && (
|
||||
@@ -62,6 +71,7 @@ export function PathColumn({ flow }) {
|
||||
{flow.intercepted && (
|
||||
<i className="fa fa-fw fa-pause pull-right"></i>
|
||||
)}
|
||||
{err}
|
||||
{RequestUtils.pretty_url(flow.request)}
|
||||
</td>
|
||||
)
|
||||
@@ -109,7 +119,7 @@ export function TimeColumn({ flow }) {
|
||||
return (
|
||||
<td className="col-time">
|
||||
{flow.response ? (
|
||||
formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start))
|
||||
formatTimeDelta(1000 * (flow.response.timestamp_end - flow.server_conn.timestamp_start))
|
||||
) : (
|
||||
'...'
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,9 @@ class Header extends Component {
|
||||
if(selectedFlowId)
|
||||
entries.push(FlowMenu)
|
||||
|
||||
const Active = _.find(entries, (e) => e.title == activeMenu)
|
||||
// Make sure to have a fallback in case FlowMenu is selected but we don't have any flows
|
||||
// (e.g. because they are all deleted or not yet received)
|
||||
const Active = _.find(entries, (e) => e.title == activeMenu) || MainMenu
|
||||
|
||||
return (
|
||||
<header>
|
||||
|
||||
@@ -21,23 +21,23 @@ function FileMenu ({clearFlows, loadFlows, saveFlows}) {
|
||||
<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
|
||||
New
|
||||
</a>
|
||||
<FileChooser
|
||||
icon="fa-folder-open"
|
||||
text="Open..."
|
||||
text=" Open..."
|
||||
onOpenFile={file => loadFlows(file)}
|
||||
/>
|
||||
<a href="#" onClick={e =>{ e.preventDefault(); saveFlows();}}>
|
||||
<i className="fa fa-fw fa-floppy-o"></i>
|
||||
Save...
|
||||
Save...
|
||||
</a>
|
||||
|
||||
<Divider/>
|
||||
|
||||
<a href="http://mitm.it/" target="_blank">
|
||||
<i className="fa fa-fw fa-external-link"></i>
|
||||
Install Certificates...
|
||||
Install Certificates...
|
||||
</a>
|
||||
</Dropdown>
|
||||
)
|
||||
|
||||
@@ -8,21 +8,23 @@ FlowMenu.title = 'Flow'
|
||||
|
||||
FlowMenu.propTypes = {
|
||||
flow: PropTypes.object,
|
||||
acceptFlow: PropTypes.func.isRequired,
|
||||
resumeFlow: PropTypes.func.isRequired,
|
||||
killFlow: 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 }) {
|
||||
function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow, removeFlow, revertFlow }) {
|
||||
if (!flow)
|
||||
return <div/>
|
||||
return (
|
||||
<div>
|
||||
<div className="menu-group">
|
||||
<div className="menu-content">
|
||||
<Button title="[r]eplay flow" icon="fa-repeat text-primary" onClick={() => replayFlow(flow)}>
|
||||
<Button title="[r]eplay flow" icon="fa-repeat text-primary"
|
||||
onClick={() => replayFlow(flow)}>
|
||||
Replay
|
||||
</Button>
|
||||
<Button title="[D]uplicate flow" icon="fa-copy text-info"
|
||||
@@ -33,7 +35,8 @@ function FlowMenu({ flow, acceptFlow, replayFlow, duplicateFlow, removeFlow, rev
|
||||
icon="fa-history text-warning" onClick={() => revertFlow(flow)}>
|
||||
Revert
|
||||
</Button>
|
||||
<Button title="[d]elete flow" icon="fa-trash text-danger" onClick={() => removeFlow(flow)}>
|
||||
<Button title="[d]elete flow" icon="fa-trash text-danger"
|
||||
onClick={() => removeFlow(flow)}>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
@@ -51,17 +54,18 @@ function FlowMenu({ flow, acceptFlow, replayFlow, duplicateFlow, removeFlow, rev
|
||||
<div className="menu-group">
|
||||
<div className="menu-content">
|
||||
<Button disabled={!flow || !flow.intercepted} title="[a]ccept intercepted flow"
|
||||
icon="fa-play text-success" onClick={() => acceptFlow(flow)}
|
||||
>
|
||||
Resume
|
||||
</Button>
|
||||
|
||||
icon="fa-play text-success" onClick={() => resumeFlow(flow)}>
|
||||
Resume
|
||||
</Button>
|
||||
<Button disabled={!flow || !flow.intercepted} title="kill intercepted flow [x]"
|
||||
icon="fa-times text-danger" onClick={() => killFlow(flow)}>
|
||||
Abort
|
||||
</Button>
|
||||
</div>
|
||||
<div className="menu-legend">Interception</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -71,7 +75,8 @@ export default connect(
|
||||
flow: state.flows.byId[state.flows.selected[0]],
|
||||
}),
|
||||
{
|
||||
acceptFlow: flowsActions.accept,
|
||||
resumeFlow: flowsActions.resume,
|
||||
killFlow: flowsActions.kill,
|
||||
replayFlow: flowsActions.replay,
|
||||
duplicateFlow: flowsActions.duplicate,
|
||||
removeFlow: flowsActions.remove,
|
||||
|
||||
@@ -14,7 +14,7 @@ export function MenuToggle({ value, onChange, children }) {
|
||||
<div className="menu-entry">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
value={value}
|
||||
checked={value}
|
||||
onChange={onChange}/>
|
||||
{children}
|
||||
</label>
|
||||
|
||||
@@ -11,7 +11,7 @@ Button.propTypes = {
|
||||
export default function Button({ onClick, children, icon, disabled, className, title }) {
|
||||
return (
|
||||
<div className={classnames(className, 'btn btn-default')}
|
||||
onClick={onClick}
|
||||
onClick={!disabled && onClick}
|
||||
disabled={disabled}
|
||||
title={title}>
|
||||
{icon && (<i className={"fa fa-fw " + icon}/> )}
|
||||
|
||||
@@ -36,8 +36,29 @@ export default function reduce(state = defaultState, action) {
|
||||
makeFilter(state.filter),
|
||||
makeSort(state.sort)
|
||||
)
|
||||
|
||||
let selected = state.selected
|
||||
if(action.type === REMOVE && state.selected.includes(action.data)) {
|
||||
if(state.selected.length > 1){
|
||||
selected = selected.filter(x => x !== action.data)
|
||||
} else {
|
||||
selected = []
|
||||
if (action.data in state.viewIndex && state.view.length > 1) {
|
||||
let currentIndex = state.viewIndex[action.data],
|
||||
nextSelection
|
||||
if(currentIndex === state.view.length -1){ // last row
|
||||
nextSelection = state.view[currentIndex - 1]
|
||||
} else {
|
||||
nextSelection = state.view[currentIndex + 1]
|
||||
}
|
||||
selected.push(nextSelection.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
selected,
|
||||
...reduceStore(state, storeAction)
|
||||
}
|
||||
|
||||
@@ -48,6 +69,12 @@ export default function reduce(state = defaultState, action) {
|
||||
...reduceStore(state, storeActions.setFilter(makeFilter(action.filter), makeSort(state.sort)))
|
||||
}
|
||||
|
||||
case SET_HIGHLIGHT:
|
||||
return {
|
||||
...state,
|
||||
highlight: action.highlight
|
||||
}
|
||||
|
||||
case SET_SORT:
|
||||
return {
|
||||
...state,
|
||||
@@ -144,14 +171,23 @@ export function selectRelative(shift) {
|
||||
}
|
||||
|
||||
|
||||
export function accept(flow) {
|
||||
return dispatch => fetchApi(`/flows/${flow.id}/accept`, { method: 'POST' })
|
||||
export function resume(flow) {
|
||||
return dispatch => fetchApi(`/flows/${flow.id}/resume`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export function acceptAll() {
|
||||
return dispatch => fetchApi('/flows/accept', { method: 'POST' })
|
||||
export function resumeAll() {
|
||||
return dispatch => fetchApi('/flows/resume', { method: 'POST' })
|
||||
}
|
||||
|
||||
export function kill(flow) {
|
||||
return dispatch => fetchApi(`/flows/${flow.id}/kill`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export function killAll() {
|
||||
return dispatch => fetchApi('/flows/kill', { method: 'POST' })
|
||||
}
|
||||
|
||||
|
||||
export function remove(flow) {
|
||||
return dispatch => fetchApi(`/flows/${flow.id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as flowsActions from '../flows'
|
||||
import * as flowsActions from "../flows"
|
||||
|
||||
export const SET_ACTIVE_MENU = 'UI_SET_ACTIVE_MENU'
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function reducer(state = defaultState, action) {
|
||||
|
||||
case flowsActions.SELECT:
|
||||
// First Select
|
||||
if (action.flowIds.length && !state.isFlowSelected) {
|
||||
if (action.flowIds.length > 0 && !state.isFlowSelected) {
|
||||
return {
|
||||
...state,
|
||||
activeMenu: 'Flow',
|
||||
@@ -28,7 +28,7 @@ export default function reducer(state = defaultState, action) {
|
||||
}
|
||||
|
||||
// Deselect
|
||||
if (!action.flowIds.length && state.isFlowSelected) {
|
||||
if (action.flowIds.length === 0 && state.isFlowSelected) {
|
||||
let activeMenu = state.activeMenu
|
||||
if (activeMenu == 'Flow') {
|
||||
activeMenu = 'Start'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Key } from '../../utils'
|
||||
import { selectTab } from './flow'
|
||||
import * as flowsActions from '../flows'
|
||||
import { Key } from "../../utils"
|
||||
import { selectTab } from "./flow"
|
||||
import * as flowsActions from "../flows"
|
||||
|
||||
|
||||
export function onKeyDown(e) {
|
||||
@@ -9,7 +9,7 @@ export function onKeyDown(e) {
|
||||
return () => {
|
||||
}
|
||||
}
|
||||
var key = e.keyCode
|
||||
var key = e.keyCode
|
||||
var shiftKey = e.shiftKey
|
||||
e.preventDefault()
|
||||
return (dispatch, getState) => {
|
||||
@@ -48,9 +48,8 @@ export function onKeyDown(e) {
|
||||
dispatch(flowsActions.select(null))
|
||||
break
|
||||
|
||||
case Key.LEFT:
|
||||
{
|
||||
if(!flow) break
|
||||
case Key.LEFT: {
|
||||
if (!flow) break
|
||||
let tabs = ['request', 'response', 'error'].filter(k => flow[k]).concat(['details']),
|
||||
currentTab = getState().ui.flow.tab,
|
||||
nextTab = tabs[(tabs.indexOf(currentTab) - 1 + tabs.length) % tabs.length]
|
||||
@@ -59,9 +58,8 @@ export function onKeyDown(e) {
|
||||
}
|
||||
|
||||
case Key.TAB:
|
||||
case Key.RIGHT:
|
||||
{
|
||||
if(!flow) break
|
||||
case Key.RIGHT: {
|
||||
if (!flow) break
|
||||
let tabs = ['request', 'response', 'error'].filter(k => flow[k]).concat(['details']),
|
||||
currentTab = getState().ui.flow.tab,
|
||||
nextTab = tabs[(tabs.indexOf(currentTab) + 1) % tabs.length]
|
||||
@@ -69,14 +67,7 @@ export function onKeyDown(e) {
|
||||
break
|
||||
}
|
||||
|
||||
case Key.C:
|
||||
if (shiftKey) {
|
||||
dispatch(flowsActions.clear())
|
||||
}
|
||||
break
|
||||
|
||||
case Key.D:
|
||||
{
|
||||
case Key.D: {
|
||||
if (!flow) {
|
||||
return
|
||||
}
|
||||
@@ -88,32 +79,46 @@ export function onKeyDown(e) {
|
||||
break
|
||||
}
|
||||
|
||||
case Key.A:
|
||||
{
|
||||
case Key.A: {
|
||||
if (shiftKey) {
|
||||
dispatch(flowsActions.acceptAll())
|
||||
dispatch(flowsActions.resumeAll())
|
||||
} else if (flow && flow.intercepted) {
|
||||
dispatch(flowsActions.accept(flow))
|
||||
dispatch(flowsActions.resume(flow))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case Key.R:
|
||||
{
|
||||
case Key.R: {
|
||||
if (!shiftKey && flow) {
|
||||
dispatch(flowsActions.replay(flow))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case Key.V:
|
||||
{
|
||||
case Key.V: {
|
||||
if (!shiftKey && flow && flow.modified) {
|
||||
dispatch(flowsActions.revert(flow))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case Key.X: {
|
||||
if (shiftKey) {
|
||||
dispatch(flowsActions.killAll())
|
||||
} else if (flow && flow.intercepted) {
|
||||
dispatch(flowsActions.kill(flow))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case Key.Z: {
|
||||
if (!shiftKey) {
|
||||
dispatch(flowsActions.clear())
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ export default function reduce(state = defaultState, action) {
|
||||
if (!(action.id in byId)) {
|
||||
break
|
||||
}
|
||||
byId = {...byId}
|
||||
delete byId[action.id];
|
||||
({data: list, dataIndex: listIndex} = removeData(list, listIndex, action.id))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user