Add FastAPI backend and React frontend subprojects
Backend: - FastAPI app with 17 REST endpoints covering dashboard, monitoring, media index, file browser, and jobs - Reuses existing clients/domain/services unchanged - pydantic-settings config, dependency injection, CORS setup - Auto-generated OpenAPI docs at /docs Frontend: - Vite + React + TypeScript SPA - @tanstack/react-query for data fetching with polling - ag-grid-react for media table and file browser - recharts for monitoring charts - Tailwind CSS styling - 4 pages: Dashboard, Monitoring, Media, File Browser - Typed API client matching all backend endpoints Also: - docs/MIGRATION_PLAN.md with full architecture plan - Updated .gitignore for both subprojects - Streamlit app preserved for now (can coexist)
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchCounts, fetchLibraries, fetchNowPlaying } from "../api/client";
|
||||
|
||||
export function useCounts() {
|
||||
return useQuery({
|
||||
queryKey: ["dashboard", "counts"],
|
||||
queryFn: fetchCounts,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLibraries() {
|
||||
return useQuery({
|
||||
queryKey: ["dashboard", "libraries"],
|
||||
queryFn: fetchLibraries,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useNowPlaying() {
|
||||
return useQuery({
|
||||
queryKey: ["dashboard", "now-playing"],
|
||||
queryFn: fetchNowPlaying,
|
||||
refetchInterval: 15_000,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import {
|
||||
fetchDirectoryListing,
|
||||
fetchFfprobe,
|
||||
fetchStat,
|
||||
fetchJobTemplates,
|
||||
runJob,
|
||||
} from "../api/client";
|
||||
|
||||
export function useDirectoryListing(path: string) {
|
||||
return useQuery({
|
||||
queryKey: ["files", "list", path],
|
||||
queryFn: () => fetchDirectoryListing(path),
|
||||
enabled: !!path,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useFfprobe(path: string, enabled = false) {
|
||||
return useQuery({
|
||||
queryKey: ["files", "ffprobe", path],
|
||||
queryFn: () => fetchFfprobe(path),
|
||||
enabled: enabled && !!path,
|
||||
staleTime: 5 * 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useStat(path: string, enabled = false) {
|
||||
return useQuery({
|
||||
queryKey: ["files", "stat", path],
|
||||
queryFn: () => fetchStat(path),
|
||||
enabled: enabled && !!path,
|
||||
});
|
||||
}
|
||||
|
||||
export function useJobTemplates() {
|
||||
return useQuery({
|
||||
queryKey: ["jobs", "templates"],
|
||||
queryFn: fetchJobTemplates,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRunJob() {
|
||||
return useMutation({
|
||||
mutationFn: ({ jobKey, path }: { jobKey: string; path: string }) =>
|
||||
runJob(jobKey, path),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { fetchMediaStatus, buildMediaIndex, queryMedia } from "../api/client";
|
||||
|
||||
export function useMediaStatus() {
|
||||
return useQuery({
|
||||
queryKey: ["media", "status"],
|
||||
queryFn: fetchMediaStatus,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useMediaQuery(params: {
|
||||
libraries?: string;
|
||||
types?: string;
|
||||
search?: string;
|
||||
hdr_filter?: string;
|
||||
sort_key?: string;
|
||||
sort_order?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
enabled?: boolean;
|
||||
}) {
|
||||
const { enabled = true, ...queryParams } = params;
|
||||
return useQuery({
|
||||
queryKey: ["media", "query", queryParams],
|
||||
queryFn: () => queryMedia(queryParams),
|
||||
enabled,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useBuildIndex() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: buildMediaIndex,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["media"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
fetchMonitoringStatus,
|
||||
fetchMonitoringMetrics,
|
||||
fetchDiskSpace,
|
||||
startCollector,
|
||||
stopCollector,
|
||||
restartCollector,
|
||||
} from "../api/client";
|
||||
|
||||
export function useMonitoringStatus() {
|
||||
return useQuery({
|
||||
queryKey: ["monitoring", "status"],
|
||||
queryFn: fetchMonitoringStatus,
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useMonitoringMetrics(lastSeconds = 3600) {
|
||||
return useQuery({
|
||||
queryKey: ["monitoring", "metrics", lastSeconds],
|
||||
queryFn: () => fetchMonitoringMetrics(lastSeconds),
|
||||
refetchInterval: 15_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDiskSpace() {
|
||||
return useQuery({
|
||||
queryKey: ["monitoring", "disk"],
|
||||
queryFn: fetchDiskSpace,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCollectorControls() {
|
||||
const queryClient = useQueryClient();
|
||||
const invalidate = () =>
|
||||
queryClient.invalidateQueries({ queryKey: ["monitoring"] });
|
||||
|
||||
const start = useMutation({
|
||||
mutationFn: startCollector,
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
const stop = useMutation({
|
||||
mutationFn: stopCollector,
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
const restart = useMutation({
|
||||
mutationFn: restartCollector,
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
|
||||
return { start, stop, restart };
|
||||
}
|
||||
Reference in New Issue
Block a user