This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { priceUpdateQueue, maintenanceQueue, schedulerQueue } from '@/lib/queue'
|
||||
import { priceUpdateQueue, maintenanceQueue, schedulerQueue, fundNavSnapshotQueue } from '@/lib/queue'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import RetryFailedButton from '@/components/admin/RetryFailedButton'
|
||||
import TriggerJobButton from '@/components/admin/TriggerJobButton'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -69,13 +70,21 @@ async function getQueueSummary(queue: typeof priceUpdateQueue): Promise<QueueSum
|
||||
}
|
||||
|
||||
export default async function AdminQueuePage() {
|
||||
const [priceSummary, maintenanceSummary, schedulerSummary] = await Promise.all([
|
||||
const [priceSummary, maintenanceSummary, schedulerSummary, fundNavSummary] = await Promise.all([
|
||||
getQueueSummary(priceUpdateQueue),
|
||||
getQueueSummary(maintenanceQueue),
|
||||
getQueueSummary(schedulerQueue),
|
||||
getQueueSummary(fundNavSnapshotQueue),
|
||||
])
|
||||
|
||||
const queues = [priceSummary, maintenanceSummary, schedulerSummary]
|
||||
const queues = [priceSummary, maintenanceSummary, schedulerSummary, fundNavSummary]
|
||||
|
||||
// Queues that support a manual trigger, and the label to show
|
||||
const triggerLabels: Record<string, string> = {
|
||||
'hashex-scheduler': 'Trigger sweep',
|
||||
'hashex-maintenance': 'Run maintenance',
|
||||
'hashex-fund-nav-snapshot': 'Snapshot NAVs',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -90,6 +99,9 @@ export default async function AdminQueuePage() {
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-surface-border">
|
||||
<h3 className="font-medium text-sm">{q.name}</h3>
|
||||
<div className="flex items-center gap-3 text-xs">
|
||||
{triggerLabels[q.name] && (
|
||||
<TriggerJobButton queueName={q.name} label={triggerLabels[q.name]} />
|
||||
)}
|
||||
<RetryFailedButton queueName={q.name} count={q.failed} />
|
||||
<Badge label="waiting" count={q.waiting} color="slate" />
|
||||
<Badge label="active" count={q.active} color="indigo" />
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { priceUpdateQueue, maintenanceQueue, schedulerQueue } from '@/lib/queue'
|
||||
import { priceUpdateQueue, maintenanceQueue, schedulerQueue, fundNavSnapshotQueue } from '@/lib/queue'
|
||||
import { Queue } from 'bullmq'
|
||||
|
||||
const QUEUES: Record<string, Queue> = {
|
||||
'hashex-price-updates': priceUpdateQueue,
|
||||
'hashex-maintenance': maintenanceQueue,
|
||||
'hashex-scheduler': schedulerQueue,
|
||||
'hashex-fund-nav-snapshot': fundNavSnapshotQueue,
|
||||
}
|
||||
|
||||
// Job name to add when manually triggering each queue
|
||||
const TRIGGER_JOB: Record<string, string> = {
|
||||
'hashex-scheduler': 'trigger-sweep',
|
||||
'hashex-maintenance': 'daily-maintenance',
|
||||
'hashex-fund-nav-snapshot': 'fund-nav-snapshot',
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
@@ -36,5 +44,12 @@ export async function POST(
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
||||
if (action === 'trigger') {
|
||||
const jobName = TRIGGER_JOB[params.name]
|
||||
if (!jobName) return NextResponse.json({ error: 'Queue does not support manual trigger' }, { status: 400 })
|
||||
await queue.add(jobName, {}, { jobId: `manual-${Date.now()}` })
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Unknown action' }, { status: 400 })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function TriggerJobButton({ queueName, label }: { queueName: string; label: string }) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [done, setDone] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
async function handleTrigger() {
|
||||
setLoading(true)
|
||||
setDone(false)
|
||||
try {
|
||||
await fetch(`/api/admin/queues/${encodeURIComponent(queueName)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'trigger' }),
|
||||
})
|
||||
setDone(true)
|
||||
setTimeout(() => setDone(false), 3000)
|
||||
} finally {
|
||||
router.refresh()
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleTrigger}
|
||||
disabled={loading}
|
||||
className="text-xs px-2 py-1 rounded bg-indigo-500/20 text-indigo-400 hover:bg-indigo-500/30 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{loading ? 'Triggering…' : done ? '✓ Triggered' : label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user