From 01a5cceeac262a8d1dc754bbf6d360870ec81169 Mon Sep 17 00:00:00 2001 From: Matteo Luppi <100372313+lups2000@users.noreply.github.com> Date: Tue, 29 Apr 2025 12:45:19 +0200 Subject: [PATCH] [web] replay multiple selected flows (#7674) Co-authored-by: Maximilian Hils --- CHANGELOG.md | 2 +- web/src/js/__tests__/ducks/flowsSpec.tsx | 4 +++- web/src/js/components/FlowTable/FlowColumns.tsx | 2 +- web/src/js/components/Header/FlowMenu.tsx | 11 +++-------- web/src/js/ducks/flows.ts | 17 ++++++++++++++--- web/src/js/ducks/ui/keyboard.tsx | 4 +--- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f092ab8f6..d2a667afa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,7 @@ ([#7652](https://github.com/mitmproxy/mitmproxy/pull/7652), @lups2000) - `mitmproxy.dns.Message` has been renamed to `mitmproxy.dns.DNSMessage` ([#7670](https://github.com/mitmproxy/mitmproxy/pull/7670), @mhils) -- Added support for selecting multiple flows in mitmweb using Ctrl+Click and Shift+Click. Multi-selection is now supported for deleting, duplicating, marking, reverting, resuming, and aborting flows. +- Added support for selecting multiple flows in mitmweb using Ctrl+Click and Shift+Click. Multi-selection is now supported for deleting, duplicating, marking, reverting, replaying ,resuming, and aborting flows. ([#7319](https://github.com/mitmproxy/mitmproxy/pull/7319), @lups2000, @mhils) [tree-sitter]: https://tree-sitter.github.io/tree-sitter/ diff --git a/web/src/js/__tests__/ducks/flowsSpec.tsx b/web/src/js/__tests__/ducks/flowsSpec.tsx index ec421d1b7..8d6153e65 100644 --- a/web/src/js/__tests__/ducks/flowsSpec.tsx +++ b/web/src/js/__tests__/ducks/flowsSpec.tsx @@ -255,6 +255,8 @@ describe("flows actions", () => { const tflow = TFlow(); tflow.intercepted = true; tflow.modified = true; + // @ts-expect-error TFlow is Required<> for other tests. + tflow.websocket = undefined; const ttcpflow = TTCPFlow(); beforeEach(() => { @@ -316,7 +318,7 @@ describe("flows actions", () => { }); it("should handle replay action", () => { - store.dispatch(flowActions.replay(tflow)); + store.dispatch(flowActions.replay([tflow])); expect(fetchApi).toBeCalledWith( "/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/replay", { method: "POST" }, diff --git a/web/src/js/components/FlowTable/FlowColumns.tsx b/web/src/js/components/FlowTable/FlowColumns.tsx index b4becf7e2..8b0a2a566 100644 --- a/web/src/js/components/FlowTable/FlowColumns.tsx +++ b/web/src/js/components/FlowTable/FlowColumns.tsx @@ -172,7 +172,7 @@ export const quickactions: FlowColumn = ({ flow }) => { dispatch(flowActions.replay(flow))} + onClick={() => dispatch(flowActions.replay([flow]))} > diff --git a/web/src/js/components/Header/FlowMenu.tsx b/web/src/js/components/Header/FlowMenu.tsx index aa8c21d2d..b17d0ebf2 100644 --- a/web/src/js/components/Header/FlowMenu.tsx +++ b/web/src/js/components/Header/FlowMenu.tsx @@ -12,7 +12,7 @@ import { duplicate as duplicateFlows, kill as killFlows, remove as removeFlows, - replay as replayFlow, + replay as replayFlows, resume as resumeFlows, revert as revertFlows, mark as markFlows, @@ -29,9 +29,6 @@ export default function FlowMenu(): JSX.Element { const selectedFlows = useAppSelector((state) => state.flows.selected); const flow = selectedFlows[0]; - const hasSingleFlowSelected = useAppSelector( - (state) => state.flows.selected.length === 1, - ); const canResumeOrKillAny = selectedFlows.some(canResumeOrKill); if (selectedFlows.length === 0) return
; @@ -43,10 +40,8 @@ export default function FlowMenu(): JSX.Element { diff --git a/web/src/js/ducks/flows.ts b/web/src/js/ducks/flows.ts index 31fafa17a..4c661d9a2 100644 --- a/web/src/js/ducks/flows.ts +++ b/web/src/js/ducks/flows.ts @@ -3,7 +3,12 @@ import { fetchApi } from "../utils"; import * as store from "./utils/store"; import Filt from "../filt/filt"; import { Flow } from "../flow"; -import { canResumeOrKill, canRevert, sortFunctions } from "../flow/utils"; +import { + canReplay, + canResumeOrKill, + canRevert, + sortFunctions, +} from "../flow/utils"; import { AppDispatch, RootState } from "./store"; import { State } from "./utils/store"; @@ -255,8 +260,14 @@ export function duplicate(flows: Flow[]) { ); } -export function replay(flow: Flow) { - return () => fetchApi(`/flows/${flow.id}/replay`, { method: "POST" }); +export function replay(flows: Flow[]) { + flows = flows.filter(canReplay); + return () => + Promise.all( + flows.map((flow) => + fetchApi(`/flows/${flow.id}/replay`, { method: "POST" }), + ), + ); } export function revert(flows: Flow[]) { diff --git a/web/src/js/ducks/ui/keyboard.tsx b/web/src/js/ducks/ui/keyboard.tsx index 668fe6684..5a2422d20 100644 --- a/web/src/js/ducks/ui/keyboard.tsx +++ b/web/src/js/ducks/ui/keyboard.tsx @@ -107,9 +107,7 @@ export function onKeyDown(e: KeyboardEvent) { } case "r": { - if (selectedFlows.length === 1) { - dispatch(flowsActions.replay(flow)); - } + dispatch(flowsActions.replay(selectedFlows)); break; }