113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
|
|
import { Link } from "react-router-dom";
|
||
|
|
import { manualPoll, updateTracker, removeTracker } from "../../lib/api";
|
||
|
|
import type { WatchedTracker } from "../../lib/types";
|
||
|
|
|
||
|
|
interface Props {
|
||
|
|
trackers: WatchedTracker[];
|
||
|
|
projectId: string;
|
||
|
|
onRefresh: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function TrackerList({ trackers, projectId, onRefresh }: Props) {
|
||
|
|
async function handlePollNow(tracker: WatchedTracker) {
|
||
|
|
try {
|
||
|
|
await manualPoll(tracker.id);
|
||
|
|
onRefresh();
|
||
|
|
} catch (err) {
|
||
|
|
console.error("Poll failed:", err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function handleToggleEnabled(tracker: WatchedTracker) {
|
||
|
|
try {
|
||
|
|
await updateTracker(
|
||
|
|
tracker.id,
|
||
|
|
tracker.polling_interval,
|
||
|
|
tracker.agent_config,
|
||
|
|
tracker.filters,
|
||
|
|
!tracker.enabled
|
||
|
|
);
|
||
|
|
onRefresh();
|
||
|
|
} catch (err) {
|
||
|
|
console.error("Update failed:", err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function handleRemove(tracker: WatchedTracker) {
|
||
|
|
if (!window.confirm(`Remove tracker "${tracker.tracker_label}"?`)) return;
|
||
|
|
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)}
|
||
|
|
className="px-3 py-1 bg-blue-600 text-white rounded text-xs hover:bg-blue-700"
|
||
|
|
>
|
||
|
|
Poll now
|
||
|
|
</button>
|
||
|
|
<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={() => handleRemove(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>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|