- remove nodes table, ssh service, nodes api, NodesPage route - execute.ts: local-only spawn, atomic CAS job claim via UPDATE status - wrap job done + subtitle_files insert + review_plans status in db transaction - stream ffmpeg output per line with 500ms throttled flush - bump version to 2026.04.13
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
import { createRootRoute, Link, Outlet } from '@tanstack/react-router';
|
|
import { useEffect, useState } from 'react';
|
|
import { cn } from '~/shared/lib/utils';
|
|
import { api } from '~/shared/lib/api';
|
|
|
|
declare const __APP_VERSION__: string;
|
|
|
|
export const Route = createRootRoute({
|
|
component: RootLayout,
|
|
});
|
|
|
|
function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
|
|
return (
|
|
<Link
|
|
to={to}
|
|
className={cn('px-2.5 py-1 rounded text-[0.85rem] no-underline transition-colors text-gray-500 hover:bg-gray-100 hover:text-gray-900')}
|
|
activeProps={{ className: 'bg-gray-100 text-gray-900 font-medium' }}
|
|
activeOptions={{ exact: true }}
|
|
>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
function VersionBadge() {
|
|
const [serverVersion, setServerVersion] = useState<string | null>(null);
|
|
useEffect(() => { api.get<{ version: string }>('/api/version').then((d) => setServerVersion(d.version)).catch(() => {}); }, []);
|
|
const buildVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : null;
|
|
const mismatch = buildVersion && serverVersion && buildVersion !== serverVersion;
|
|
return (
|
|
<span className="text-[0.65rem] text-gray-400 font-mono ml-1" title={mismatch ? `Frontend: ${buildVersion}, Server: ${serverVersion}` : undefined}>
|
|
v{serverVersion ?? buildVersion ?? '?'}
|
|
{mismatch && <span className="text-amber-500 ml-1" title="Frontend and server versions differ — rebuild or refresh">⚠</span>}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
function RootLayout() {
|
|
const isDev = import.meta.env.DEV;
|
|
return (
|
|
<div className="min-h-screen bg-white text-gray-900 text-sm">
|
|
{isDev && (
|
|
<div className="bg-amber-100 border-b border-amber-300 text-amber-800 text-xs font-semibold text-center py-1 px-4">
|
|
⚠ DEV MODE — fresh test database, 50 movies + 10 series per scan
|
|
</div>
|
|
)}
|
|
<nav className="flex flex-wrap items-center gap-0.5 px-3 sm:px-5 py-2 sm:py-0 sm:h-12 bg-white border-b border-gray-200 sticky top-0 z-50">
|
|
<Link to="/" className="font-bold text-[0.95rem] mr-3 sm:mr-5 no-underline text-gray-900">
|
|
🎬 netfelix
|
|
</Link>
|
|
<VersionBadge />
|
|
<div className="flex flex-wrap items-center gap-0.5">
|
|
<NavLink to="/scan">Scan</NavLink>
|
|
<NavLink to="/pipeline">Pipeline</NavLink>
|
|
<NavLink to="/review/subtitles">Subtitles</NavLink>
|
|
</div>
|
|
<div className="flex-1" />
|
|
<div className="flex items-center gap-0.5">
|
|
<NavLink to="/settings">Settings</NavLink>
|
|
</div>
|
|
</nav>
|
|
<main className="max-w-[1600px] mx-auto px-3 sm:px-5 pt-4 pb-12">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
import type React from 'react';
|