add paths page to check volume accessibility after scan
All checks were successful
Build and Push Docker Image / build (push) Successful in 38s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 10:31:05 +01:00
parent 923f9afafc
commit da668b2d36
5 changed files with 119 additions and 0 deletions

35
server/api/paths.ts Normal file
View File

@@ -0,0 +1,35 @@
import { existsSync } from 'node:fs';
import { Hono } from 'hono';
import { getDb } from '../db/index';
const app = new Hono();
interface PathInfo {
prefix: string;
itemCount: number;
accessible: boolean;
}
app.get('/', (c) => {
const db = getDb();
const rows = db
.query<{ prefix: string; count: number }, []>(
`SELECT substr(file_path, 1, instr(substr(file_path, 2), '/') + 1) AS prefix,
COUNT(*) AS count
FROM media_items
WHERE file_path IS NOT NULL AND file_path != ''
GROUP BY prefix
ORDER BY prefix`,
)
.all();
const paths: PathInfo[] = rows.map((r: { prefix: string; count: number }) => ({
prefix: r.prefix,
itemCount: r.count,
accessible: existsSync(r.prefix),
}));
return c.json({ paths });
});
export default app;

View File

@@ -11,6 +11,7 @@ import executeRoutes from './api/execute';
import nodesRoutes from './api/nodes';
import subtitlesRoutes from './api/subtitles';
import dashboardRoutes from './api/dashboard';
import pathsRoutes from './api/paths';
const app = new Hono();
@@ -41,6 +42,7 @@ app.route('/api/review', reviewRoutes);
app.route('/api/execute', executeRoutes);
app.route('/api/subtitles', subtitlesRoutes);
app.route('/api/nodes', nodesRoutes);
app.route('/api/paths', pathsRoutes);
// ─── Static assets (production: serve Vite build) ────────────────────────────

View File

@@ -0,0 +1,75 @@
import { useEffect, useState } from 'react';
import { api } from '~/shared/lib/api';
import { Badge } from '~/shared/components/ui/badge';
import { Button } from '~/shared/components/ui/button';
interface PathInfo {
prefix: string;
itemCount: number;
accessible: boolean;
}
export function PathsPage() {
const [paths, setPaths] = useState<PathInfo[]>([]);
const [loading, setLoading] = useState(true);
const load = () => {
setLoading(true);
api.get<{ paths: PathInfo[] }>('/api/paths')
.then((d) => setPaths(d.paths))
.finally(() => setLoading(false));
};
useEffect(() => { load(); }, []);
return (
<div>
<div className="flex items-center gap-3 mb-4">
<h1 className="text-lg font-bold">Paths</h1>
<Button variant="secondary" size="sm" onClick={load} disabled={loading}>
{loading ? 'Checking…' : 'Refresh'}
</Button>
</div>
{paths.length === 0 && !loading && (
<p className="text-gray-500">No media items scanned yet. Run a scan first.</p>
)}
{paths.length > 0 && (
<>
<table className="w-full text-left border-collapse">
<thead>
<tr className="border-b border-gray-200 text-xs text-gray-500 uppercase tracking-wide">
<th className="py-2 pr-4">Path</th>
<th className="py-2 pr-4 text-right">Items</th>
<th className="py-2">Status</th>
</tr>
</thead>
<tbody>
{paths.map((p) => (
<tr key={p.prefix} className="border-b border-gray-100">
<td className="py-2 pr-4 font-mono text-sm">{p.prefix}</td>
<td className="py-2 pr-4 text-right tabular-nums">{p.itemCount}</td>
<td className="py-2">
{p.accessible ? (
<Badge variant="keep">Accessible</Badge>
) : (
<Badge variant="error">Not mounted</Badge>
)}
</td>
</tr>
))}
</tbody>
</table>
{paths.some((p) => !p.accessible) && (
<p className="mt-4 text-xs text-gray-500">
Paths marked "Not mounted" are not reachable from the container.
Add a Docker volume mount for the missing path, or configure a path mapping in Settings to translate the Jellyfin path to a local path.
</p>
)}
</>
)}
</div>
);
}

View File

@@ -50,6 +50,7 @@ function RootLayout() {
<VersionBadge />
<div className="flex flex-wrap items-center gap-0.5">
<NavLink to="/scan">Scan</NavLink>
<NavLink to="/paths">Paths</NavLink>
<NavLink to="/review/audio">Audio</NavLink>
<NavLink to="/review/subtitles">Subs</NavLink>
<NavLink to="/execute">Execute</NavLink>

6
src/routes/paths.tsx Normal file
View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router';
import { PathsPage } from '~/features/paths/PathsPage';
export const Route = createFileRoute('/paths')({
component: PathsPage,
});