import { useNavigate } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { Link } from "react-router-dom";
import {
Activity,
Server,
AlertTriangle,
Wifi,
WifiOff,
Terminal,
RefreshCw,
Shield,
Zap,
ArrowRight,
} from "lucide-react";
import { agentsApi, Agent } from "../api/client";
/**
* Formats a date to a relative time string (e.g., "2 minutes ago", "1 hour ago")
*/
function formatRelativeTime(dateString: string | null): string {
if (!dateString) return "Never seen";
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
if (diffInSeconds < 60) {
return "Just now";
} else if (diffInSeconds < 3600) {
const minutes = Math.floor(diffInSeconds / 60);
return `${minutes}m ago`;
} else if (diffInSeconds < 86400) {
const hours = Math.floor(diffInSeconds / 3600);
return `${hours}h ago`;
} else {
const days = Math.floor(diffInSeconds / 86400);
return `${days}d ago`;
}
}
/**
* Loading skeleton for stat cards with shimmer animation
*/
function StatCardSkeleton({ delay }: { delay: number }) {
return (
);
}
/**
* Stat card component with Mission Control styling - navigates to filtered agents page on click
*/
function StatCard({
title,
value,
icon: Icon,
description,
accentColor,
delay,
linkTo,
}: {
title: string;
value: string | number;
icon: React.ComponentType<{ className?: string }>;
description?: string;
accentColor: "cyan" | "green" | "amber" | "rose";
delay: number;
linkTo: string;
}) {
const navigate = useNavigate();
const colorClasses = {
cyan: {
icon: "text-cyan",
glow: "glow-cyan",
bg: "bg-[var(--accent-cyan-muted)]",
value: "text-cyan",
},
green: {
icon: "text-green",
glow: "glow-green",
bg: "bg-[var(--accent-green-muted)]",
value: "text-green",
},
amber: {
icon: "text-amber",
glow: "glow-amber",
bg: "bg-[var(--accent-amber-muted)]",
value: "text-amber",
},
rose: {
icon: "text-rose",
glow: "glow-rose",
bg: "bg-[var(--accent-rose-muted)]",
value: "text-rose",
},
};
const colors = colorClasses[accentColor];
const handleClick = () => {
navigate(linkTo);
};
return (
{
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleClick();
}
}}
>
{/* Subtle gradient overlay on hover */}
{title}
{value}
{description && (
{description}
)}
{/* Icon - smaller padding */}
{/* Simplified view all - no border */}
);
}
/**
* Status indicator dot with pulse animation
*/
function StatusDot({ status }: { status: string }) {
const statusClasses = {
online: "status-dot online",
offline: "status-dot offline",
error: "status-dot error",
};
return (
);
}
/**
* Activity list item with hover effects - clickable with Link
*/
function ActivityItem({ agent }: { agent: Agent }) {
return (
{agent.hostname}
{agent.os_type}
{formatRelativeTime(agent.last_seen)}
);
}
/**
* Quick action button with glow effect
*/
function QuickActionButton({
icon: Icon,
label,
description,
onClick,
}: {
icon: React.ComponentType<{ className?: string }>;
label: string;
description: string;
onClick?: () => void;
}) {
return (
);
}
/**
* Loading skeleton for activity list
*/
function ActivityListSkeleton() {
return (
{[0, 1, 2, 3, 4].map((i) => (
))}
);
}
/**
* Main Dashboard component with Mission Control aesthetic
*
* Stat cards navigate to the Agents page with status filters.
* This approach scales better for large agent counts (1000+) since
* the Agents page handles pagination, search, and sorting.
*/
export function Dashboard() {
const { data: agents = [], isLoading } = useQuery({
queryKey: ["agents"],
queryFn: () => agentsApi.list().then((res) => res.data),
refetchInterval: 30000,
});
const onlineAgents = agents.filter((a: Agent) => a.status === "online");
const offlineAgents = agents.filter((a: Agent) => a.status === "offline");
const errorAgents = agents.filter((a: Agent) => a.status === "error");
// Determine system status
const hasErrors = errorAgents.length > 0;
const allOffline = agents.length > 0 && onlineAgents.length === 0;
const systemStatus = hasErrors
? "ATTENTION REQUIRED"
: allOffline
? "ALL SYSTEMS OFFLINE"
: "SYSTEMS OPERATIONAL";
const statusClass = hasErrors
? "status-error"
: allOffline
? "status-warning"
: "status-online";
return (
{/* Page Header */}
{/* Stat Cards Grid */}
{isLoading ? (
<>
>
) : (
<>
>
)}
{/* Bottom Grid: Activity + Quick Actions */}
{/* Recent Activity Card */}
Recent Activity
{!isLoading && agents.length > 0 && (
{agents.length} agent{agents.length !== 1 ? "s" : ""}
)}
{isLoading ? (
) : agents.length === 0 ? (
No agents registered
Deploy an agent to start monitoring endpoints.
) : (
{agents.slice(0, 5).map((agent: Agent) => (
))}
)}
{/* Quick Actions Card */}
Quick Actions
{/* Terminal-style hint */}
);
}