persist proxy trace panel state (#331)

This commit is contained in:
Cita
2026-03-30 19:21:57 +08:00
committed by GitHub
parent 9aeec87cfa
commit 6726da987f
2 changed files with 102 additions and 1 deletions
@@ -97,6 +97,7 @@ function buildListResponse(overrides?: Partial<{
describe('ProxyLogs server-driven page', () => {
beforeEach(() => {
vi.clearAllMocks();
const localStorageState = new Map<string, string>();
Object.defineProperty(globalThis, 'navigator', {
value: {
clipboard: {
@@ -106,6 +107,22 @@ describe('ProxyLogs server-driven page', () => {
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'localStorage', {
value: {
getItem: vi.fn((key: string) => (localStorageState.has(key) ? localStorageState.get(key)! : null)),
setItem: vi.fn((key: string, value: string) => {
localStorageState.set(String(key), String(value));
}),
removeItem: vi.fn((key: string) => {
localStorageState.delete(String(key));
}),
clear: vi.fn(() => {
localStorageState.clear();
}),
},
configurable: true,
writable: true,
});
apiMock.getSites.mockResolvedValue([
{ id: 9, name: 'main-site', status: 'active' },
{ id: 12, name: 'backup-site', status: 'active' },
@@ -458,6 +475,67 @@ describe('ProxyLogs server-driven page', () => {
}
});
it('remembers the collapsed debug trace panel state across remounts', async () => {
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
<MemoryRouter initialEntries={['/logs']}>
<ToastProvider>
<ProxyLogs />
</ToastProvider>
</MemoryRouter>,
);
});
await flushMicrotasks();
const toggleButton = root.root.find((node) => (
node.type === 'button'
&& typeof node.props.onClick === 'function'
&& node.props['data-debug-trace-panel-toggle'] === true
));
await act(async () => {
toggleButton.props.onClick();
});
await flushMicrotasks();
expect(globalThis.localStorage.setItem).toHaveBeenCalledWith('metapi.proxyLogs.debugTracePanelExpanded', 'false');
await act(async () => {
root.unmount();
});
await act(async () => {
root = create(
<MemoryRouter initialEntries={['/logs']}>
<ToastProvider>
<ProxyLogs />
</ToastProvider>
</MemoryRouter>,
);
});
await flushMicrotasks();
const restoredToggleButton = root.root.find((node) => (
node.type === 'button'
&& typeof node.props.onClick === 'function'
&& node.props['data-debug-trace-panel-toggle'] === true
));
const restoredPanelBody = root.root.find((node) => (
node.type === 'div'
&& node.props['data-debug-trace-panel-body'] === true
));
expect(globalThis.localStorage.getItem).toHaveBeenCalledWith('metapi.proxyLogs.debugTracePanelExpanded');
expect(restoredToggleButton.props['aria-expanded']).toBe(false);
expect(String(restoredPanelBody.props.className || '')).not.toContain('is-open');
} finally {
root?.unmount();
}
});
it('opens debug trace detail on demand instead of preloading the first trace inline', async () => {
let root!: WebTestRenderer;
+24 -1
View File
@@ -75,6 +75,7 @@ const PAGE_SIZES = [20, 50, 100];
const DEFAULT_PAGE_SIZE = 50;
const TRACE_TABLE_LIMIT = 20;
const DEBUG_TRACE_PAGE_SIZE = 5;
const PROXY_LOGS_DEBUG_TRACE_PANEL_STORAGE_KEY = 'metapi.proxyLogs.debugTracePanelExpanded';
const PROXY_LOG_CLIENT_FAMILY_LABELS: Record<string, string> = {
codex: 'Codex',
claude_code: 'Claude Code',
@@ -596,6 +597,24 @@ function CompactSummaryMetric({
);
}
function readStoredDebugTracePanelExpanded(): boolean {
try {
const stored = globalThis.localStorage?.getItem(PROXY_LOGS_DEBUG_TRACE_PANEL_STORAGE_KEY);
if (stored == null) return true;
return stored !== 'false';
} catch {
return true;
}
}
function persistDebugTracePanelExpanded(expanded: boolean) {
try {
globalThis.localStorage?.setItem(PROXY_LOGS_DEBUG_TRACE_PANEL_STORAGE_KEY, expanded ? 'true' : 'false');
} catch {
// Ignore storage write failures and keep UI responsive.
}
}
export default function ProxyLogs() {
const location = useLocation();
const navigate = useNavigate();
@@ -622,7 +641,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 [debugTracePanelExpanded, setDebugTracePanelExpanded] = useState(() => readStoredDebugTracePanelExpanded());
const [debugSettings, setDebugSettings] = useState<ProxyDebugSettingsState>(DEFAULT_PROXY_DEBUG_SETTINGS);
const [debugDraftSettings, setDebugDraftSettings] = useState<ProxyDebugSettingsState>(DEFAULT_PROXY_DEBUG_SETTINGS);
const [debugTraces, setDebugTraces] = useState<ProxyDebugTraceListItem[]>([]);
@@ -986,6 +1005,10 @@ export default function ProxyLogs() {
return () => clearInterval(timer);
}, [debugSettings.proxyDebugTraceEnabled, loadDebugTraceList]);
useEffect(() => {
persistDebugTracePanelExpanded(debugTracePanelExpanded);
}, [debugTracePanelExpanded]);
const persistDebugSettings = useCallback(async (
nextSettings: ProxyDebugSettingsState,
options?: { successMessage?: string; closeAfterSave?: boolean },