- "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
135 lines
4.1 KiB
TypeScript
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 });
|
|
},
|
|
});
|
|
}
|