UI: job badges, skeletons, cancel support + API route to cancel jobs\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,7 @@ export default function JobHistory({ ticker }: Props) {
|
||||
const [jobs, setJobs] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selected, setSelected] = useState<any | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const fetchJobs = async () => {
|
||||
setLoading(true);
|
||||
@@ -28,6 +29,21 @@ export default function JobHistory({ ticker }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = async (jobId: string) => {
|
||||
try {
|
||||
const res = await fetch(`/api/jobs/${jobId}/cancel`, { method: "POST" });
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
// Refresh list
|
||||
fetchJobs();
|
||||
return data.cancelled === true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Cancel failed:", e);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchJobs();
|
||||
const id = setInterval(fetchJobs, 8000);
|
||||
@@ -58,7 +74,13 @@ export default function JobHistory({ ticker }: Props) {
|
||||
<span className="text-xs text-gray-500">{loading ? "Loading..." : `${jobs.length} jobs`}</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{jobs.length === 0 ? (
|
||||
{loading ? (
|
||||
<div className="space-y-2">
|
||||
<div className="h-10 bg-gray-100 rounded animate-pulse" />
|
||||
<div className="h-10 bg-gray-100 rounded animate-pulse" />
|
||||
<div className="h-10 bg-gray-100 rounded animate-pulse" />
|
||||
</div>
|
||||
) : jobs.length === 0 ? (
|
||||
<p className="text-gray-500">No recent jobs for {ticker}</p>
|
||||
) : (
|
||||
jobs.map((j: any) => (
|
||||
@@ -66,7 +88,7 @@ export default function JobHistory({ ticker }: Props) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm">
|
||||
<div className="font-medium">Job: <span className="text-blue-600">{j.id}</span></div>
|
||||
<div className="text-xs text-gray-600">State: <strong>{j.state}</strong></div>
|
||||
<div className="text-xs text-gray-600">State: <strong className={`px-2 py-0.5 rounded text-xs ${j.state === 'completed' ? 'bg-green-100 text-green-800' : j.state === 'failed' ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800'}`}>{j.state}</strong></div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<a href={`/api/jobs/${j.id}`} target="_blank" rel="noreferrer" className="text-sm text-blue-600 hover:underline">API</a>
|
||||
@@ -76,6 +98,14 @@ export default function JobHistory({ ticker }: Props) {
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
{(j.state === 'waiting' || j.state === 'queued') && (
|
||||
<button
|
||||
onClick={() => cancel(j.id)}
|
||||
className="text-sm text-red-600 hover:underline"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user