diff --git a/src/frontend/src/api/hooks/useSystem.ts b/src/frontend/src/api/hooks/useSystem.ts index eb1b01b..eb0ede2 100644 --- a/src/frontend/src/api/hooks/useSystem.ts +++ b/src/frontend/src/api/hooks/useSystem.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { apiClient } from '../client'; -import type { HealthResponse, SystemStatusResponse, ApiKeyResponse, AppSettingsResponse } from '@shared/types/api'; +import type { HealthResponse, SystemStatusResponse, ApiKeyResponse, AppSettingsResponse, YtDlpStatusResponse, YtDlpUpdateResponse } from '@shared/types/api'; // ── Query Keys ── @@ -9,6 +9,7 @@ export const systemKeys = { health: ['system', 'health'] as const, apiKey: ['system', 'apikey'] as const, appSettings: ['system', 'appSettings'] as const, + ytdlpStatus: ['system', 'ytdlpStatus'] as const, }; // ── Queries ── @@ -70,3 +71,23 @@ export function useUpdateAppSettings() { }, }); } + +/** Fetch yt-dlp version and last-updated timestamp. Auto-refreshes every 60s. */ +export function useYtDlpStatus() { + return useQuery({ + queryKey: systemKeys.ytdlpStatus, + queryFn: () => apiClient.get('/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('/api/v1/system/ytdlp/update'), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: systemKeys.ytdlpStatus }); + }, + }); +} diff --git a/src/frontend/src/pages/System.tsx b/src/frontend/src/pages/System.tsx index 4ae8e27..9a3f695 100644 --- a/src/frontend/src/pages/System.tsx +++ b/src/frontend/src/pages/System.tsx @@ -1,9 +1,11 @@ -import { RefreshCw, Server, Activity, Cpu, HardDrive } from 'lucide-react'; -import { useSystemStatus, useHealth } from '../api/hooks/useSystem'; +import { RefreshCw, Server, Activity, Cpu, HardDrive, Download, CheckCircle, AlertCircle, Loader2 } from 'lucide-react'; +import { useSystemStatus, useHealth, useYtDlpStatus, useUpdateYtDlp } from '../api/hooks/useSystem'; import { HealthStatus } from '../components/HealthStatus'; import { SkeletonSystem } from '../components/Skeleton'; import { formatBytes } from '../utils/format'; +import { useState } from 'react'; + // ── Helpers ── function formatUptime(seconds: number): string { @@ -23,6 +25,9 @@ function formatUptime(seconds: number): string { export function SystemPage() { const { data: health, isLoading: healthLoading, error: healthError, refetch: refetchHealth } = useHealth(); const { data: status, isLoading: statusLoading, error: statusError, refetch: refetchStatus } = useSystemStatus(); + const { data: ytdlpStatus, isLoading: ytdlpLoading, error: ytdlpError } = useYtDlpStatus(); + const updateYtDlp = useUpdateYtDlp(); + const [updateMessage, setUpdateMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const isLoading = healthLoading || statusLoading; @@ -84,6 +89,105 @@ export function SystemPage() { ) : null} + {/* ── yt-dlp section ── */} +
+
+

+ + yt-dlp +

+
+ +
+ {ytdlpLoading ? ( +

Loading yt-dlp status…

+ ) : ytdlpError ? ( +

+ Failed to load yt-dlp status: {ytdlpError instanceof Error ? ytdlpError.message : 'Unknown error'} +

+ ) : ( + <> +
+
+ Version +

+ {ytdlpStatus?.version ?? 'Unknown'} +

+
+
+ Last Updated +

+ {ytdlpStatus?.lastUpdated ? new Date(ytdlpStatus.lastUpdated).toLocaleString() : 'Never'} +

+
+
+ +
+ + + {updateMessage && ( + + {updateMessage.type === 'success' ? : } + {updateMessage.text} + + )} +
+ + )} +
+ +

+ Auto-refreshes every 60 seconds. +

+
+ {/* ── System Status section ── */}