'use client'
import { useEffect, useState, useRef, useCallback } from 'react'
import { motion } from 'framer-motion'
import { cn, formatUptime, timeAgo } from '@/lib/utils'
import { useEngineStore } from '@/store/engineStore'
import {
pauseEngine,
resumeEngine,
restartEngine,
killEngine,
} from '@/lib/api/engine'
import type { TargetSite } from '@/lib/types'
const BASE = 'http://localhost:8000'
const SITE_POLL_MS = 25_000
function StatusOrb({ status }: { status: 'Running' | 'Paused' | 'Idle' }) {
const map = {
Running: { color: '#00e87b', label: 'Running', shadow: 'rgba(0,232,123,0.5)' },
Paused: { color: '#fbbf24', label: 'Paused', shadow: 'rgba(251,191,36,0.5)' },
Idle: { color: '#3d4f78', label: 'Idle', shadow: 'rgba(61,79,120,0.3)' },
}
const m = map[status] ?? map.Idle
return (
{/* Pulsing dot + aura */}
{status === 'Running' && (
)}
{m.label}
)
}
interface CtrlBtn {
label: string
icon: React.ReactNode
onClick: () => void
variant: 'default' | 'danger'
disabled?: boolean
}
function ControlButton({ label, icon, onClick, variant, disabled }: CtrlBtn) {
const [busy, setBusy] = useState(false)
const handle = async () => {
if (busy || disabled) return
setBusy(true)
try { await onClick() } finally {
setTimeout(() => setBusy(false), 1500)
}
}
return (
{busy ? (
) : icon}
{label}
)
}
function SiteRow({ site }: { site: TargetSite }) {
const isEnabled = site.enabled === 1
const isCooling = site.cooldown_until ? new Date(site.cooldown_until) > new Date() : false
const hasError = site.consecutive_failures > 0
let statusColor = '#3d4f78'
let statusLabel = 'Disabled'
if (isEnabled && isCooling) { statusColor = '#fbbf24'; statusLabel = 'Cooldown' }
else if (isEnabled && hasError) { statusColor = '#f43f5e'; statusLabel = `${site.consecutive_failures}x fail` }
else if (isEnabled) { statusColor = '#00e87b'; statusLabel = 'Active' }
return (
{site.name}
{statusLabel}
)
}
export default function EngineConsole() {
const { status, uptime_seconds, total_scanned, last_cycle, isOffline } = useEngineStore()
const [sites, setSites] = useState([])
const [cycleAgo, setCycleAgo] = useState('—')
const aliveRef = useRef(true)
const fetchSites = useCallback(async () => {
try {
const res = await fetch(`${BASE}/api/sites`)
const data: TargetSite[] = await res.json()
if (aliveRef.current) setSites(Array.isArray(data) ? data : [])
} catch { /* silent */ }
}, [])
useEffect(() => {
aliveRef.current = true
fetchSites()
const id = setInterval(fetchSites, SITE_POLL_MS)
return () => { aliveRef.current = false; clearInterval(id) }
}, [fetchSites])
// Live relative timestamp
useEffect(() => {
const tick = () => setCycleAgo(timeAgo(last_cycle === 'Never' ? null : last_cycle))
tick()
const id = setInterval(tick, 5000)
return () => clearInterval(id)
}, [last_cycle])
const isRunning = status === 'Running'
const isPaused = status === 'Paused'
const enabledCount = sites.filter(s => s.enabled === 1).length
const controls: CtrlBtn[] = [
{
label: 'Pause',
variant: 'default',
disabled: !isRunning,
onClick: () => pauseEngine(),
icon: (
),
},
{
label: 'Resume',
variant: 'default',
disabled: !isPaused,
onClick: () => resumeEngine(),
icon: (
),
},
{
label: 'Restart',
variant: 'default',
disabled: false,
onClick: () => restartEngine(),
icon: (
),
},
{
label: 'Kill',
variant: 'danger',
disabled: false,
onClick: () => killEngine(),
icon: (
),
},
]
return (
{/* ── Status Hero ── */}
Engine Status
{isOffline && (
OFFLINE
)}
{/* Big status */}
{/* Telemetry strip */}
Uptime
{formatUptime(uptime_seconds)}
Lots Scanned
{total_scanned.toLocaleString()}
{/* ── Controls ── */}
Controls
{controls.map(btn => (
))}
{/* ── Site Health ── */}
Sites
{enabledCount}/{sites.length} active
{sites.length === 0 ? (
No sites configured
) : (
sites.map(s =>
)
)}
)
}