mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 03:55:38 -04:00
Rebuild the background jobs card as a sortable table showing each job's schedule, last run time (live-updating via a new useTimeAgo hook), last duration, and a manual trigger button backed by a new POST /api/admin/jobs/trigger route. Extract StatusDot into a shared component. Add cronToHuman() to display schedule patterns as readable strings (e.g. "Every 6h", "Daily at 03:00"). Replace static formatDistanceToNow calls throughout the health section with a LiveTimeAgo component that refreshes every 30 seconds. Also swap a handful of section icons for better visual matches across settings cards.
69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import { atom, useAtom } from "jotai";
|
|
import { useCallback } from "react";
|
|
import { toast } from "sonner";
|
|
import type { WebhookConnection } from "@/app/(pages)/settings/_components/webhook-card";
|
|
import {
|
|
deleteWebhookConnection,
|
|
regenerateWebhookToken,
|
|
saveWebhookConnection,
|
|
} from "@/lib/actions/settings";
|
|
|
|
export const connectionsAtom = atom<WebhookConnection[]>([]);
|
|
|
|
function providerLabel(provider: "plex" | "jellyfin" | "emby") {
|
|
return provider === "plex"
|
|
? "Plex"
|
|
: provider === "emby"
|
|
? "Emby"
|
|
: "Jellyfin";
|
|
}
|
|
|
|
export function useConnectionActions(provider: "plex" | "jellyfin" | "emby") {
|
|
const [connections, setConnections] = useAtom(connectionsAtom);
|
|
const label = providerLabel(provider);
|
|
const connection = connections.find((c) => c.provider === provider) ?? null;
|
|
|
|
const handleConnect = useCallback(async () => {
|
|
try {
|
|
const result = await saveWebhookConnection(provider);
|
|
setConnections((prev) => [...prev, { ...result, recentEvents: [] }]);
|
|
toast.success(`${label} connected`);
|
|
} catch {
|
|
toast.error(`Failed to connect ${label}`);
|
|
}
|
|
}, [provider, label, setConnections]);
|
|
|
|
const handleDelete = useCallback(async () => {
|
|
const previous = connections;
|
|
setConnections((prev) => prev.filter((c) => c.provider !== provider));
|
|
try {
|
|
await deleteWebhookConnection(provider);
|
|
toast.success(`${label} disconnected`);
|
|
} catch {
|
|
setConnections(previous);
|
|
toast.error(`Failed to disconnect ${label}`);
|
|
}
|
|
}, [provider, label, connections, setConnections]);
|
|
|
|
const handleRegenerateToken = useCallback(async () => {
|
|
try {
|
|
const result = await regenerateWebhookToken(provider);
|
|
setConnections((prev) =>
|
|
prev.map((c) =>
|
|
c.provider === provider ? { ...c, token: result.token } : c,
|
|
),
|
|
);
|
|
toast.success(`${label} webhook URL regenerated`);
|
|
} catch {
|
|
toast.error(`Failed to regenerate ${label} URL`);
|
|
}
|
|
}, [provider, label, setConnections]);
|
|
|
|
return {
|
|
connection,
|
|
handleConnect,
|
|
handleDelete,
|
|
handleRegenerateToken,
|
|
};
|
|
}
|