fixes and improvements

This commit is contained in:
2026-05-06 16:33:02 +02:00
parent d8d80867ce
commit 5277f21577
20 changed files with 577 additions and 222 deletions
+53 -4
View File
@@ -1,3 +1,4 @@
import { useMemo } from "react";
import { useSearchParams } from "react-router-dom";
import { DataGrid } from "@mui/x-data-grid";
import type { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";
@@ -25,6 +26,7 @@ import {
useRunJob,
} from "../hooks/useFiles";
import { usePersistentState } from "../hooks/usePersistentState";
import { useMonitoringSettings } from "../hooks/useSettings";
interface DisplayRow {
id: string;
@@ -556,9 +558,22 @@ function FfprobeDetails({ path, data }: { path: string; data: FfprobeData }) {
}
export function FileBrowser() {
const [searchParams] = useSearchParams();
const [searchParams, setSearchParams] = useSearchParams();
const isMobile = useMediaQuery("(max-width: 900px)");
const { data: machines } = useMonitoringSettings();
const fileMachines = useMemo(
() =>
(machines ?? []).filter(
(machine) =>
machine.enabled &&
(machine.services.includes("files") ||
machine.services.includes("monitoring")),
),
[machines],
);
const initialRequestedPath = searchParams.get("path");
const initialMachineId =
searchParams.get("machine_id") || fileMachines[0]?.id || "";
const [browserState, setBrowserState] = usePersistentState<FileBrowserState>(
FILE_BROWSER_STATE_KEY,
() => {
@@ -580,6 +595,7 @@ export function FileBrowser() {
},
);
const { currentDir, pathInput, selectedPath, selectedJob } = browserState;
const selectedMachineId = searchParams.get("machine_id") || initialMachineId;
const updateBrowserState = (patch: Partial<FileBrowserState>) =>
setBrowserState((current) => ({ ...current, ...patch }));
@@ -588,7 +604,7 @@ export function FileBrowser() {
isLoading,
error,
refetch,
} = useDirectoryListing(currentDir);
} = useDirectoryListing(currentDir, selectedMachineId || undefined);
const {
data: ffprobeData,
isLoading: ffprobeLoading,
@@ -596,9 +612,10 @@ export function FileBrowser() {
} = useFfprobe(
selectedPath ?? "",
!!selectedPath && isVideoFile(selectedPath),
selectedMachineId || undefined,
);
const { data: templates } = useJobTemplates();
const runJob = useRunJob();
const runJob = useRunJob(selectedMachineId || undefined);
const navigate = (path: string) => {
updateBrowserState({
@@ -608,6 +625,18 @@ export function FileBrowser() {
});
};
const setMachine = (machineId: string) => {
setSearchParams(
(current) => {
const next = new URLSearchParams(current);
if (machineId) next.set("machine_id", machineId);
else next.delete("machine_id");
return next;
},
{ replace: true },
);
};
const handlePathSubmit = (e: React.KeyboardEvent) => {
if (e.key === "Enter") navigate(pathInput || "/");
};
@@ -657,7 +686,27 @@ export function FileBrowser() {
return (
<Stack spacing={2}>
<Typography variant="h5">File Browser</Typography>
<Stack
direction="row"
spacing={1}
sx={{ alignItems: "center", flexWrap: "wrap" }}
>
<Typography variant="h5">File Browser</Typography>
<FormControl size="small" sx={{ minWidth: 220 }}>
<InputLabel>Machine</InputLabel>
<Select
label="Machine"
value={selectedMachineId}
onChange={(e) => setMachine(String(e.target.value))}
>
{fileMachines.map((machine) => (
<MenuItem key={machine.id} value={machine.id}>
{machine.name} · {machine.mode}
</MenuItem>
))}
</Select>
</FormControl>
</Stack>
<Stack direction={isMobile ? "column" : "row"} spacing={1}>
<TextField
+69 -7
View File
@@ -1,5 +1,5 @@
import { useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useEffect, useMemo } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { DataGrid } from "@mui/x-data-grid";
import type { GridColDef } from "@mui/x-data-grid";
import {
@@ -28,6 +28,8 @@ import {
} from "../hooks/useMedia";
import { usePersistentState } from "../hooks/usePersistentState";
import type { MediaItem } from "../types";
import { useMonitoringSettings } from "../hooks/useSettings";
import { useCounts, useLibraries } from "../hooks/useDashboard";
function formatDuration(seconds: number | null | undefined): string {
if (seconds == null || Number.isNaN(seconds)) return "-";
@@ -64,11 +66,26 @@ function defaultMediaTabState(): MediaTabState {
export function Media() {
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const isMobile = useMediaQuery("(max-width: 900px)");
const { data: status } = useMediaStatus();
const buildIndex = useBuildIndex();
const stopBuildIndex = useStopBuildIndex();
const forceStopBuildIndex = useForceStopBuildIndex();
const { data: machines } = useMonitoringSettings();
const jellyfinMachines = useMemo(
() =>
(machines ?? []).filter(
(machine) => machine.enabled && machine.services.includes("jellyfin"),
),
[machines],
);
const selectedMachineId =
searchParams.get("machine_id") || jellyfinMachines[0]?.id || "";
const { data: counts } = useCounts(selectedMachineId || undefined);
const { data: libraries } = useLibraries(selectedMachineId || undefined);
const { data: status } = useMediaStatus(selectedMachineId || undefined);
const buildIndex = useBuildIndex(selectedMachineId || undefined);
const stopBuildIndex = useStopBuildIndex(selectedMachineId || undefined);
const forceStopBuildIndex = useForceStopBuildIndex(
selectedMachineId || undefined,
);
const [mediaState, setMediaState] = usePersistentState<MediaTabState>(
MEDIA_TAB_STATE_KEY,
@@ -79,6 +96,19 @@ export function Media() {
setMediaState((current) => ({ ...current, ...patch }));
const limit = 100;
useEffect(() => {
if (!searchParams.get("machine_id") && selectedMachineId) {
setSearchParams(
(current) => {
const next = new URLSearchParams(current);
next.set("machine_id", selectedMachineId);
return next;
},
{ replace: true },
);
}
}, [searchParams, selectedMachineId, setSearchParams]);
const { data: queryResult, isLoading } = useMediaDataQuery({
types,
search,
@@ -87,6 +117,7 @@ export function Media() {
sort_order: sortOrder,
limit,
offset,
machineId: selectedMachineId || undefined,
enabled: status?.exists ?? false,
});
@@ -155,7 +186,30 @@ export function Media() {
spacing={1.5}
sx={{ alignItems: "center", flexWrap: "wrap" }}
>
<Typography variant="h5">Media</Typography>
<Typography variant="h5">Jellyfin</Typography>
<FormControl size="small" sx={{ minWidth: 220 }}>
<InputLabel>Machine</InputLabel>
<Select
label="Machine"
value={selectedMachineId}
onChange={(e) =>
setSearchParams(
(current) => {
const next = new URLSearchParams(current);
next.set("machine_id", String(e.target.value));
return next;
},
{ replace: true },
)
}
>
{jellyfinMachines.map((machine) => (
<MenuItem key={machine.id} value={machine.id}>
{machine.name}
</MenuItem>
))}
</Select>
</FormControl>
{status?.exists ? (
<Typography variant="body2" color="text.secondary">
Index: {status.item_count.toLocaleString()} items
@@ -168,6 +222,14 @@ export function Media() {
No index built yet.
</Alert>
)}
{counts && (
<Typography variant="body2" color="text.secondary">
Library stats: {counts.movies.toLocaleString()} movies ·{" "}
{counts.series.toLocaleString()} series ·{" "}
{counts.episodes.toLocaleString()} episodes ·{" "}
{(libraries?.length ?? 0).toLocaleString()} libraries
</Typography>
)}
<Button
variant="outlined"
onClick={() => buildIndex.mutate()}