remove path mappings from settings UI, fix clear-scan blocking by deleting children first
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m6s
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m6s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "netfelix-audio-fix",
|
||||
"version": "2026.03.05.3",
|
||||
"version": "2026.03.05.4",
|
||||
"scripts": {
|
||||
"dev:server": "NODE_ENV=development bun --hot server/index.tsx",
|
||||
"dev:client": "vite",
|
||||
|
||||
@@ -97,8 +97,16 @@ app.post('/path-mappings', async (c) => {
|
||||
|
||||
app.post('/clear-scan', (c) => {
|
||||
const db = getDb();
|
||||
db.prepare('DELETE FROM media_items').run();
|
||||
db.prepare("UPDATE config SET value = '0' WHERE key = 'scan_running'").run();
|
||||
// Delete children first to avoid slow cascade deletes
|
||||
db.transaction(() => {
|
||||
db.prepare('DELETE FROM stream_decisions').run();
|
||||
db.prepare('DELETE FROM jobs').run();
|
||||
db.prepare('DELETE FROM subtitle_files').run();
|
||||
db.prepare('DELETE FROM review_plans').run();
|
||||
db.prepare('DELETE FROM media_streams').run();
|
||||
db.prepare('DELETE FROM media_items').run();
|
||||
db.prepare("UPDATE config SET value = '0' WHERE key = 'scan_running'").run();
|
||||
})();
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -171,22 +171,18 @@ function ConnSection({
|
||||
export function SetupPage() {
|
||||
const [data, setData] = useState<SetupData | null>(null);
|
||||
const [clearStatus, setClearStatus] = useState('');
|
||||
const [mappings, setMappings] = useState<[string, string][]>([['', '']]);
|
||||
const [mappingSaved, setMappingSaved] = useState('');
|
||||
const [mappingsLoaded, setMappingsLoaded] = useState(false);
|
||||
const [subLangs, setSubLangs] = useState<string[]>([]);
|
||||
const [subSaved, setSubSaved] = useState('');
|
||||
const [audLangs, setAudLangs] = useState<string[]>([]);
|
||||
const [audSaved, setAudSaved] = useState('');
|
||||
const [langsLoaded, setLangsLoaded] = useState(false);
|
||||
|
||||
const load = () => api.get<SetupData>('/api/setup').then((d) => {
|
||||
setData(d);
|
||||
if (!mappingsLoaded) {
|
||||
const pm: [string, string][] = JSON.parse(d.config.path_mappings ?? '[]');
|
||||
setMappings(pm.length > 0 ? pm : [['', '']]);
|
||||
if (!langsLoaded) {
|
||||
setSubLangs(JSON.parse(d.config.subtitle_languages ?? '["eng","deu","spa"]'));
|
||||
setAudLangs(JSON.parse(d.config.audio_languages ?? '[]'));
|
||||
setMappingsLoaded(true);
|
||||
setLangsLoaded(true);
|
||||
}
|
||||
});
|
||||
useEffect(() => { load(); }, []);
|
||||
@@ -202,13 +198,6 @@ export function SetupPage() {
|
||||
const saveSonarr = (url: string, apiKey: string) =>
|
||||
api.post('/api/setup/sonarr', { url, api_key: apiKey });
|
||||
|
||||
const saveMappings = async () => {
|
||||
const valid = mappings.filter(([f, t]) => f.trim() && t.trim());
|
||||
await api.post('/api/setup/path-mappings', { mappings: valid });
|
||||
setMappingSaved('Saved.');
|
||||
setTimeout(() => setMappingSaved(''), 2000);
|
||||
};
|
||||
|
||||
const saveSubtitleLangs = async () => {
|
||||
await api.post('/api/setup/subtitle-languages', { langs: subLangs });
|
||||
setSubSaved('Saved.');
|
||||
@@ -258,47 +247,6 @@ export function SetupPage() {
|
||||
onSave={saveSonarr}
|
||||
/>
|
||||
|
||||
{/* Path mappings */}
|
||||
<SectionCard
|
||||
title={
|
||||
<span className="flex items-center gap-2">
|
||||
Path Mappings
|
||||
<EnvBadge envVar="PATH_MAPPINGS" locked={locked.has('path_mappings')} />
|
||||
</span>
|
||||
}
|
||||
subtitle="Translate Jellyfin library paths to container mount paths. E.g. Jellyfin uses /tv/ but the container mounts at /series/."
|
||||
>
|
||||
{mappings.map(([from, to], i) => (
|
||||
<div key={i} className="flex items-center gap-2 mb-2">
|
||||
<Input
|
||||
value={from}
|
||||
onChange={(e) => { const m = [...mappings]; m[i] = [e.target.value, m[i][1]]; setMappings(m); }}
|
||||
placeholder="/tv/"
|
||||
className="max-w-[12rem] text-sm"
|
||||
disabled={locked.has('path_mappings')}
|
||||
/>
|
||||
<span className="text-gray-400">→</span>
|
||||
<Input
|
||||
value={to}
|
||||
onChange={(e) => { const m = [...mappings]; m[i] = [m[i][0], e.target.value]; setMappings(m); }}
|
||||
placeholder="/series/"
|
||||
className="max-w-[12rem] text-sm"
|
||||
disabled={locked.has('path_mappings')}
|
||||
/>
|
||||
{mappings.length > 1 && (
|
||||
<button type="button" onClick={() => setMappings(mappings.filter((_, j) => j !== i))} className="text-red-400 hover:text-red-600 text-sm border-0 bg-transparent cursor-pointer">✕</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<button type="button" onClick={() => setMappings([...mappings, ['', '']])} className="text-blue-600 text-sm border-0 bg-transparent cursor-pointer hover:underline" disabled={locked.has('path_mappings')}>+ Add mapping</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-3">
|
||||
<Button onClick={saveMappings} disabled={locked.has('path_mappings')}>Save</Button>
|
||||
{mappingSaved && <span className="text-green-700 text-sm">{mappingSaved}</span>}
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
{/* Audio languages */}
|
||||
<SectionCard
|
||||
title={
|
||||
|
||||
Reference in New Issue
Block a user