Files
scrapy-ui/app/(dashboard)/page.tsx
Sebastian Krüger fa31df4e02 refactor: Remove /ui path prefix
Removed the /ui basePath from Next.js configuration to serve the app at root path:

- Removed basePath: "/ui" from next.config.ts
- Updated all API route calls from /ui/api/* to /api/*
- App now accessible at root path instead of /ui subdirectory

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 06:29:58 +01:00

174 lines
5.4 KiB
TypeScript

"use client";
import { useQuery } from "@tanstack/react-query";
import { Header } from "@/components/header";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import {
Activity,
FolderKanban,
PlayCircle,
Clock,
CheckCircle2,
} from "lucide-react";
import { DaemonStatus } from "@/lib/types";
export default function DashboardPage() {
const { data: daemonStatus, isLoading: isDaemonLoading } = useQuery({
queryKey: ["daemon-status"],
queryFn: async (): Promise<DaemonStatus> => {
const res = await fetch("/api/scrapyd/daemon");
if (!res.ok) throw new Error("Failed to fetch daemon status");
return res.json();
},
});
const { data: projects, isLoading: isProjectsLoading } = useQuery({
queryKey: ["projects"],
queryFn: async () => {
const res = await fetch("/api/scrapyd/projects");
if (!res.ok) throw new Error("Failed to fetch projects");
return res.json();
},
});
const stats = [
{
title: "Running Jobs",
value: daemonStatus?.running ?? 0,
icon: PlayCircle,
color: "text-green-500",
bgColor: "bg-green-500/10",
},
{
title: "Pending Jobs",
value: daemonStatus?.pending ?? 0,
icon: Clock,
color: "text-yellow-500",
bgColor: "bg-yellow-500/10",
},
{
title: "Finished Jobs",
value: daemonStatus?.finished ?? 0,
icon: CheckCircle2,
color: "text-blue-500",
bgColor: "bg-blue-500/10",
},
{
title: "Total Projects",
value: projects?.projects?.length ?? 0,
icon: FolderKanban,
color: "text-purple-500",
bgColor: "bg-purple-500/10",
},
];
return (
<div className="space-y-6">
<Header
title="Dashboard"
description="Monitor your Scrapyd instance and scraping jobs"
/>
{/* Stats Grid */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{stats.map((stat, index) => (
<Card key={index}>
<CardHeader className="flex flex-row items-center justify-between gap-0 pb-2">
<CardTitle className="text-sm font-medium">
{stat.title}
</CardTitle>
<div className={`rounded-full p-2 ${stat.bgColor}`}>
<stat.icon className={`h-4 w-4 ${stat.color}`} />
</div>
</CardHeader>
<CardContent>
{isDaemonLoading || isProjectsLoading ? (
<Skeleton className="h-8 w-16" />
) : (
<div className="text-2xl font-bold">{stat.value}</div>
)}
</CardContent>
</Card>
))}
</div>
{/* System Status Card */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>System Status</CardTitle>
<Badge variant="outline" className="flex items-center gap-1">
<Activity className="h-3 w-3" />
{isDaemonLoading ? (
<Skeleton className="h-4 w-12" />
) : (
daemonStatus?.status
)}
</Badge>
</div>
</CardHeader>
<CardContent>
{isDaemonLoading ? (
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
</div>
) : (
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Node Name:</span>
<span className="font-mono">{daemonStatus?.node_name}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Total Jobs:</span>
<span>
{(daemonStatus?.running ?? 0) +
(daemonStatus?.pending ?? 0) +
(daemonStatus?.finished ?? 0)}
</span>
</div>
</div>
)}
</CardContent>
</Card>
{/* Projects Overview */}
<Card>
<CardHeader>
<CardTitle>Projects Overview</CardTitle>
</CardHeader>
<CardContent>
{isProjectsLoading ? (
<div className="space-y-2">
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-8 w-full" />
))}
</div>
) : projects?.projects?.length > 0 ? (
<div className="space-y-2">
{projects.projects.map((project: string) => (
<div
key={project}
className="flex items-center justify-between rounded-lg border p-3"
>
<div className="flex items-center gap-2">
<FolderKanban className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{project}</span>
</div>
<Badge variant="secondary">Active</Badge>
</div>
))}
</div>
) : (
<p className="text-center text-muted-foreground">
No projects found. Upload a project to get started.
</p>
)}
</CardContent>
</Card>
</div>
);
}