feat(dashboard): add error log viewer for CLIProxy diagnostics

Add ErrorLogsMonitor component to Home page that displays CLIProxyAPI
error logs when requests fail. Users can now diagnose why success rates
drop by viewing detailed error log contents.

Backend:
- Add fetchCliproxyErrorLogs/fetchCliproxyErrorLogContent in stats-fetcher
- Add GET /api/cliproxy/error-logs and /api/cliproxy/error-logs/:name routes
- Include path traversal protection for filename validation

Frontend:
- Add CliproxyErrorLog type and errorLogs API methods
- Add useCliproxyErrorLogs/useCliproxyErrorLogContent hooks
- Create ErrorLogsMonitor component with expandable log viewer
- Integrate into Home page below AuthMonitor

Closes #132
This commit is contained in:
kaitranntt
2025-12-18 02:15:35 -05:00
parent 1a140e4a32
commit 5b3d56548a
6 changed files with 451 additions and 0 deletions
+73
View File
@@ -21,6 +21,8 @@ import {
fetchCliproxyStats,
fetchCliproxyModels,
isCliproxyRunning,
fetchCliproxyErrorLogs,
fetchCliproxyErrorLogContent,
} from '../cliproxy/stats-fetcher';
import {
listOpenAICompatProviders,
@@ -1374,6 +1376,77 @@ apiRoutes.get('/cliproxy/models', async (_req: Request, res: Response): Promise<
}
});
// ==================== Error Logs ====================
/**
* GET /api/cliproxy/error-logs - Get list of error log files
* Returns: { files: CliproxyErrorLog[] } or error if proxy not running
*/
apiRoutes.get('/cliproxy/error-logs', async (_req: Request, res: Response): Promise<void> => {
try {
const running = await isCliproxyRunning();
if (!running) {
res.status(503).json({
error: 'CLIProxyAPI not running',
message: 'Start a CLIProxy session to view error logs',
});
return;
}
const files = await fetchCliproxyErrorLogs();
if (files === null) {
res.status(503).json({
error: 'Error logs unavailable',
message: 'CLIProxyAPI is running but error logs endpoint not responding',
});
return;
}
res.json({ files });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
/**
* GET /api/cliproxy/error-logs/:name - Get content of a specific error log
* Returns: plain text log content
*/
apiRoutes.get('/cliproxy/error-logs/:name', async (req: Request, res: Response): Promise<void> => {
const { name } = req.params;
// Validate filename format and prevent path traversal
if (
!name ||
!name.startsWith('error-') ||
!name.endsWith('.log') ||
name.includes('..') ||
name.includes('/') ||
name.includes('\\')
) {
res.status(400).json({ error: 'Invalid error log filename' });
return;
}
try {
const running = await isCliproxyRunning();
if (!running) {
res.status(503).json({ error: 'CLIProxyAPI not running' });
return;
}
const content = await fetchCliproxyErrorLogContent(name);
if (content === null) {
res.status(404).json({ error: 'Error log not found' });
return;
}
res.type('text/plain').send(content);
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
// ============================================
// OpenAI Compatibility Layer Routes
// ============================================