← 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>
  )
}