diff options
Diffstat (limited to 'web/src/js')
-rw-r--r-- | web/src/js/components/ContentView/ShowFullContentButton.jsx | 4 | ||||
-rw-r--r-- | web/src/js/components/FlowTable/FlowColumns.jsx | 12 | ||||
-rw-r--r-- | web/src/js/components/FlowView/ToggleEdit.jsx | 4 | ||||
-rw-r--r-- | web/src/js/components/Footer.jsx | 15 | ||||
-rw-r--r-- | web/src/js/components/Header.jsx | 11 | ||||
-rw-r--r-- | web/src/js/components/Header/FileMenu.jsx | 8 | ||||
-rw-r--r-- | web/src/js/components/Header/FilterDocs.jsx | 2 | ||||
-rw-r--r-- | web/src/js/components/Header/FlowMenu.jsx | 74 | ||||
-rw-r--r-- | web/src/js/components/Header/MainMenu.jsx | 21 | ||||
-rw-r--r-- | web/src/js/components/Header/MenuToggle.jsx | 69 | ||||
-rw-r--r-- | web/src/js/components/Header/OptionMenu.jsx | 118 | ||||
-rw-r--r-- | web/src/js/components/Header/ViewMenu.jsx | 32 | ||||
-rw-r--r-- | web/src/js/components/common/Button.jsx | 18 | ||||
-rw-r--r-- | web/src/js/components/common/DocsLink.jsx | 14 | ||||
-rw-r--r-- | web/src/js/ducks/flows.js | 44 | ||||
-rw-r--r-- | web/src/js/ducks/ui/header.js | 6 | ||||
-rw-r--r-- | web/src/js/ducks/ui/keyboard.js | 57 | ||||
-rw-r--r-- | web/src/js/ducks/utils/store.js | 1 |
18 files changed, 329 insertions, 181 deletions
diff --git a/web/src/js/components/ContentView/ShowFullContentButton.jsx b/web/src/js/components/ContentView/ShowFullContentButton.jsx index cfd96dd8..fd68991e 100644 --- a/web/src/js/components/ContentView/ShowFullContentButton.jsx +++ b/web/src/js/components/ContentView/ShowFullContentButton.jsx @@ -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> ) diff --git a/web/src/js/components/FlowTable/FlowColumns.jsx b/web/src/js/components/FlowTable/FlowColumns.jsx index 0ff80453..02a4fba1 100644 --- a/web/src/js/components/FlowTable/FlowColumns.jsx +++ b/web/src/js/components/FlowTable/FlowColumns.jsx @@ -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)) ) : ( '...' )} diff --git a/web/src/js/components/FlowView/ToggleEdit.jsx b/web/src/js/components/FlowView/ToggleEdit.jsx index 9016348e..6a691a3d 100644 --- a/web/src/js/components/FlowView/ToggleEdit.jsx +++ b/web/src/js/components/FlowView/ToggleEdit.jsx @@ -14,11 +14,11 @@ function ToggleEdit({ isEdit, startEdit, stopEdit, flow, modifiedFlow }) { return ( <div className="edit-flow-container"> {isEdit ? - <a className="edit-flow" onClick={() => stopEdit(flow, modifiedFlow)}> + <a className="edit-flow" title="Finish Edit" onClick={() => stopEdit(flow, modifiedFlow)}> <i className="fa fa-check"/> </a> : - <a className="edit-flow" onClick={() => startEdit(flow)}> + <a className="edit-flow" title="Edit Flow" onClick={() => startEdit(flow)}> <i className="fa fa-pencil"/> </a> } diff --git a/web/src/js/components/Footer.jsx b/web/src/js/components/Footer.jsx index 1ae4ee73..ee26dad2 100644 --- a/web/src/js/components/Footer.jsx +++ b/web/src/js/components/Footer.jsx @@ -7,7 +7,8 @@ Footer.propTypes = { } function Footer({ settings }) { - let {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickyauth, stickycookie, stream_large_bodies} = settings; + let {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, websocket, anticache, anticomp, + stickyauth, stickycookie, stream_large_bodies, listen_host, listen_port} = settings; return ( <footer> {mode && mode != "regular" && ( @@ -25,8 +26,11 @@ function Footer({ settings }) { {rawtcp && ( <span className="label label-success">raw-tcp</span> )} - {!http2 && ( - <span className="label label-success">no-http2</span> + {http2 && ( + <span className="label label-success">http2</span> + )} + {!websocket && ( + <span className="label label-success">no-websocket</span> )} {anticache && ( <span className="label label-success">anticache</span> @@ -43,6 +47,11 @@ function Footer({ settings }) { {stream_large_bodies && ( <span className="label label-success">stream: {formatSize(stream_large_bodies)}</span> )} + <div className="pull-right"> + <span className="label label-primary" title="HTTP Proxy Server Address"> + [{listen_host || "*"}:{listen_port}] + </span> + </div> </footer> ) } diff --git a/web/src/js/components/Header.jsx b/web/src/js/components/Header.jsx index 702786e6..c15c951f 100644 --- a/web/src/js/components/Header.jsx +++ b/web/src/js/components/Header.jsx @@ -2,14 +2,13 @@ import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' import classnames from 'classnames' import MainMenu from './Header/MainMenu' -import ViewMenu from './Header/ViewMenu' import OptionMenu from './Header/OptionMenu' import FileMenu from './Header/FileMenu' import FlowMenu from './Header/FlowMenu' import {setActiveMenu} from '../ducks/ui/header' class Header extends Component { - static entries = [MainMenu, ViewMenu, OptionMenu] + static entries = [MainMenu, OptionMenu] handleClick(active, e) { e.preventDefault() @@ -23,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> @@ -38,9 +39,9 @@ class Header extends Component { </a> ))} </nav> - <div className="menu"> + <menu> <Active/> - </div> + </menu> </header> ) } diff --git a/web/src/js/components/Header/FileMenu.jsx b/web/src/js/components/Header/FileMenu.jsx index 53c63ea1..ec32c857 100644 --- a/web/src/js/components/Header/FileMenu.jsx +++ b/web/src/js/components/Header/FileMenu.jsx @@ -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> ) diff --git a/web/src/js/components/Header/FilterDocs.jsx b/web/src/js/components/Header/FilterDocs.jsx index c237e230..49fb5fbc 100644 --- a/web/src/js/components/Header/FilterDocs.jsx +++ b/web/src/js/components/Header/FilterDocs.jsx @@ -47,7 +47,7 @@ export default class FilterDocs extends Component { <a href="http://docs.mitmproxy.org/en/stable/features/filters.html" target="_blank"> <i className="fa fa-external-link"></i> -   mitmproxy docs</a> + mitmproxy docs</a> </td> </tr> </tbody> diff --git a/web/src/js/components/Header/FlowMenu.jsx b/web/src/js/components/Header/FlowMenu.jsx index e78a49aa..a404fdb7 100644 --- a/web/src/js/components/Header/FlowMenu.jsx +++ b/web/src/js/components/Header/FlowMenu.jsx @@ -1,32 +1,71 @@ -import React, { PropTypes } from 'react' -import { connect } from 'react-redux' -import Button from '../common/Button' -import { MessageUtils } from '../../flow/utils.js' -import * as flowsActions from '../../ducks/flows' +import React, { PropTypes } from "react" +import { connect } from "react-redux" +import Button from "../common/Button" +import { MessageUtils } from "../../flow/utils.js" +import * as flowsActions from "../../ducks/flows" FlowMenu.title = 'Flow' FlowMenu.propTypes = { - flow: PropTypes.object.isRequired, - acceptFlow: PropTypes.func.isRequired, + flow: PropTypes.object, + 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-row"> - <Button disabled={!flow || !flow.intercepted} title="[a]ccept intercepted flow" text="Accept" icon="fa-play" onClick={() => acceptFlow(flow)} /> - <Button title="[r]eplay flow" text="Replay" icon="fa-repeat" onClick={() => replayFlow(flow)} /> - <Button title="[D]uplicate flow" text="Duplicate" icon="fa-copy" onClick={() => duplicateFlow(flow)} /> - <Button title="[d]elete flow" text="Delete" icon="fa-trash" onClick={() => removeFlow(flow)}/> - <Button disabled={!flow || !flow.modified} title="revert changes to flow [V]" text="Revert" icon="fa-history" onClick={() => revertFlow(flow)} /> - <Button title="download" text="Download" icon="fa-download" onClick={() => window.location = MessageUtils.getContentURL(flow, flow.response)}/> + <div className="menu-group"> + <div className="menu-content"> + <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" + onClick={() => duplicateFlow(flow)}> + Duplicate + </Button> + <Button disabled={!flow || !flow.modified} title="revert changes to flow [V]" + icon="fa-history text-warning" onClick={() => revertFlow(flow)}> + Revert + </Button> + <Button title="[d]elete flow" icon="fa-trash text-danger" + onClick={() => removeFlow(flow)}> + Delete + </Button> + </div> + <div className="menu-legend">Flow Modification</div> </div> - <div className="clearfix"/> + <div className="menu-group"> + <div className="menu-content"> + <Button title="download" icon="fa-download" + onClick={() => window.location = MessageUtils.getContentURL(flow, flow.response)}> + Download + </Button> + </div> + <div className="menu-legend">Export</div> + </div> + <div className="menu-group"> + <div className="menu-content"> + <Button disabled={!flow || !flow.intercepted} title="[a]ccept intercepted flow" + 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> ) } @@ -36,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, diff --git a/web/src/js/components/Header/MainMenu.jsx b/web/src/js/components/Header/MainMenu.jsx index 5ab3fa9d..6a4e12bf 100644 --- a/web/src/js/components/Header/MainMenu.jsx +++ b/web/src/js/components/Header/MainMenu.jsx @@ -1,20 +1,17 @@ -import React, { Component, PropTypes } from 'react' -import { connect } from 'react-redux' -import FilterInput from './FilterInput' -import { update as updateSettings } from '../../ducks/settings' -import { setFilter, setHighlight } from '../../ducks/flows' +import React, { Component, PropTypes } from "react" +import { connect } from "react-redux" +import FilterInput from "./FilterInput" +import { update as updateSettings } from "../../ducks/settings" +import { setFilter, setHighlight } from "../../ducks/flows" MainMenu.title = "Start" export default function MainMenu() { return ( - <div> - <div className="menu-row"> - <FlowFilterInput/> - <HighlightInput/> - <InterceptInput/> - </div> - <div className="clearfix"></div> + <div className="menu-main"> + <FlowFilterInput/> + <HighlightInput/> + <InterceptInput/> </div> ) } diff --git a/web/src/js/components/Header/MenuToggle.jsx b/web/src/js/components/Header/MenuToggle.jsx new file mode 100644 index 00000000..91f093c6 --- /dev/null +++ b/web/src/js/components/Header/MenuToggle.jsx @@ -0,0 +1,69 @@ +import { PropTypes } from "react" +import { connect } from "react-redux" +import { update as updateSettings } from "../../ducks/settings" +import { toggleVisibility } from "../../ducks/eventLog" + +MenuToggle.propTypes = { + value: PropTypes.bool.isRequired, + onChange: PropTypes.func.isRequired, + children: PropTypes.node.isRequired, +} + +export function MenuToggle({ value, onChange, children }) { + return ( + <div className="menu-entry"> + <label> + <input type="checkbox" + checked={value} + onChange={onChange}/> + {children} + </label> + </div> + ) +} + + +SettingsToggle.propTypes = { + setting: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, +} + +export function SettingsToggle({ setting, children, settings, updateSettings }) { + return ( + <MenuToggle + value={settings[setting] || false} // we don't have settings initially, so just pass false. + onChange={() => updateSettings({ [setting]: !settings[setting] })} + > + {children} + </MenuToggle> + ) +} +SettingsToggle = connect( + state => ({ + settings: state.settings, + }), + { + updateSettings, + } +)(SettingsToggle) + + +export function EventlogToggle({ toggleVisibility, eventLogVisible }) { + return ( + <MenuToggle + value={eventLogVisible} + onChange={toggleVisibility} + > + Display Event Log + </MenuToggle> + ) +} +EventlogToggle = connect( + state => ({ + eventLogVisible: state.eventLog.visible, + }), + { + toggleVisibility, + } +)(EventlogToggle) + diff --git a/web/src/js/components/Header/OptionMenu.jsx b/web/src/js/components/Header/OptionMenu.jsx index 186a9c6a..d6a8dfc2 100644 --- a/web/src/js/components/Header/OptionMenu.jsx +++ b/web/src/js/components/Header/OptionMenu.jsx @@ -1,71 +1,65 @@ -import React, { PropTypes } from 'react' -import { connect } from 'react-redux' -import ToggleButton from '../common/ToggleButton' -import ToggleInputButton from '../common/ToggleInputButton' -import { update as updateSettings } from '../../ducks/settings' +import React, { PropTypes } from "react" +import { connect } from "react-redux" +import { SettingsToggle, EventlogToggle } from "./MenuToggle" +import DocsLink from "../common/DocsLink" OptionMenu.title = 'Options' -OptionMenu.propTypes = { - settings: PropTypes.object.isRequired, - updateSettings: PropTypes.func.isRequired, -} - -function OptionMenu({ settings, updateSettings }) { +export default function OptionMenu() { return ( <div> - <div className="menu-row"> - <ToggleButton text="showhost" - checked={settings.showhost} - onToggle={() => updateSettings({ showhost: !settings.showhost })} - /> - <ToggleButton text="no_upstream_cert" - checked={settings.no_upstream_cert} - onToggle={() => updateSettings({ no_upstream_cert: !settings.no_upstream_cert })} - /> - <ToggleButton text="rawtcp" - checked={settings.rawtcp} - onToggle={() => updateSettings({ rawtcp: !settings.rawtcp })} - /> - <ToggleButton text="http2" - checked={settings.http2} - onToggle={() => updateSettings({ http2: !settings.http2 })} - /> - <ToggleButton text="anticache" - checked={settings.anticache} - onToggle={() => updateSettings({ anticache: !settings.anticache })} - /> - <ToggleButton text="anticomp" - checked={settings.anticomp} - onToggle={() => updateSettings({ anticomp: !settings.anticomp })} - /> - <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter" - checked={!!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} - onToggleChanged={txt => updateSettings({ stickycookie: !settings.stickycookie ? txt : null })} - /> - <ToggleInputButton name="stream_large_bodies" placeholder="stream..." - checked={!!settings.stream_large_bodies} - txt={settings.stream_large_bodies} - inputType="number" - onToggleChanged={txt => updateSettings({ stream_large_bodies: !settings.stream_large_bodies ? txt : null })} - /> + <div className="menu-group"> + <div className="menu-content"> + <SettingsToggle setting="http2">HTTP/2.0</SettingsToggle> + <SettingsToggle setting="websocket">WebSockets</SettingsToggle> + <SettingsToggle setting="rawtcp">Raw TCP</SettingsToggle> + </div> + <div className="menu-legend">Protocol Support</div> + </div> + <div className="menu-group"> + <div className="menu-content"> + <SettingsToggle setting="anticache"> + Disable Caching <DocsLink resource="features/anticache.html"/> + </SettingsToggle> + <SettingsToggle setting="anticomp"> + Disable Compression <i className="fa fa-question-circle" + title="Do not forward Accept-Encoding headers to the server to force an uncompressed response."></i> + </SettingsToggle> + </div> + <div className="menu-legend">HTTP Options</div> </div> - <div className="clearfix"/> + <div className="menu-group"> + <div className="menu-content"> + <SettingsToggle setting="showhost"> + Use Host Header <i className="fa fa-question-circle" + title="Use the Host header to construct URLs for display."></i> + </SettingsToggle> + <EventlogToggle/> + </div> + <div className="menu-legend">View Options</div> + </div> + { /* + <ToggleButton text="no_upstream_cert" + checked={settings.no_upstream_cert} + onToggle={() => updateSettings({ no_upstream_cert: !settings.no_upstream_cert })} + /> + <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter" + checked={!!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} + onToggleChanged={txt => updateSettings({ stickycookie: !settings.stickycookie ? txt : null })} + /> + <ToggleInputButton name="stream_large_bodies" placeholder="stream..." + checked={!!settings.stream_large_bodies} + txt={settings.stream_large_bodies} + inputType="number" + onToggleChanged={txt => updateSettings({ stream_large_bodies: !settings.stream_large_bodies ? txt : null })} + /> + */} </div> ) } - -export default connect( - state => ({ - settings: state.settings, - }), - { - updateSettings, - } -)(OptionMenu) diff --git a/web/src/js/components/Header/ViewMenu.jsx b/web/src/js/components/Header/ViewMenu.jsx deleted file mode 100644 index 22b370d1..00000000 --- a/web/src/js/components/Header/ViewMenu.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { PropTypes } from 'react' -import { connect } from 'react-redux' -import ToggleButton from '../common/ToggleButton' -import { toggleVisibility } from '../../ducks/eventLog' - -ViewMenu.title = 'View' -ViewMenu.route = 'flows' - -ViewMenu.propTypes = { - eventLogVisible: PropTypes.bool.isRequired, - toggleEventLog: PropTypes.func.isRequired, -} - -function ViewMenu({ eventLogVisible, toggleEventLog }) { - return ( - <div> - <div className="menu-row"> - <ToggleButton text="Show Event Log" checked={eventLogVisible} onToggle={toggleEventLog} /> - </div> - <div className="clearfix"></div> - </div> - ) -} - -export default connect( - state => ({ - eventLogVisible: state.eventLog.visible, - }), - { - toggleEventLog: toggleVisibility, - } -)(ViewMenu) diff --git a/web/src/js/components/common/Button.jsx b/web/src/js/components/common/Button.jsx index bfbb455d..f05a68d0 100644 --- a/web/src/js/components/common/Button.jsx +++ b/web/src/js/components/common/Button.jsx @@ -1,19 +1,21 @@ -import React, { PropTypes } from 'react' -import classnames from 'classnames' +import React, { PropTypes } from "react" +import classnames from "classnames" Button.propTypes = { onClick: PropTypes.func.isRequired, - text: PropTypes.string, - icon: PropTypes.string + children: PropTypes.node.isRequired, + icon: PropTypes.string, + title: PropTypes.string, } -export default function Button({ onClick, text, icon, disabled, className }) { +export default function Button({ onClick, children, icon, disabled, className, title }) { return ( <div className={classnames(className, 'btn btn-default')} - onClick={onClick} - disabled={disabled}> + onClick={!disabled && onClick} + disabled={disabled} + title={title}> {icon && (<i className={"fa fa-fw " + icon}/> )} - {text && text} + {children} </div> ) } diff --git a/web/src/js/components/common/DocsLink.jsx b/web/src/js/components/common/DocsLink.jsx new file mode 100644 index 00000000..182811a3 --- /dev/null +++ b/web/src/js/components/common/DocsLink.jsx @@ -0,0 +1,14 @@ +import { PropTypes } from 'react' + +DocsLink.propTypes = { + resource: PropTypes.string.isRequired, +} + +export default function DocsLink({ children, resource }) { + let url = `http://docs.mitmproxy.org/en/stable/${resource}` + return ( + <a target="_blank" href={url}> + {children || <i className="fa fa-question-circle"></i>} + </a> + ) +} diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js index d3717533..92408891 100644 --- a/web/src/js/ducks/flows.js +++ b/web/src/js/ducks/flows.js @@ -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 resumeAll() { + return dispatch => fetchApi('/flows/resume', { method: 'POST' }) +} + +export function kill(flow) { + return dispatch => fetchApi(`/flows/${flow.id}/kill`, { method: 'POST' }) } -export function acceptAll() { - return dispatch => fetchApi('/flows/accept', { method: 'POST' }) +export function killAll() { + return dispatch => fetchApi('/flows/kill', { method: 'POST' }) } + export function remove(flow) { return dispatch => fetchApi(`/flows/${flow.id}`, { method: 'DELETE' }) } diff --git a/web/src/js/ducks/ui/header.js b/web/src/js/ducks/ui/header.js index 25dfe602..6581149e 100644 --- a/web/src/js/ducks/ui/header.js +++ b/web/src/js/ducks/ui/header.js @@ -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' diff --git a/web/src/js/ducks/ui/keyboard.js b/web/src/js/ducks/ui/keyboard.js index 7418eca9..30fd76e1 100644 --- a/web/src/js/ducks/ui/keyboard.js +++ b/web/src/js/ducks/ui/keyboard.js @@ -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 } diff --git a/web/src/js/ducks/utils/store.js b/web/src/js/ducks/utils/store.js index 9ea4f02e..ac272650 100644 --- a/web/src/js/ducks/utils/store.js +++ b/web/src/js/ducks/utils/store.js @@ -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)) |