Fix: Dashboard history and recent tasks visibility
- Recent Tasks widget now shows missions instead of tasks (fixing broken links) - History page now shows Recent Runs at the top, before Missions - This ensures completed runs are visible without scrolling past 50+ missions
This commit is contained in:
@@ -248,6 +248,81 @@ export default function HistoryPage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* Archived Runs - shown first for visibility */}
|
||||
{filteredRuns.length > 0 && (
|
||||
<div>
|
||||
<h2 className="mb-3 text-xs font-medium uppercase tracking-wider text-white/40">
|
||||
Recent Runs ({filteredRuns.length})
|
||||
</h2>
|
||||
<div className="rounded-xl bg-white/[0.02] border border-white/[0.04] overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-white/[0.04]">
|
||||
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
|
||||
Input
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
|
||||
Created
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
|
||||
Cost
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-white/[0.04]">
|
||||
{filteredRuns.map((run) => {
|
||||
const status = run.status as keyof typeof statusIcons;
|
||||
const Icon = statusIcons[status] || Clock;
|
||||
const config =
|
||||
statusConfig[status] || statusConfig.pending;
|
||||
return (
|
||||
<tr
|
||||
key={run.id}
|
||||
className="group hover:bg-white/[0.02] transition-colors"
|
||||
>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium",
|
||||
config.bg,
|
||||
config.color
|
||||
)}
|
||||
>
|
||||
<Icon className="h-3 w-3" />
|
||||
{run.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="max-w-md truncate text-sm text-white/80">
|
||||
{run.input_text}
|
||||
</p>
|
||||
<CopyButton text={run.input_text} showOnHover label="Copied input" />
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<RelativeTime
|
||||
date={run.created_at}
|
||||
className="text-xs text-white/40"
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className="text-sm text-emerald-400 tabular-nums">
|
||||
${(run.total_cost_cents / 100).toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Missions */}
|
||||
{filteredMissions.length > 0 && (
|
||||
<div>
|
||||
@@ -449,80 +524,6 @@ export default function HistoryPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Archived Runs */}
|
||||
{filteredRuns.length > 0 && (
|
||||
<div>
|
||||
<h2 className="mb-3 text-xs font-medium uppercase tracking-wider text-white/40">
|
||||
Archived Runs ({filteredRuns.length})
|
||||
</h2>
|
||||
<div className="rounded-xl bg-white/[0.02] border border-white/[0.04] overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-white/[0.04]">
|
||||
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
|
||||
Input
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
|
||||
Created
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
|
||||
Cost
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-white/[0.04]">
|
||||
{filteredRuns.map((run) => {
|
||||
const status = run.status as keyof typeof statusIcons;
|
||||
const Icon = statusIcons[status] || Clock;
|
||||
const config =
|
||||
statusConfig[status] || statusConfig.pending;
|
||||
return (
|
||||
<tr
|
||||
key={run.id}
|
||||
className="group hover:bg-white/[0.02] transition-colors"
|
||||
>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium",
|
||||
config.bg,
|
||||
config.color
|
||||
)}
|
||||
>
|
||||
<Icon className="h-3 w-3" />
|
||||
{run.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="max-w-md truncate text-sm text-white/80">
|
||||
{run.input_text}
|
||||
</p>
|
||||
<CopyButton text={run.input_text} showOnHover label="Copied input" />
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<RelativeTime
|
||||
date={run.created_at}
|
||||
className="text-xs text-white/40"
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className="text-sm text-emerald-400 tabular-nums">
|
||||
${(run.total_cost_cents / 100).toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { listTasks, TaskState } from "@/lib/api";
|
||||
import { listMissions, Mission } from "@/lib/api";
|
||||
import {
|
||||
ArrowRight,
|
||||
CheckCircle,
|
||||
@@ -11,18 +11,21 @@ import {
|
||||
Loader,
|
||||
Clock,
|
||||
Ban,
|
||||
Target,
|
||||
} from "lucide-react";
|
||||
|
||||
const statusIcons = {
|
||||
const statusIcons: Record<string, typeof Clock> = {
|
||||
pending: Clock,
|
||||
active: Loader,
|
||||
running: Loader,
|
||||
completed: CheckCircle,
|
||||
failed: XCircle,
|
||||
cancelled: Ban,
|
||||
};
|
||||
|
||||
const statusColors = {
|
||||
const statusColors: Record<string, string> = {
|
||||
pending: "text-amber-400",
|
||||
active: "text-indigo-400",
|
||||
running: "text-indigo-400",
|
||||
completed: "text-emerald-400",
|
||||
failed: "text-red-400",
|
||||
@@ -30,30 +33,34 @@ const statusColors = {
|
||||
};
|
||||
|
||||
export function RecentTasks() {
|
||||
const [tasks, setTasks] = useState<TaskState[]>([]);
|
||||
const [missions, setMissions] = useState<Mission[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTasks = async () => {
|
||||
const fetchMissions = async () => {
|
||||
try {
|
||||
const data = await listTasks();
|
||||
setTasks(data.slice(0, 5));
|
||||
const data = await listMissions();
|
||||
// Sort by updated_at descending and take top 5
|
||||
const sorted = data
|
||||
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
|
||||
.slice(0, 5);
|
||||
setMissions(sorted);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch tasks:", error);
|
||||
console.error("Failed to fetch missions:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchTasks();
|
||||
const interval = setInterval(fetchTasks, 3000);
|
||||
fetchMissions();
|
||||
const interval = setInterval(fetchMissions, 5000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-white">Recent Tasks</h3>
|
||||
<h3 className="text-sm font-medium text-white">Recent Missions</h3>
|
||||
<span className="flex items-center gap-1.5 rounded-md bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-400">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />
|
||||
LIVE
|
||||
@@ -62,28 +69,30 @@ export function RecentTasks() {
|
||||
|
||||
{loading ? (
|
||||
<p className="text-xs text-white/40">Loading...</p>
|
||||
) : tasks.length === 0 ? (
|
||||
<p className="text-xs text-white/40">No tasks yet</p>
|
||||
) : missions.length === 0 ? (
|
||||
<p className="text-xs text-white/40">No missions yet</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{tasks.map((task) => {
|
||||
const Icon = statusIcons[task.status];
|
||||
{missions.map((mission) => {
|
||||
const Icon = statusIcons[mission.status] || Clock;
|
||||
const color = statusColors[mission.status] || "text-white/40";
|
||||
const title = mission.title || "Untitled Mission";
|
||||
return (
|
||||
<Link
|
||||
key={task.id}
|
||||
href={`/control?task=${task.id}`}
|
||||
key={mission.id}
|
||||
href={`/control?mission=${mission.id}`}
|
||||
className="flex items-center justify-between rounded-lg bg-white/[0.02] hover:bg-white/[0.04] border border-white/[0.04] hover:border-white/[0.08] p-3 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Icon
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
statusColors[task.status],
|
||||
task.status === "running" && "animate-spin"
|
||||
color,
|
||||
(mission.status === "running" || mission.status === "active") && "animate-spin"
|
||||
)}
|
||||
/>
|
||||
<span className="max-w-[180px] truncate text-sm text-white/80">
|
||||
{task.task}
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight className="h-4 w-4 text-white/30" />
|
||||
|
||||
Reference in New Issue
Block a user