orchai/src/components/trackers/TrackerList.tsx

138 lines
4.4 KiB
TypeScript
Raw Normal View History

import { useState } from "react";
import { Link } from "react-router-dom";
import { manualPoll, updateTracker, removeTracker } from "../../lib/api";
import type { WatchedTracker } from "../../lib/types";
import ConfirmModal from "../ui/ConfirmModal";
interface Props {
trackers: WatchedTracker[];
projectId: string;
onRefresh: () => void;
}
export default function TrackerList({ trackers, projectId, onRefresh }: Props) {
const [pollingIds, setPollingIds] = useState<string[]>([]);
const [trackerToRemove, setTrackerToRemove] = useState<WatchedTracker | null>(null);
async function handlePollNow(tracker: WatchedTracker) {
try {
setPollingIds((prev) => [...prev, tracker.id]);
await manualPoll(tracker.id);
onRefresh();
} catch (err) {
console.error("Poll failed:", err);
} finally {
setPollingIds((prev) => prev.filter((id) => id !== tracker.id));
}
}
async function handleToggleEnabled(tracker: WatchedTracker) {
try {
await updateTracker(
tracker.id,
tracker.tracker_id,
tracker.tracker_label,
tracker.polling_interval,
tracker.agent_config,
tracker.filters,
!tracker.enabled
);
onRefresh();
} catch (err) {
console.error("Update failed:", err);
}
}
async function handleConfirmRemove() {
if (!trackerToRemove) return;
const tracker = trackerToRemove;
setTrackerToRemove(null);
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}
className="bg-white rounded-lg border border-gray-200 p-4 flex items-center justify-between gap-4"
>
<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 ${
tracker.enabled
? "bg-green-100 text-green-700"
: "bg-gray-100 text-gray-500"
}`}
>
{tracker.enabled ? "Active" : "Paused"}
</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)}
disabled={pollingIds.includes(tracker.id)}
className="px-3 py-1 bg-blue-600 text-white rounded text-xs hover:bg-blue-700 disabled:opacity-50"
>
{pollingIds.includes(tracker.id) ? "Polling..." : "Poll now"}
</button>
<Link
to={`/projects/${projectId}/trackers/${tracker.id}/edit`}
className="px-3 py-1 bg-gray-200 text-gray-700 rounded text-xs hover:bg-gray-300"
>
Edit
</Link>
<button
type="button"
onClick={() => handleToggleEnabled(tracker)}
className="px-3 py-1 bg-gray-200 text-gray-700 rounded text-xs hover:bg-gray-300"
>
{tracker.enabled ? "Pause" : "Resume"}
</button>
<button
type="button"
onClick={() => setTrackerToRemove(tracker)}
className="px-3 py-1 bg-red-100 text-red-700 rounded text-xs hover:bg-red-200"
>
Remove
</button>
</div>
</div>
))}
<Link
to={`/projects/${projectId}/trackers/new`}
className="inline-block px-4 py-2 bg-blue-600 text-white rounded text-sm hover:bg-blue-700"
>
Add tracker
</Link>
<ConfirmModal
isOpen={trackerToRemove !== null}
onCancel={() => setTrackerToRemove(null)}
onConfirm={() => void handleConfirmRemove()}
/>
</div>
);
}