tubearr/src/frontend/src/api/hooks/useSystem.ts
jlightner e6711e91a5 feat: Added missing status badge, Library filter/re-download button, an…
- "src/frontend/src/components/StatusBadge.tsx"
- "src/frontend/src/pages/Library.tsx"
- "src/frontend/src/pages/System.tsx"
- "src/frontend/src/api/hooks/useLibrary.ts"
- "src/frontend/src/api/hooks/useSystem.ts"

GSD-Task: S06/T03
2026-04-04 06:39:17 +00:00

135 lines
4.1 KiB
TypeScript

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../client';
import type { HealthResponse, SystemStatusResponse, ApiKeyResponse, AppSettingsResponse, YtDlpStatusResponse, YtDlpUpdateResponse } from '@shared/types/api';
// ── Query Keys ──
export const systemKeys = {
status: ['system', 'status'] as const,
health: ['system', 'health'] as const,
apiKey: ['system', 'apikey'] as const,
appSettings: ['system', 'appSettings'] as const,
ytdlpStatus: ['system', 'ytdlpStatus'] as const,
missingScanStatus: ['system', 'missingScanStatus'] as const,
};
// ── Queries ──
/** Fetch system status (uptime, node version, memory, etc.). Auto-refreshes every 30s. */
export function useSystemStatus() {
return useQuery({
queryKey: systemKeys.status,
queryFn: () => apiClient.get<SystemStatusResponse>('/api/v1/system/status'),
refetchInterval: 30_000,
});
}
/** Fetch health check results. Auto-refreshes every 30s. */
export function useHealth() {
return useQuery({
queryKey: systemKeys.health,
queryFn: () => apiClient.get<HealthResponse>('/api/v1/health'),
refetchInterval: 30_000,
});
}
/** Fetch the current API key. Does not auto-refresh (key is stable). */
export function useApiKey() {
return useQuery({
queryKey: systemKeys.apiKey,
queryFn: () => apiClient.get<ApiKeyResponse>('/api/v1/system/apikey'),
staleTime: Infinity,
});
}
/** Regenerate the API key. Invalidates the apikey query cache on success. */
export function useRegenerateApiKey() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => apiClient.post<ApiKeyResponse>('/api/v1/system/apikey/regenerate'),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: systemKeys.apiKey });
},
});
}
/** Fetch app-wide settings (check interval, concurrent downloads). */
export function useAppSettings() {
return useQuery({
queryKey: systemKeys.appSettings,
queryFn: () => apiClient.get<AppSettingsResponse>('/api/v1/system/settings'),
});
}
/** Update app-wide settings. Accepts partial updates (only changed fields). */
export function useUpdateAppSettings() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: Partial<AppSettingsResponse>) =>
apiClient.put<AppSettingsResponse>('/api/v1/system/settings', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: systemKeys.appSettings });
},
});
}
/** Fetch yt-dlp version and last-updated timestamp. Auto-refreshes every 60s. */
export function useYtDlpStatus() {
return useQuery({
queryKey: systemKeys.ytdlpStatus,
queryFn: () => apiClient.get<YtDlpStatusResponse>('/api/v1/system/ytdlp/status'),
refetchInterval: 60_000,
});
}
/** Trigger a yt-dlp update check. Invalidates the status query on success. */
export function useUpdateYtDlp() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => apiClient.post<YtDlpUpdateResponse>('/api/v1/system/ytdlp/update'),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: systemKeys.ytdlpStatus });
},
});
}
// ── Missing File Scan ──
interface ScanResult {
checked: number;
missing: number;
duration: number;
}
interface MissingScanStatusResponse {
lastRun: string;
result: ScanResult;
}
interface MissingScanTriggerResponse {
success: boolean;
data: ScanResult;
}
/** Fetch last missing file scan status. Does not auto-refresh. */
export function useMissingScanStatus() {
return useQuery({
queryKey: systemKeys.missingScanStatus,
queryFn: () =>
apiClient.get<{ success: boolean; data: MissingScanStatusResponse | null }>(
'/api/v1/system/missing-scan/status',
),
});
}
/** Trigger an on-demand missing file scan. Invalidates scan status on success. */
export function useTriggerMissingScan() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () =>
apiClient.post<MissingScanTriggerResponse>('/api/v1/system/missing-scan'),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: systemKeys.missingScanStatus });
},
});
}