feat(services): select Jellyfin via jellyfin_service_id on the frontend

Slice 4b frontend half. Jellyfin-touching pages now select a Jellyfin service
instance instead of a machine.

- api/client.ts: Jellyfin-backed calls (counts/libraries/activity/users, media
  status/build/stop/force-stop, queryMedia) send jellyfin_service_id.
- hooks/useDashboard, useUsers, useMedia: selector param renamed to
  jellyfinServiceId.
- pages/Media + Applications: list jellyfin service instances and persist
  jellyfin_service_id in the URL.
- Dashboard (widgets) and Users (default instance) need no selector change.
- Update Applications + Media tests for the new hook/param.

Files/SSH transport keeps machine_id. Verification: frontend lint 0 errors,
build success, 70 tests; backend ruff clean, 222 tests.
This commit is contained in:
Developer
2026-06-23 12:10:27 +00:00
parent a13f560df2
commit cbb703341e
9 changed files with 113 additions and 89 deletions
+9 -9
View File
@@ -9,26 +9,26 @@ import {
} from "../api/client";
import type { DashboardShortcutInput } from "../types";
export function useCounts(machineId?: string) {
export function useCounts(jellyfinServiceId?: string) {
return useQuery({
queryKey: ["dashboard", "counts", machineId ?? "default"],
queryFn: () => fetchCounts(machineId),
queryKey: ["dashboard", "counts", jellyfinServiceId ?? "default"],
queryFn: () => fetchCounts(jellyfinServiceId),
staleTime: 5 * 60 * 1000,
});
}
export function useLibraries(machineId?: string) {
export function useLibraries(jellyfinServiceId?: string) {
return useQuery({
queryKey: ["dashboard", "libraries", machineId ?? "default"],
queryFn: () => fetchLibraries(machineId),
queryKey: ["dashboard", "libraries", jellyfinServiceId ?? "default"],
queryFn: () => fetchLibraries(jellyfinServiceId),
staleTime: 5 * 60 * 1000,
});
}
export function useActivity(machineId?: string) {
export function useActivity(jellyfinServiceId?: string) {
return useQuery({
queryKey: ["dashboard", "activity", machineId ?? "default"],
queryFn: () => fetchActivity(machineId),
queryKey: ["dashboard", "activity", jellyfinServiceId ?? "default"],
queryFn: () => fetchActivity(jellyfinServiceId),
refetchInterval: 15_000,
});
}
+10 -10
View File
@@ -7,10 +7,10 @@ import {
forceStopMediaIndexBuild,
} from "../api/client";
export function useMediaStatus(machineId?: string) {
export function useMediaStatus(jellyfinServiceId?: string) {
return useQuery({
queryKey: ["media", "status", machineId ?? "default"],
queryFn: () => fetchMediaStatus(machineId),
queryKey: ["media", "status", jellyfinServiceId ?? "default"],
queryFn: () => fetchMediaStatus(jellyfinServiceId),
staleTime: 5_000,
refetchInterval: (query) =>
query.state.data?.build_running ? 1000 : false,
@@ -27,7 +27,7 @@ export function useMediaQuery(params: {
sort_order?: string;
limit?: number;
offset?: number;
machineId?: string;
jellyfinServiceId?: string;
enabled?: boolean;
}) {
const { enabled = true, ...queryParams } = params;
@@ -44,30 +44,30 @@ function invalidateMedia(queryClient: ReturnType<typeof useQueryClient>) {
queryClient.invalidateQueries({ queryKey: ["media"] });
}
export function useBuildIndex(machineId?: string) {
export function useBuildIndex(jellyfinServiceId?: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => buildMediaIndex(machineId),
mutationFn: () => buildMediaIndex(jellyfinServiceId),
onSuccess: () => {
invalidateMedia(queryClient);
},
});
}
export function useStopBuildIndex(machineId?: string) {
export function useStopBuildIndex(jellyfinServiceId?: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => stopMediaIndexBuild(machineId),
mutationFn: () => stopMediaIndexBuild(jellyfinServiceId),
onSuccess: () => {
invalidateMedia(queryClient);
},
});
}
export function useForceStopBuildIndex(machineId?: string) {
export function useForceStopBuildIndex(jellyfinServiceId?: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => forceStopMediaIndexBuild(machineId),
mutationFn: () => forceStopMediaIndexBuild(jellyfinServiceId),
onSuccess: () => {
invalidateMedia(queryClient);
},
+3 -3
View File
@@ -2,10 +2,10 @@ import { useQuery } from "@tanstack/react-query";
import { fetchUsers } from "../api/client";
import type { UserDirectoryResponse } from "../types";
export function useUsers(machineId?: string) {
export function useUsers(jellyfinServiceId?: string) {
return useQuery<UserDirectoryResponse>({
queryKey: ["users", machineId ?? "default"],
queryFn: () => fetchUsers(machineId),
queryKey: ["users", jellyfinServiceId ?? "default"],
queryFn: () => fetchUsers(jellyfinServiceId),
staleTime: 30_000,
});
}