From 0d2c4501c20222122cf971287ac14dc93e3e013c Mon Sep 17 00:00:00 2001 From: Cita Date: Mon, 30 Mar 2026 16:32:26 +0800 Subject: [PATCH] make proxy trace panel collapsible (#327) --- .../pages/ProxyLogs.server-driven.test.tsx | 68 +++++ src/web/pages/ProxyLogs.tsx | 287 ++++++++++-------- 2 files changed, 229 insertions(+), 126 deletions(-) diff --git a/src/web/pages/ProxyLogs.server-driven.test.tsx b/src/web/pages/ProxyLogs.server-driven.test.tsx index 7b66ccd..1e32da6 100644 --- a/src/web/pages/ProxyLogs.server-driven.test.tsx +++ b/src/web/pages/ProxyLogs.server-driven.test.tsx @@ -390,6 +390,74 @@ describe('ProxyLogs server-driven page', () => { } }); + it('allows collapsing and expanding the debug trace panel to reduce page footprint', async () => { + let root!: WebTestRenderer; + + try { + await act(async () => { + root = create( + + + + + , + ); + }); + await flushMicrotasks(); + + const toggleButton = root.root.find((node) => ( + node.type === 'button' + && typeof node.props.onClick === 'function' + && node.props['data-debug-trace-panel-toggle'] === true + )); + const panelBody = root.root.find((node) => ( + node.type === 'div' + && node.props['data-debug-trace-panel-body'] === true + )); + + expect(toggleButton.props['aria-expanded']).toBe(true); + expect(String(panelBody.props.className || '')).toContain('is-open'); + + await act(async () => { + toggleButton.props.onClick(); + }); + await flushMicrotasks(); + + const collapsedToggleButton = root.root.find((node) => ( + node.type === 'button' + && typeof node.props.onClick === 'function' + && node.props['data-debug-trace-panel-toggle'] === true + )); + const collapsedPanelBody = root.root.find((node) => ( + node.type === 'div' + && node.props['data-debug-trace-panel-body'] === true + )); + + expect(collapsedToggleButton.props['aria-expanded']).toBe(false); + expect(String(collapsedPanelBody.props.className || '')).not.toContain('is-open'); + + await act(async () => { + collapsedToggleButton.props.onClick(); + }); + await flushMicrotasks(); + + const expandedToggleButton = root.root.find((node) => ( + node.type === 'button' + && typeof node.props.onClick === 'function' + && node.props['data-debug-trace-panel-toggle'] === true + )); + const expandedPanelBody = root.root.find((node) => ( + node.type === 'div' + && node.props['data-debug-trace-panel-body'] === true + )); + + expect(expandedToggleButton.props['aria-expanded']).toBe(true); + expect(String(expandedPanelBody.props.className || '')).toContain('is-open'); + } finally { + root?.unmount(); + } + }); + it('opens debug trace detail on demand instead of preloading the first trace inline', async () => { let root!: WebTestRenderer; diff --git a/src/web/pages/ProxyLogs.tsx b/src/web/pages/ProxyLogs.tsx index c3fa534..0355d0f 100644 --- a/src/web/pages/ProxyLogs.tsx +++ b/src/web/pages/ProxyLogs.tsx @@ -622,6 +622,7 @@ export default function ProxyLogs() { const [showDebugSettingsModal, setShowDebugSettingsModal] = useState(false); const [debugPanelLoading, setDebugPanelLoading] = useState(false); const [debugPanelSaving, setDebugPanelSaving] = useState(false); + const [debugTracePanelExpanded, setDebugTracePanelExpanded] = useState(true); const [debugSettings, setDebugSettings] = useState(DEFAULT_PROXY_DEBUG_SETTINGS); const [debugDraftSettings, setDebugDraftSettings] = useState(DEFAULT_PROXY_DEBUG_SETTINGS); const [debugTraces, setDebugTraces] = useState([]); @@ -994,6 +995,9 @@ export default function ProxyLogs() { const updated = await api.updateRuntimeSettings(buildProxyDebugSettingsPayload(nextSettings)); const normalized = normalizeProxyDebugSettings(updated); applyLoadedDebugSettings(normalized, { syncDraft: true }); + if (normalized.proxyDebugTraceEnabled) { + setDebugTracePanelExpanded(true); + } if (options?.closeAfterSave) { setShowDebugSettingsModal(false); } @@ -1542,6 +1546,29 @@ export default function ProxyLogs() {
+
-
-
-
-
最近调试追踪
-
- 最多抓最近 20 条,列表分页每页 5 条;打开详情后各段内容可按需展开和收起。 +
+
+
+
+
+
最近调试追踪
+
+ 最多抓最近 20 条,列表分页每页 5 条;打开详情后各段内容可按需展开和收起。 +
+
+
+ {debugSettings.proxyDebugTraceEnabled ? '开启中,结果会自动刷新' : '尚未开启'} +
-
-
- {debugSettings.proxyDebugTraceEnabled ? '开启中,结果会自动刷新' : '尚未开启'} + + {debugPanelLoading && debugTraces.length === 0 ? ( +
加载调试追踪中...
+ ) : debugTraces.length === 0 ? ( +
+ {debugSettings.proxyDebugTraceEnabled + ? '暂时还没有新追踪。这里只显示开启后产生的新请求,等下一次代理请求进入就会出现在这里。' + : '调试追踪尚未开启。点击上方“开启调试”或“调试设置”后,新的代理请求会出现在这里。'} +
+ ) : isMobile ? ( +
+ {visibleDebugTraces.map((trace) => ( + openDebugTraceDetailModal(trace.id)} + > + 查看详情 + + )} + > + + + + + + ))} +
+ ) : ( + + + + + + + + + + + + + + + {visibleDebugTraces.map((trace) => ( + + + + + + + + + + + ))} + +
时间Session模型下游路径上游路径客户端{tr('状态')}操作
+ {formatDateTimeLocal(trace.createdAt)} + {trace.sessionId || `trace-${trace.id}`}{trace.requestedModel || '-'}{trace.downstreamPath || '-'}{trace.finalUpstreamPath || '-'}{trace.clientKind || '-'}{renderTraceStatusBadge(trace)} + +
+ )} + {debugTraces.length > 0 ? ( +
+
+ 显示第 {debugTraceDisplayedStart} - {debugTraceDisplayedEnd} 条,共 {debugTraces.length} 条 +
+ + {Array.from({ length: debugTraceTotalPages }, (_, index) => index + 1).map((num) => ( + + ))} + +
+ ) : null}
- - {debugPanelLoading && debugTraces.length === 0 ? ( -
加载调试追踪中...
- ) : debugTraces.length === 0 ? ( -
- {debugSettings.proxyDebugTraceEnabled - ? '暂时还没有新追踪。这里只显示开启后产生的新请求,等下一次代理请求进入就会出现在这里。' - : '调试追踪尚未开启。点击上方“开启调试”或“调试设置”后,新的代理请求会出现在这里。'} -
- ) : isMobile ? ( -
- {visibleDebugTraces.map((trace) => ( - openDebugTraceDetailModal(trace.id)} - > - 查看详情 - - )} - > - - - - - - ))} -
- ) : ( - - - - - - - - - - - - - - - {visibleDebugTraces.map((trace) => ( - - - - - - - - - - - ))} - -
时间Session模型下游路径上游路径客户端{tr('状态')}操作
- {formatDateTimeLocal(trace.createdAt)} - {trace.sessionId || `trace-${trace.id}`}{trace.requestedModel || '-'}{trace.downstreamPath || '-'}{trace.finalUpstreamPath || '-'}{trace.clientKind || '-'}{renderTraceStatusBadge(trace)} - -
- )} - {debugTraces.length > 0 ? ( -
-
- 显示第 {debugTraceDisplayedStart} - {debugTraceDisplayedEnd} 条,共 {debugTraces.length} 条 -
- - {Array.from({ length: debugTraceTotalPages }, (_, index) => index + 1).map((num) => ( - - ))} - -
- ) : null}
{isMobile ? (