Wire frontend to real API endpoints
Dashboard page:
- Fetch top clients from /api/dashboard/by-client
- Show loading state while fetching
- Display empty state when no client data
- Show real client_id, job count, and success rate
Scrapers page:
- Fetch versions from /api/admin/scrapers
- Wire promote/deprecate buttons to real API calls
- Wire add version form to POST /api/admin/scrapers
- Wire traffic allocation to PUT /api/admin/scrapers/{id}/traffic
- Add loading and error states
Dockerfile:
- Add COPY commands for new directories (api/, core/, scrapers/, etc.)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useJobs } from '@/contexts/JobsContext';
|
||||
import { JobStatus } from '@/components/ScraperTest';
|
||||
|
||||
// Mock data for initial development - will be replaced with API data
|
||||
const MOCK_CLIENTS = [
|
||||
{ client_id: 'client-001', job_count: 45, success_rate: 94.2 },
|
||||
{ client_id: 'client-002', job_count: 38, success_rate: 89.5 },
|
||||
{ client_id: 'client-003', job_count: 27, success_rate: 96.3 },
|
||||
{ client_id: 'client-004', job_count: 19, success_rate: 84.2 },
|
||||
{ client_id: 'client-005', job_count: 12, success_rate: 91.7 },
|
||||
];
|
||||
// API base URL
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
|
||||
|
||||
// Client stats type from API
|
||||
interface ClientStats {
|
||||
client_id: string;
|
||||
source: string | null;
|
||||
total_jobs: number;
|
||||
completed: number;
|
||||
failed: number;
|
||||
success_rate: number;
|
||||
total_reviews: number;
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
||||
@@ -55,6 +60,26 @@ function getErrorType(errorMessage: string | null): string {
|
||||
export default function DashboardPage() {
|
||||
const { jobs, isLoading } = useJobs();
|
||||
const [currentDate] = useState(new Date());
|
||||
const [clients, setClients] = useState<ClientStats[]>([]);
|
||||
const [clientsLoading, setClientsLoading] = useState(true);
|
||||
|
||||
// Fetch client stats from API
|
||||
useEffect(() => {
|
||||
async function fetchClients() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/dashboard/by-client?limit=5`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setClients(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch client stats:', error);
|
||||
} finally {
|
||||
setClientsLoading(false);
|
||||
}
|
||||
}
|
||||
fetchClients();
|
||||
}, []);
|
||||
|
||||
// Calculate stats from jobs data
|
||||
const stats = useMemo(() => {
|
||||
@@ -453,42 +478,67 @@ export default function DashboardPage() {
|
||||
View all
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{MOCK_CLIENTS.map((client, index) => (
|
||||
<div
|
||||
key={client.client_id}
|
||||
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
||||
{clientsLoading ? (
|
||||
<div className="py-8 flex justify-center">
|
||||
<div className="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
) : clients.length === 0 ? (
|
||||
<div className="py-8 text-center text-gray-500">
|
||||
<svg
|
||||
className="w-12 h-12 mx-auto mb-3 opacity-30"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-6 h-6 bg-blue-100 text-blue-700 rounded-full flex items-center justify-center text-xs font-bold">
|
||||
{index + 1}
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-medium text-gray-800">
|
||||
{client.client_id}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{client.job_count} jobs
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||
/>
|
||||
</svg>
|
||||
<p className="font-medium">No client data yet</p>
|
||||
<p className="text-sm mt-1">Run jobs with requester metadata to see stats</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{clients.map((client, index) => (
|
||||
<div
|
||||
key={client.client_id}
|
||||
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="w-6 h-6 bg-blue-100 text-blue-700 rounded-full flex items-center justify-center text-xs font-bold">
|
||||
{index + 1}
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-medium text-gray-800">
|
||||
{client.client_id}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{client.total_jobs} jobs
|
||||
{client.source && ` · ${client.source}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p
|
||||
className={`font-semibold ${
|
||||
client.success_rate >= 90
|
||||
? 'text-green-600'
|
||||
: client.success_rate >= 80
|
||||
? 'text-yellow-600'
|
||||
: 'text-red-600'
|
||||
}`}
|
||||
>
|
||||
{client.success_rate.toFixed(1)}%
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">success</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p
|
||||
className={`font-semibold ${
|
||||
client.success_rate >= 90
|
||||
? 'text-green-600'
|
||||
: client.success_rate >= 80
|
||||
? 'text-yellow-600'
|
||||
: 'text-red-600'
|
||||
}`}
|
||||
>
|
||||
{client.success_rate}%
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">success</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user