diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/src/css/flowtable.less | 5 | ||||
-rw-r--r-- | web/src/css/header.less | 1 | ||||
-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/Header.jsx | 4 | ||||
-rw-r--r-- | web/src/js/components/Header/FileMenu.jsx | 8 | ||||
-rw-r--r-- | web/src/js/components/Header/FlowMenu.jsx | 27 | ||||
-rw-r--r-- | web/src/js/components/Header/MenuToggle.jsx | 2 | ||||
-rw-r--r-- | web/src/js/components/common/Button.jsx | 2 | ||||
-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 |
13 files changed, 119 insertions, 54 deletions
diff --git a/web/src/css/flowtable.less b/web/src/css/flowtable.less index 1b560eba..e8d3d5af 100644 --- a/web/src/css/flowtable.less +++ b/web/src/css/flowtable.less @@ -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; } -}
\ No newline at end of file +} diff --git a/web/src/css/header.less b/web/src/css/header.less index a026d8aa..042d6811 100644 --- a/web/src/css/header.less +++ b/web/src/css/header.less @@ -47,6 +47,7 @@ header { .menu-entry { + text-align: left; height: (@menu-height - @menu-legend-height)/3; line-height: 1; padding: 0.5rem 1rem; 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/Header.jsx b/web/src/js/components/Header.jsx index 1500db1b..c15c951f 100644 --- a/web/src/js/components/Header.jsx +++ b/web/src/js/components/Header.jsx @@ -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> 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/FlowMenu.jsx b/web/src/js/components/Header/FlowMenu.jsx index 420cb054..a404fdb7 100644 --- a/web/src/js/components/Header/FlowMenu.jsx +++ b/web/src/js/components/Header/FlowMenu.jsx @@ -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, diff --git a/web/src/js/components/Header/MenuToggle.jsx b/web/src/js/components/Header/MenuToggle.jsx index 8977f3b9..91f093c6 100644 --- a/web/src/js/components/Header/MenuToggle.jsx +++ b/web/src/js/components/Header/MenuToggle.jsx @@ -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> diff --git a/web/src/js/components/common/Button.jsx b/web/src/js/components/common/Button.jsx index 69471f25..f05a68d0 100644 --- a/web/src/js/components/common/Button.jsx +++ b/web/src/js/components/common/Button.jsx @@ -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}/> )} 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)) |