diff --git a/backend/app/routers/libraries.py b/backend/app/routers/libraries.py
index d6c4a48..0393174 100644
--- a/backend/app/routers/libraries.py
+++ b/backend/app/routers/libraries.py
@@ -172,6 +172,8 @@ async def update_library(
{"id": f.get("id", str(uuid.uuid4())), "fullPath": f.get("fullPath", f.get("full_path", ""))}
for f in body.folders
]
+ if body.media_type is not None:
+ lib.media_type = body.media_type
if body.settings is not None:
lib.settings = {**(lib.settings or {}), **body.settings}
diff --git a/backend/app/schemas/library.py b/backend/app/schemas/library.py
index 2bdb0ab..cfcfaad 100644
--- a/backend/app/schemas/library.py
+++ b/backend/app/schemas/library.py
@@ -55,6 +55,7 @@ class LibraryCreate(BaseModel):
class LibraryUpdate(BaseModel):
name: str | None = None
folders: list[dict] | None = None
+ media_type: str | None = None
settings: dict | None = None
diff --git a/frontend/src/api/libraries.ts b/frontend/src/api/libraries.ts
index 84dd77a..8678cb1 100644
--- a/frontend/src/api/libraries.ts
+++ b/frontend/src/api/libraries.ts
@@ -17,5 +17,8 @@ export const scanLibrary = (libraryId: string) =>
export const createLibrary = (data: object) =>
api.post('/api/libraries', data).then((r) => r.data)
+export const updateLibrary = (id: string, data: object) =>
+ api.patch(`/api/libraries/${id}`, data).then((r) => r.data)
+
export const deleteLibrary = (id: string) =>
api.delete(`/api/libraries/${id}`).then((r) => r.data)
diff --git a/frontend/src/components/common/FileBrowser.tsx b/frontend/src/components/common/FileBrowser.tsx
index cddb9d4..222397e 100644
--- a/frontend/src/components/common/FileBrowser.tsx
+++ b/frontend/src/components/common/FileBrowser.tsx
@@ -49,6 +49,16 @@ export default function FileBrowser({ initialPath = '/', onSelect, onClose }: Pr
+ {/* Quick access */}
+
+ {['/audiofiles', '/data', '/media', '/'].map((p) => (
+
+ ))}
+
+
{/* Current path */}
{parent && (
diff --git a/frontend/src/components/common/Sidebar.tsx b/frontend/src/components/common/Sidebar.tsx
index dd33947..c694d58 100644
--- a/frontend/src/components/common/Sidebar.tsx
+++ b/frontend/src/components/common/Sidebar.tsx
@@ -6,6 +6,9 @@ import { useAuthStore } from '../../store/authStore'
export default function Sidebar() {
const { libraries, user, logout } = useAuthStore()
+ const bookLibraries = libraries.filter((l: any) => l.mediaType !== 'podcast')
+ const podcastLibraries = libraries.filter((l: any) => l.mediaType === 'podcast')
+
return (
- {/* Libraries */}
{/* Footer */}
diff --git a/frontend/src/pages/Admin.tsx b/frontend/src/pages/Admin.tsx
index 686533b..a4df4d0 100644
--- a/frontend/src/pages/Admin.tsx
+++ b/frontend/src/pages/Admin.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
-import { Users, Library, Settings, Trash2, Plus, RefreshCw, Loader2, Check, X, FolderOpen } from 'lucide-react'
+import { Users, Library, Settings, Trash2, Plus, RefreshCw, Loader2, Check, X, FolderOpen, Pencil } from 'lucide-react'
import { getUsers, createUser, deleteUser, getSettings, updateSettings } from '../api/me'
-import { getLibraries, scanLibrary, createLibrary, deleteLibrary } from '../api/libraries'
+import { getLibraries, scanLibrary, createLibrary, updateLibrary, deleteLibrary } from '../api/libraries'
import FileBrowser from '../components/common/FileBrowser'
type Tab = 'users' | 'libraries' | 'settings'
@@ -115,15 +115,74 @@ function UsersPanel() {
)
}
+function LibraryForm({
+ initial, onSave, onCancel, title,
+}: {
+ initial: { name: string; path: string; mediaType: string }
+ onSave: (v: { name: string; path: string; mediaType: string }) => Promise
+ onCancel: () => void
+ title: string
+}) {
+ const [form, setForm] = useState(initial)
+ const [showBrowser, setShowBrowser] = useState(false)
+ const [saving, setSaving] = useState(false)
+
+ const submit = async () => {
+ setSaving(true)
+ await onSave(form).finally(() => setSaving(false))
+ }
+
+ return (
+
+
{title}
+
setForm({ ...form, name: e.target.value })}
+ className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-primary"
+ />
+
+ setForm({ ...form, path: e.target.value })}
+ className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-primary"
+ />
+
+
+ {showBrowser && (
+
setForm({ ...form, path: p })}
+ onClose={() => setShowBrowser(false)}
+ />
+ )}
+
+
+
+
+
+
+ )
+}
+
function LibrariesPanel() {
const [libraries, setLibraries] = useState([])
const [loading, setLoading] = useState(true)
const [scanning, setScanning] = useState(null)
const [showCreate, setShowCreate] = useState(false)
- const [showBrowser, setShowBrowser] = useState(false)
- const [form, setForm] = useState({ name: '', path: '', mediaType: 'book' })
+ const [editingId, setEditingId] = useState(null)
- useEffect(() => { getLibraries().then(setLibraries).finally(() => setLoading(false)) }, [])
+ const reload = () => getLibraries().then(setLibraries)
+ useEffect(() => { reload().finally(() => setLoading(false)) }, [])
const handleScan = async (id: string) => {
setScanning(id)
@@ -131,12 +190,16 @@ function LibrariesPanel() {
setTimeout(() => setScanning(null), 5000)
}
- const handleCreate = async () => {
+ const handleCreate = async (form: { name: string; path: string; mediaType: string }) => {
await createLibrary({ name: form.name, folders: [{ fullPath: form.path }], media_type: form.mediaType })
- const libs = await getLibraries()
- setLibraries(libs)
+ await reload()
setShowCreate(false)
- setForm({ name: '', path: '', mediaType: 'book' })
+ }
+
+ const handleUpdate = async (id: string, form: { name: string; path: string; mediaType: string }) => {
+ await updateLibrary(id, { name: form.name, folders: [{ fullPath: form.path }], media_type: form.mediaType })
+ await reload()
+ setEditingId(null)
}
const handleDelete = async (id: string) => {
@@ -149,75 +212,54 @@ function LibrariesPanel() {
{libraries.length} Bibliotheken
-
{showCreate && (
-
-
Neue Bibliothek
-
setForm({ ...form, name: e.target.value })}
- className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-primary"
- />
-
- setForm({ ...form, path: e.target.value })}
- className="flex-1 bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-primary"
- />
- setShowBrowser(true)}
- className="flex items-center gap-1.5 bg-white/5 border border-white/10 px-3 py-2 rounded-lg text-sm text-gray-300 hover:text-white hover:bg-white/10"
- >
-
- Durchsuchen
-
-
- {showBrowser && (
-
setForm({ ...form, path: p })}
- onClose={() => setShowBrowser(false)}
- />
- )}
-
-
-
- Anlegen
-
- setShowCreate(false)} className="text-gray-400 px-4 py-2 rounded-lg text-sm hover:text-white">
- Abbrechen
-
-
-
+
setShowCreate(false)}
+ />
)}
{loading ? : (
{libraries.map((lib: any) => (
-
-
-
{lib.name}
-
- {lib.folders?.[0]?.fullPath || ''} · {lib.mediaType}
-
-
-
handleScan(lib.id)} disabled={scanning === lib.id}
- className="flex items-center gap-1 text-xs text-gray-400 hover:text-white bg-white/5 px-3 py-1.5 rounded-lg disabled:opacity-50">
-
- Scan
-
-
handleDelete(lib.id)} className="text-gray-500 hover:text-red-400 p-1">
-
-
+
+ {editingId === lib.id ? (
+
handleUpdate(lib.id, form)}
+ onCancel={() => setEditingId(null)}
+ />
+ ) : (
+
+
+
{lib.name}
+
+ {lib.folders?.[0]?.fullPath || ''} · {lib.mediaType === 'podcast' ? 'Podcast' : 'Hörbücher'}
+
+
+
handleScan(lib.id)} disabled={scanning === lib.id}
+ className="flex items-center gap-1 text-xs text-gray-400 hover:text-white bg-white/5 px-3 py-1.5 rounded-lg disabled:opacity-50 flex-shrink-0">
+
+ Scan
+
+
{ setEditingId(lib.id); setShowCreate(false) }}
+ className="text-gray-500 hover:text-white p-1 flex-shrink-0">
+
+
+
handleDelete(lib.id)} className="text-gray-500 hover:text-red-400 p-1 flex-shrink-0">
+
+
+
+ )}
))}