Korrektur-Flow + Freigaben-Auswahl + Schreibtest + Schnellwahl-Klartext
Ampel / ampel (push) Failing after 52s
Ampel / ampel (push) Failing after 52s
Commander-Befunde (23.07. spaet): - MANUELLE KORREKTUR (KONZEPT Schritt 5 endlich komplett): Metadaten-Seite hat jetzt Titelsuche ueber ALLE Quellen (GET /metadata/search) mit Poster-Kacheln; ein Klick uebernimmt den Treffer (POST /metadata/override) fuer Karte, Jobs UND merkt ihn sich 30 Tage per Disc-Fingerabdruck — dieselbe Disc wird beim naechsten Einlegen sofort richtig erkannt - PC/NAS per Klick: SMB-Freigaben eines Rechners auflisten (GET /storage-mounts/shares via smbclient) statt //host/share raten - Berechtigungs-PRUEFUNG beim Einhaengen: Schreibtest, Warnung bei nur-lesbaren Zielen (API + UI + Log) - "Standard-Unterordner" verstaendlich gemacht: heisst jetzt "Schnellwahl beim Rippen" mit Erklaertext; kryptisches Basis-Feld raus - Preview zeigt Poster; Fingerprint fuer Cache/Override vereinheitlicht (billiges Label+Groesse statt Mount-Titel) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,8 +25,12 @@ export default function StorageMounts() {
|
||||
const [mounts, setMounts] = useState<Mount[]>([])
|
||||
const [targets, setTargets] = useState<Target[]>([])
|
||||
const [name, setName] = useState('')
|
||||
const [typ, setTyp] = useState<'nfs' | 'cifs'>('nfs')
|
||||
const [typ, setTyp] = useState<'nfs' | 'cifs'>('cifs')
|
||||
const [source, setSource] = useState('')
|
||||
const [host, setHost] = useState('')
|
||||
const [shares, setShares] = useState<string[]>([])
|
||||
const [selectedShare, setSelectedShare] = useState('')
|
||||
const [sharesBusy, setSharesBusy] = useState(false)
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [busy, setBusy] = useState(false)
|
||||
@@ -75,16 +79,43 @@ export default function StorageMounts() {
|
||||
|
||||
useEffect(() => { laden(); browsen('/app/media') }, [])
|
||||
|
||||
const freigabenAuflisten = async () => {
|
||||
if (!host) return
|
||||
setSharesBusy(true)
|
||||
setFeedback(null)
|
||||
try {
|
||||
const r = await api.get('/storage-mounts/shares', {
|
||||
params: { host, ...(username ? { username, password } : {}) },
|
||||
})
|
||||
setShares(r.data.shares)
|
||||
if (r.data.shares.length === 0) {
|
||||
setFeedback('✗ Keine Freigaben gefunden — Rechner erreichbar und Freigaben aktiv?')
|
||||
} else {
|
||||
setSelectedShare(r.data.shares[0])
|
||||
}
|
||||
} catch (e: any) {
|
||||
setFeedback(`✗ ${e?.response?.data?.detail || 'Freigaben-Abfrage fehlgeschlagen'}`)
|
||||
setShares([])
|
||||
} finally {
|
||||
setSharesBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
const hinzufuegen = async () => {
|
||||
setBusy(true)
|
||||
setFeedback(null)
|
||||
const quelle = typ === 'cifs' && host && selectedShare ? `//${host}/${selectedShare}` : source
|
||||
try {
|
||||
await api.post('/storage-mounts', {
|
||||
name, type: typ, source,
|
||||
const r = await api.post('/storage-mounts', {
|
||||
name, type: typ, source: quelle,
|
||||
...(username ? { username, password } : {}),
|
||||
})
|
||||
setFeedback(`✓ „${name}" eingehängt — ab sofort als Rip-Ziel wählbar`)
|
||||
setName(''); setSource(''); setUsername(''); setPassword('')
|
||||
setFeedback(
|
||||
r.data.writable
|
||||
? `✓ „${name}" eingehängt — Schreibtest OK, ab sofort als Rip-Ziel wählbar`
|
||||
: `⚠️ „${name}" eingehängt, aber NUR LESBAR — Freigabe-Rechte prüfen!`
|
||||
)
|
||||
setName(''); setSource(''); setHost(''); setShares([]); setSelectedShare(''); setUsername(''); setPassword('')
|
||||
laden()
|
||||
} catch (e: any) {
|
||||
setFeedback(`✗ ${e?.response?.data?.detail || 'Einhängen fehlgeschlagen'}`)
|
||||
@@ -205,14 +236,38 @@ export default function StorageMounts() {
|
||||
<option value="cifs">SMB/CIFS (Windows-Freigabe, NAS)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<label className={`block text-xs font-medium mb-1 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||
Quelle {typ === 'nfs' ? '(host:/export/pfad)' : '(//host/freigabe)'}
|
||||
</label>
|
||||
<input value={source} onChange={e => setSource(e.target.value)} className={feld} placeholder={typ === 'nfs' ? '192.168.1.10:/volume1/filme' : '//192.168.1.10/filme'} />
|
||||
</div>
|
||||
{typ === 'cifs' && (
|
||||
{typ === 'nfs' ? (
|
||||
<div className="md:col-span-2">
|
||||
<label className={`block text-xs font-medium mb-1 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||
Quelle (host:/export/pfad)
|
||||
</label>
|
||||
<input value={source} onChange={e => setSource(e.target.value)} className={feld} placeholder="192.168.1.10:/volume1/filme" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<label className={`block text-xs font-medium mb-1 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||
Rechner (dein PC oder NAS — Name oder IP)
|
||||
</label>
|
||||
<input value={host} onChange={e => setHost(e.target.value)} className={feld} placeholder="192.168.178.20 oder TOBIS-PC" />
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<button
|
||||
onClick={freigabenAuflisten}
|
||||
disabled={!host || sharesBusy}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors disabled:opacity-40 ${theme === 'dark' ? 'bg-slate-700 text-indigo-300 hover:bg-slate-600' : 'bg-slate-200 text-indigo-700 hover:bg-slate-300'}`}
|
||||
>
|
||||
{sharesBusy ? 'Suche…' : 'Freigaben auflisten'}
|
||||
</button>
|
||||
</div>
|
||||
{shares.length > 0 && (
|
||||
<div className="md:col-span-2">
|
||||
<label className={`block text-xs font-medium mb-1 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Freigabe wählen</label>
|
||||
<select value={selectedShare} onChange={e => setSelectedShare(e.target.value)} className={feld}>
|
||||
{shares.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className={`block text-xs font-medium mb-1 ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Benutzer (leer = Gast)</label>
|
||||
<input value={username} onChange={e => setUsername(e.target.value)} className={feld} autoComplete="off" />
|
||||
@@ -226,7 +281,7 @@ export default function StorageMounts() {
|
||||
</div>
|
||||
<button
|
||||
onClick={hinzufuegen}
|
||||
disabled={busy || !name || !source}
|
||||
disabled={busy || !name || (typ === 'nfs' ? !source : !(host && selectedShare))}
|
||||
className="mt-4 px-4 py-2 rounded-lg bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700 transition-colors disabled:opacity-40"
|
||||
>
|
||||
{busy ? 'Hänge ein…' : 'Einhängen & speichern'}
|
||||
|
||||
Reference in New Issue
Block a user