← back to Handbag Auth Nextjs
src/app/crawler-monitor/page.tsx
168 lines
'use client'
import { useState, useEffect } from 'react'
export default function CrawlerMonitor() {
const [logs, setLogs] = useState<string[]>([])
const [stats, setStats] = useState<any>(null)
const [isRunning, setIsRunning] = useState(false)
const [autoRefresh, setAutoRefresh] = useState(true)
useEffect(() => {
fetchStatus()
const interval = setInterval(() => {
if (autoRefresh) {
fetchStatus()
}
}, 3000)
return () => clearInterval(interval)
}, [autoRefresh])
const fetchStatus = async () => {
try {
const res = await fetch('/api/crawler-status')
const data = await res.json()
if (data.success) {
setLogs(data.logs || [])
setStats(data.stats)
setIsRunning(data.isRunning)
}
} catch (error) {
console.error('Failed to fetch status:', error)
}
}
const startCrawl = async () => {
try {
const res = await fetch('/api/crawler-status', { method: 'POST' })
const data = await res.json()
if (data.success) {
alert('Crawler started!')
fetchStatus()
}
} catch (error) {
alert('Failed to start crawler')
}
}
return (
<div className="min-h-screen bg-gray-900 text-white p-6">
<div className="max-w-7xl mx-auto">
{/* Header */}
<header className="mb-8">
<h1 className="text-4xl font-bold mb-2">🤖 Affiliate Crawler Monitor</h1>
<p className="text-gray-400">Real-time monitoring of top 20 luxury handbag brands crawler</p>
</header>
{/* Controls */}
<div className="bg-gray-800 rounded-lg p-6 mb-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className={`px-4 py-2 rounded-full ${isRunning ? 'bg-green-500' : 'bg-red-500'}`}>
{isRunning ? '🟢 Running' : '🔴 Stopped'}
</div>
<button
onClick={startCrawl}
disabled={isRunning}
className="px-6 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-semibold disabled:opacity-50 disabled:cursor-not-allowed"
>
Start Crawler
</button>
</div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={autoRefresh}
onChange={(e) => setAutoRefresh(e.target.checked)}
className="w-5 h-5"
/>
<span>Auto-refresh (3s)</span>
</label>
</div>
</div>
{/* Stats Grid */}
{stats && (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div className="bg-gradient-to-br from-purple-600 to-purple-800 rounded-lg p-6">
<div className="text-3xl font-bold mb-1">{stats.totalListings?.toLocaleString() || 0}</div>
<div className="text-purple-200 text-sm">Total Items Crawled</div>
</div>
<div className="bg-gradient-to-br from-blue-600 to-blue-800 rounded-lg p-6">
<div className="text-3xl font-bold mb-1">{stats.brands || 0}</div>
<div className="text-blue-200 text-sm">Brands Found</div>
</div>
<div className="bg-gradient-to-br from-green-600 to-green-800 rounded-lg p-6">
<div className="text-3xl font-bold mb-1">{stats.merchants || 0}</div>
<div className="text-green-200 text-sm">Merchants Crawled</div>
</div>
<div className="bg-gradient-to-br from-orange-600 to-orange-800 rounded-lg p-6">
<div className="text-3xl font-bold mb-1">{stats.newToday || 0}</div>
<div className="text-orange-200 text-sm">New Today</div>
</div>
</div>
)}
{/* Brand Progress */}
{stats?.brandProgress && (
<div className="bg-gray-800 rounded-lg p-6 mb-6">
<h2 className="text-xl font-bold mb-4">Brand Progress</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{Object.entries(stats.brandProgress).map(([brand, count]: [string, any]) => (
<div key={brand} className="bg-gray-700 rounded px-3 py-2">
<div className="text-sm font-semibold">{brand}</div>
<div className="text-2xl font-bold text-green-400">{count}</div>
</div>
))}
</div>
</div>
)}
{/* Live Logs */}
<div className="bg-gray-800 rounded-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Live Logs</h2>
<button
onClick={fetchStatus}
className="px-4 py-1 bg-gray-700 hover:bg-gray-600 rounded text-sm"
>
Refresh
</button>
</div>
<div className="bg-black rounded-lg p-4 font-mono text-sm h-96 overflow-y-auto">
{logs.length === 0 ? (
<div className="text-gray-500 text-center py-8">No logs yet. Start the crawler to see progress.</div>
) : (
logs.map((log, idx) => (
<div
key={idx}
className={`mb-1 ${
log.includes('Error') || log.includes('error')
? 'text-red-400'
: log.includes('Found')
? 'text-green-400'
: log.includes('Crawling')
? 'text-blue-400'
: log.includes('Complete')
? 'text-yellow-400'
: 'text-gray-300'
}`}
>
{log}
</div>
))
)}
</div>
</div>
{/* Footer */}
<footer className="mt-8 text-center text-gray-500 text-sm">
<p>Monitoring top 20 luxury brands: HERMÈS, CHANEL, LOUIS VUITTON, DIOR, GUCCI, and more...</p>
<p className="mt-2">Server: 45.61.58.125:7992</p>
</footer>
</div>
</div>
)
}