mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 05:12:11 +03:00
feat(traffic-inspector): live (in-flight) request filter (Gap 5) (#4130)
Integrated into release/v3.8.29
This commit is contained in:
committed by
GitHub
parent
91183dc27e
commit
1faf72bbda
@@ -19,8 +19,16 @@ export function TrafficInspectorPageClient() {
|
||||
const [containerHeight, setContainerHeight] = useState(600);
|
||||
const listContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [selectedRequest, setSelectedRequest] = useState<InterceptedRequest | null>(null);
|
||||
const { filters, setProfile, setHost, setAgent, setStatus, setSessionId, setSameContext } =
|
||||
useTrafficFilters();
|
||||
const {
|
||||
filters,
|
||||
setProfile,
|
||||
setHost,
|
||||
setAgent,
|
||||
setStatus,
|
||||
setSessionId,
|
||||
setSameContext,
|
||||
toggleLive,
|
||||
} = useTrafficFilters();
|
||||
const [{ listWidth, collapsed }, { startDrag, toggleCollapse }] = useResizablePanels();
|
||||
const [streamState, streamActions] = useTrafficStream(filters);
|
||||
const recorder = useSessionRecorder();
|
||||
@@ -108,6 +116,8 @@ export function TrafficInspectorPageClient() {
|
||||
onHostChange={setHost}
|
||||
onAgentChange={setAgent}
|
||||
onStatusChange={setStatus}
|
||||
liveOnly={filters.liveOnly ?? false}
|
||||
onToggleLive={toggleLive}
|
||||
paused={streamState.paused}
|
||||
onPause={streamActions.pause}
|
||||
onResume={streamActions.resume}
|
||||
|
||||
@@ -19,6 +19,8 @@ interface TopBarControlsProps {
|
||||
onHostChange: (h: string | undefined) => void;
|
||||
onAgentChange: (a: AgentId | undefined) => void;
|
||||
onStatusChange: (s: ListFilters["status"]) => void;
|
||||
liveOnly: boolean;
|
||||
onToggleLive: () => void;
|
||||
paused: boolean;
|
||||
onPause: () => void;
|
||||
onResume: () => void;
|
||||
@@ -46,6 +48,8 @@ export function TopBarControls({
|
||||
onHostChange,
|
||||
onAgentChange,
|
||||
onStatusChange,
|
||||
liveOnly,
|
||||
onToggleLive,
|
||||
paused,
|
||||
onPause,
|
||||
onResume,
|
||||
@@ -125,6 +129,25 @@ export function TopBarControls({
|
||||
<option value="error">error</option>
|
||||
</select>
|
||||
|
||||
{/* Live (in-flight) toggle — Gap 5 */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggleLive}
|
||||
aria-pressed={liveOnly}
|
||||
title={t("liveOnly")}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded border px-2 py-1 text-xs focus-ring",
|
||||
liveOnly
|
||||
? "border-green-500/50 bg-green-500/15 text-green-500"
|
||||
: "border-border text-text-muted hover:text-text-main"
|
||||
)}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
|
||||
sensors
|
||||
</span>
|
||||
{t("liveOnly")}
|
||||
</button>
|
||||
|
||||
{/* Action buttons */}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { ListFilters } from "@/mitm/inspector/types";
|
||||
|
||||
export interface FiltersState extends ListFilters {
|
||||
sameContextKey?: string;
|
||||
/** Show only in-flight (open) requests — Gap 5 "Live" filter. */
|
||||
liveOnly?: boolean;
|
||||
}
|
||||
|
||||
export function useTrafficFilters() {
|
||||
@@ -38,6 +40,10 @@ export function useTrafficFilters() {
|
||||
setFilters((prev) => ({ ...prev, sameContextKey: contextKey }));
|
||||
}, []);
|
||||
|
||||
const toggleLive = useCallback(() => {
|
||||
setFilters((prev) => ({ ...prev, liveOnly: !prev.liveOnly }));
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setFilters({ profile: "llm" });
|
||||
}, []);
|
||||
@@ -51,6 +57,7 @@ export function useTrafficFilters() {
|
||||
setSource,
|
||||
setSessionId,
|
||||
setSameContext,
|
||||
toggleLive,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { InterceptedRequest, ListFilters, WsEvent } from "@/mitm/inspector/types";
|
||||
import { matchesTrafficFilter } from "@/lib/inspector/matchesTrafficFilter";
|
||||
import type { FiltersState } from "./useTrafficFilters";
|
||||
|
||||
const WS_PATH = "/api/tools/traffic-inspector/ws";
|
||||
@@ -47,24 +48,7 @@ export function useTrafficStream(
|
||||
});
|
||||
|
||||
const applyFilter = useCallback((req: InterceptedRequest): boolean => {
|
||||
const f = filtersRef.current as FiltersState;
|
||||
if (f.profile === "llm" && req.detectedKind !== "llm") return false;
|
||||
if (f.profile === "custom" && req.source !== "custom-host") return false;
|
||||
if (f.host && !req.host.includes(f.host)) return false;
|
||||
if (f.agent && req.agent !== f.agent) return false;
|
||||
if (f.source && req.source !== f.source) return false;
|
||||
if (f.sessionId && req.sessionId !== f.sessionId) return false;
|
||||
if (f.sameContextKey && req.contextKey !== f.sameContextKey) return false;
|
||||
if (f.status) {
|
||||
const s = req.status;
|
||||
if (typeof s === "number") {
|
||||
const cat = `${Math.floor(s / 100)}xx`;
|
||||
if (cat !== f.status) return false;
|
||||
} else if (f.status === "error" && s !== "error") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return matchesTrafficFilter(req, filtersRef.current as FiltersState);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -8578,6 +8578,7 @@
|
||||
"httpProxyTitle": "HTTP Proxy Snippet — port {port}",
|
||||
"notRecording": "Not recording",
|
||||
"anyStatus": "Any status",
|
||||
"liveOnly": "Live",
|
||||
"viewingRecordedSession": "Viewing recorded session",
|
||||
"backToLive": "Back to live",
|
||||
"untitledSession": "Untitled session",
|
||||
|
||||
37
src/lib/inspector/matchesTrafficFilter.ts
Normal file
37
src/lib/inspector/matchesTrafficFilter.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Pure request-list filter for the Traffic Inspector.
|
||||
*
|
||||
* Extracted from the inline `applyFilter` closure inside the useTrafficStream
|
||||
* hook so the filtering rules are unit-testable. Behaviour is identical to the
|
||||
* previous inline logic, plus the new `liveOnly` toggle (Gap 5) that keeps only
|
||||
* in-flight requests — letting the user watch open connections in real time.
|
||||
*/
|
||||
import type { InterceptedRequest, ListFilters } from "@/mitm/inspector/types";
|
||||
|
||||
export type TrafficFilters = ListFilters & {
|
||||
/** Client-only: restrict the list to the same system-prompt context. */
|
||||
sameContextKey?: string;
|
||||
/** Client-only: show only in-flight (open) requests (Gap 5). */
|
||||
liveOnly?: boolean;
|
||||
};
|
||||
|
||||
export function matchesTrafficFilter(req: InterceptedRequest, f: TrafficFilters): boolean {
|
||||
if (f.profile === "llm" && req.detectedKind !== "llm") return false;
|
||||
if (f.profile === "custom" && req.source !== "custom-host") return false;
|
||||
if (f.host && !req.host.includes(f.host)) return false;
|
||||
if (f.agent && req.agent !== f.agent) return false;
|
||||
if (f.source && req.source !== f.source) return false;
|
||||
if (f.sessionId && req.sessionId !== f.sessionId) return false;
|
||||
if (f.sameContextKey && req.contextKey !== f.sameContextKey) return false;
|
||||
if (f.liveOnly && req.status !== "in-flight") return false;
|
||||
if (f.status) {
|
||||
const s = req.status;
|
||||
if (typeof s === "number") {
|
||||
const cat = `${Math.floor(s / 100)}xx`;
|
||||
if (cat !== f.status) return false;
|
||||
} else if (f.status === "error" && s !== "error") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user