queue/admin/frontend/src/pages/QueueDashboardPage.tsx
2026-02-04 16:10:54 -08:00

153 lines
4 KiB
TypeScript

/** @jsxImportSource react */
import { useCallback, useMemo } from 'react';
import { useNavigate } from '@lilith/ui-router';
import { useQueueMetrics } from '../hooks/useQueueMetrics';
import { useQueueControl } from '../hooks/useQueueControl';
import { QueueGrid } from '../components/QueueGrid';
import { QueueSummaryBar } from '../components/QueueSummaryBar';
import type { QueueStats, QueueSummaryStats } from '../types';
export interface QueueDashboardPageProps {
/** Base API URL for queue endpoints */
apiUrl?: string;
/** Refresh interval in milliseconds */
refreshInterval?: number;
/** Custom class name */
className?: string;
}
/**
* Dashboard page showing all queues with summary statistics.
*
* Uses react-router-dom for navigation to queue details.
*
* @example
* ```tsx
* <Route path="/queues" element={<QueueDashboardPage apiUrl="/api/admin/queues" />} />
* ```
*/
export function QueueDashboardPage({
apiUrl = '/api/admin/queues',
refreshInterval = 5000,
className = '',
}: QueueDashboardPageProps) {
const navigate = useNavigate();
const { metrics, isLoading, error } = useQueueMetrics({
apiUrl,
refreshInterval,
});
const { pauseQueue, resumeQueue, isPending } = useQueueControl({
apiUrl,
});
// Transform metrics to QueueStats format for grid display
const queueStats: QueueStats[] = useMemo(
() =>
metrics.map((m) => ({
name: m.name,
waiting: m.counts.waiting,
active: m.counts.active,
completed: m.counts.completed,
failed: m.counts.failed,
delayed: m.counts.delayed,
paused: m.isPaused,
})),
[metrics]
);
// Calculate summary stats
const summary: QueueSummaryStats = useMemo(
() => ({
queues: queueStats,
totalWaiting: queueStats.reduce((sum, q) => sum + q.waiting, 0),
totalActive: queueStats.reduce((sum, q) => sum + q.active, 0),
totalCompleted: queueStats.reduce((sum, q) => sum + q.completed, 0),
totalFailed: queueStats.reduce((sum, q) => sum + q.failed, 0),
}),
[queueStats]
);
const handleQueueClick = useCallback(
(queue: QueueStats) => {
navigate(`/queues/${encodeURIComponent(queue.name)}`);
},
[navigate]
);
const handlePause = useCallback(
(queueName: string) => {
pauseQueue({ queueName });
},
[pauseQueue]
);
const handleResume = useCallback(
(queueName: string) => {
resumeQueue({ queueName });
},
[resumeQueue]
);
if (isLoading) {
return (
<div className={`queue-dashboard-page ${className}`}>
<div className="queue-dashboard-loading">
<div className="queue-loading-spinner" />
<p>Loading queues...</p>
</div>
</div>
);
}
if (error) {
return (
<div className={`queue-dashboard-page ${className}`}>
<div className="queue-dashboard-error">
<h3>Error Loading Queue Data</h3>
<p>{error.message}</p>
</div>
</div>
);
}
if (queueStats.length === 0) {
return (
<div className={`queue-dashboard-page ${className}`}>
<div className="queue-dashboard-header">
<h1>Job Queues</h1>
<p>Monitor and manage background job queues</p>
</div>
<div className="queue-dashboard-empty">
<p>No queue data available</p>
</div>
</div>
);
}
return (
<div className={`queue-dashboard-page ${className}`}>
<div className="queue-dashboard-header">
<h1>Job Queues</h1>
<p>Monitor and manage background job queues</p>
</div>
<QueueSummaryBar summary={summary} className="queue-dashboard-summary" />
<QueueGrid
queues={queueStats}
onQueueClick={handleQueueClick}
onPause={!isPending ? handlePause : undefined}
onResume={!isPending ? handleResume : undefined}
/>
<div className="queue-dashboard-footer">
Auto-refreshes every {refreshInterval / 1000}s · Last updated:{' '}
{new Date().toLocaleTimeString()}
</div>
</div>
);
}