forked from boos/BoosAPI
persist proxy trace panel state (#331)
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user