2026-04-14 09:36:32 +00:00
|
|
|
import { useState } from "react";
|
2026-04-13 12:45:17 +00:00
|
|
|
import { Link } from "react-router-dom";
|
|
|
|
|
import { manualPoll, updateTracker, removeTracker } from "../../lib/api";
|
|
|
|
|
import type { WatchedTracker } from "../../lib/types";
|
2026-04-14 13:27:29 +00:00
|
|
|
import ConfirmModal from "../ui/ConfirmModal";
|
2026-04-20 06:55:26 +00:00
|
|
|
import { buttonClass, cardContentClass } from "../ui/primitives";
|
2026-04-13 12:45:17 +00:00
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
trackers: WatchedTracker[];
|
|
|
|
|
projectId: string;
|
|
|
|
|
onRefresh: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function TrackerList({ trackers, projectId, onRefresh }: Props) {
|
2026-04-14 09:36:32 +00:00
|
|
|
const [pollingIds, setPollingIds] = useState<string[]>([]);
|
2026-04-14 13:27:29 +00:00
|
|
|
const [trackerToRemove, setTrackerToRemove] = useState<WatchedTracker | null>(null);
|
2026-04-14 09:36:32 +00:00
|
|
|
|
2026-04-13 12:45:17 +00:00
|
|
|
async function handlePollNow(tracker: WatchedTracker) {
|
2026-04-14 13:59:23 +00:00
|
|
|
if (tracker.status !== "valid") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-04-13 12:45:17 +00:00
|
|
|
try {
|
2026-04-14 09:36:32 +00:00
|
|
|
setPollingIds((prev) => [...prev, tracker.id]);
|
2026-04-13 12:45:17 +00:00
|
|
|
await manualPoll(tracker.id);
|
|
|
|
|
onRefresh();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Poll failed:", err);
|
2026-04-14 09:36:32 +00:00
|
|
|
} finally {
|
|
|
|
|
setPollingIds((prev) => prev.filter((id) => id !== tracker.id));
|
2026-04-13 12:45:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleToggleEnabled(tracker: WatchedTracker) {
|
|
|
|
|
try {
|
|
|
|
|
await updateTracker(
|
|
|
|
|
tracker.id,
|
2026-04-14 09:36:32 +00:00
|
|
|
tracker.tracker_id,
|
|
|
|
|
tracker.tracker_label,
|
2026-04-13 12:45:17 +00:00
|
|
|
tracker.polling_interval,
|
2026-04-14 13:59:23 +00:00
|
|
|
tracker.analyst_agent_id ?? "",
|
|
|
|
|
tracker.developer_agent_id ?? "",
|
2026-04-22 07:06:00 +00:00
|
|
|
tracker.reviewer_agent_id ?? "",
|
2026-04-13 12:45:17 +00:00
|
|
|
tracker.filters,
|
|
|
|
|
!tracker.enabled
|
|
|
|
|
);
|
|
|
|
|
onRefresh();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Update failed:", err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:27:29 +00:00
|
|
|
async function handleConfirmRemove() {
|
|
|
|
|
if (!trackerToRemove) return;
|
|
|
|
|
const tracker = trackerToRemove;
|
|
|
|
|
setTrackerToRemove(null);
|
2026-04-13 12:45:17 +00:00
|
|
|
try {
|
|
|
|
|
await removeTracker(tracker.id);
|
|
|
|
|
onRefresh();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Remove failed:", err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
{trackers.length === 0 && (
|
|
|
|
|
<div className="text-sm text-gray-400">No trackers configured.</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{trackers.map((tracker) => (
|
|
|
|
|
<div
|
|
|
|
|
key={tracker.id}
|
2026-04-20 06:55:26 +00:00
|
|
|
className={`flex items-center justify-between gap-4 ${cardContentClass}`}
|
2026-04-13 12:45:17 +00:00
|
|
|
>
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<span className="font-medium text-sm">{tracker.tracker_label}</span>
|
|
|
|
|
<span className="text-xs text-gray-400">#{tracker.tracker_id}</span>
|
|
|
|
|
<span
|
|
|
|
|
className={`text-xs px-2 py-0.5 rounded-full font-medium ${
|
2026-04-14 13:59:23 +00:00
|
|
|
tracker.status === "invalid"
|
|
|
|
|
? "bg-red-100 text-red-700"
|
|
|
|
|
: tracker.enabled
|
2026-04-13 12:45:17 +00:00
|
|
|
? "bg-green-100 text-green-700"
|
|
|
|
|
: "bg-gray-100 text-gray-500"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
2026-04-14 13:59:23 +00:00
|
|
|
{tracker.status === "invalid" ? "Invalid" : tracker.enabled ? "Active" : "Paused"}
|
2026-04-13 12:45:17 +00:00
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-gray-400 mt-1">
|
|
|
|
|
{tracker.last_polled_at
|
|
|
|
|
? `Last poll: ${new Date(tracker.last_polled_at).toLocaleString()}`
|
|
|
|
|
: "Never polled"}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2 shrink-0">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => handlePollNow(tracker)}
|
2026-04-14 13:59:23 +00:00
|
|
|
disabled={pollingIds.includes(tracker.id) || tracker.status !== "valid"}
|
2026-04-20 06:55:26 +00:00
|
|
|
className={buttonClass({ variant: "primary", size: "xs" })}
|
2026-04-13 12:45:17 +00:00
|
|
|
>
|
2026-04-14 09:36:32 +00:00
|
|
|
{pollingIds.includes(tracker.id) ? "Polling..." : "Poll now"}
|
2026-04-13 12:45:17 +00:00
|
|
|
</button>
|
2026-04-14 09:36:32 +00:00
|
|
|
<Link
|
|
|
|
|
to={`/projects/${projectId}/trackers/${tracker.id}/edit`}
|
2026-04-20 06:55:26 +00:00
|
|
|
className={buttonClass({ variant: "secondary", size: "xs" })}
|
2026-04-14 09:36:32 +00:00
|
|
|
>
|
|
|
|
|
Edit
|
|
|
|
|
</Link>
|
2026-04-13 12:45:17 +00:00
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => handleToggleEnabled(tracker)}
|
2026-04-14 13:59:23 +00:00
|
|
|
disabled={tracker.status !== "valid"}
|
2026-04-20 06:55:26 +00:00
|
|
|
className={buttonClass({ variant: "secondary", size: "xs" })}
|
2026-04-13 12:45:17 +00:00
|
|
|
>
|
|
|
|
|
{tracker.enabled ? "Pause" : "Resume"}
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
2026-04-14 13:27:29 +00:00
|
|
|
onClick={() => setTrackerToRemove(tracker)}
|
2026-04-20 06:55:26 +00:00
|
|
|
className={buttonClass({ variant: "dangerSoft", size: "xs" })}
|
2026-04-13 12:45:17 +00:00
|
|
|
>
|
|
|
|
|
Remove
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
<Link
|
|
|
|
|
to={`/projects/${projectId}/trackers/new`}
|
2026-04-20 06:55:26 +00:00
|
|
|
className={buttonClass({ variant: "primary" })}
|
2026-04-13 12:45:17 +00:00
|
|
|
>
|
|
|
|
|
Add tracker
|
|
|
|
|
</Link>
|
2026-04-14 13:27:29 +00:00
|
|
|
|
|
|
|
|
<ConfirmModal
|
|
|
|
|
isOpen={trackerToRemove !== null}
|
|
|
|
|
onCancel={() => setTrackerToRemove(null)}
|
|
|
|
|
onConfirm={() => void handleConfirmRemove()}
|
|
|
|
|
/>
|
2026-04-13 12:45:17 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|