diff --git a/app.py b/app.py
index cae2aba..b2496b1 100644
--- a/app.py
+++ b/app.py
@@ -1528,6 +1528,46 @@ def r_favicon():
def r_devices():
return jsonify(usb_devices())
+@app.route('/api/storage-info')
+def r_storage_info():
+ cfg = load_cfg()
+ devs = usb_devices()
+ result = []
+
+ def _du_entry(mount_path):
+ try:
+ du = shutil.disk_usage(mount_path)
+ return dict(mounted=True, total=du.total, used=du.used, free=du.free,
+ pct=round(du.used / du.total * 100) if du.total else 0)
+ except Exception:
+ return dict(mounted=False, total=None, used=None, free=None, pct=None)
+
+ for sp in _resolve_source_ports(cfg):
+ dev = next((d for d in devs if d['usb_port'] == sp['port']), None)
+ entry = dict(type='source', label=sp.get('label') or f"Port {sp['port']}",
+ port=sp['port'], mounted=False,
+ total=None, used=None, free=None, pct=None)
+ if dev and dev.get('mount'):
+ entry.update(_du_entry(dev['mount']))
+ result.append(entry)
+
+ if cfg.get('dest_type') == 'internal':
+ entry = dict(type='dest',
+ label=cfg.get('internal_dest_label') or 'Interner Speicher',
+ port='__internal__')
+ entry.update(_du_entry(str(INTERNAL_DEST_DIR)))
+ result.append(entry)
+ elif cfg.get('dest_port'):
+ dev = next((d for d in devs if d['usb_port'] == cfg['dest_port']), None)
+ entry = dict(type='dest', label=cfg.get('dest_label') or f"Port {cfg['dest_port']}",
+ port=cfg['dest_port'], mounted=False,
+ total=None, used=None, free=None, pct=None)
+ if dev and dev.get('mount'):
+ entry.update(_du_entry(dev['mount']))
+ result.append(entry)
+
+ return jsonify(result)
+
@app.route('/api/config', methods=['GET', 'POST'])
def r_config():
if request.method == 'POST':
@@ -2457,7 +2497,8 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
@@ -2571,6 +2612,17 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
+
+
+
+ 💾 Speicherübersicht
+
+
+
+
+
Weitere verbundene Geräte
@@ -3818,6 +3870,60 @@ async function pollSysinfo(){
}catch(e){}
}
+// -- Speicher-Panel -----------------------------------------------------------
+function fmtBytes(b){
+ if(b==null) return '–';
+ if(b<1024**3) return (b/1024**2).toFixed(1)+' MB';
+ return (b/1024**3).toFixed(2)+' GB';
+}
+let storagePanelOpen=false;
+function toggleStoragePanel(){
+ storagePanelOpen=!storagePanelOpen;
+ const p=$('storage-panel');
+ p.style.display=storagePanelOpen?'block':'none';
+ if(storagePanelOpen) loadStorageInfo();
+}
+async function loadStorageInfo(){
+ const list=$('storage-list');
+ list.innerHTML='
Wird geladen…
';
+ try{
+ const items=await api('/storage-info');
+ if(!items||!items.length){
+ list.innerHTML='
Keine Geräte konfiguriert.
';
+ return;
+ }
+ list.innerHTML=items.map(it=>{
+ const icon=it.type==='source'?'▲':'▼';
+ const typeLabel=it.type==='source'?'Quelle':'Ziel';
+ const color=it.type==='source'?'var(--grn)':'var(--acc)';
+ if(!it.mounted||it.total==null){
+ return `
+
${icon}
+
+
${it.label}
+
${typeLabel} · Port ${it.port==='__internal__'?'intern':it.port} · nicht verbunden
+
+
`;
+ }
+ const pct=it.pct||0;
+ const barCls=pct>=90?'hot':pct>=75?'warn':'ok';
+ return `
+
+
${icon}
+
+
${it.label}
+
${typeLabel} · ${fmtBytes(it.used)} belegt · ${fmtBytes(it.free)} frei · ${fmtBytes(it.total)} gesamt
+
+
${pct}%
+
+
+
`;
+ }).join('');
+ }catch(e){
+ list.innerHTML='
Fehler beim Laden.
';
+ }
+}
+
// -- Kopier-Verlauf -----------------------------------------------------------
function fmtDur(s){
if(s<60) return s+'s';
diff --git a/version.txt b/version.txt
index c8b4742..01c08cf 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-1.0.67
+1.0.68