feat: Speicher-Panel und API zur Anzeige von Speicherinformationen hinzugefügt; Versionsnummer auf 1.0.68 erhöht

This commit is contained in:
2026-05-13 10:58:23 +02:00
parent 00e706b186
commit ed969fe0a7
2 changed files with 108 additions and 2 deletions

108
app.py
View File

@@ -1528,6 +1528,46 @@ def r_favicon():
def r_devices(): def r_devices():
return jsonify(usb_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']) @app.route('/api/config', methods=['GET', 'POST'])
def r_config(): def r_config():
if request.method == 'POST': if request.method == 'POST':
@@ -2457,7 +2497,8 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<div class="card-head"> <div class="card-head">
<div class="card-icon green">&#8644;</div> <div class="card-icon green">&#8644;</div>
<span class="card-title">USB Ports &amp; Datei-Explorer</span> <span class="card-title">USB Ports &amp; Datei-Explorer</span>
<button class="btn sm ghost danger" style="margin-left:auto" onclick="resetPorts()">&#8635;&nbsp;Ports zurücksetzen</button> <button class="btn sm ghost" style="margin-left:auto" onclick="toggleStoragePanel()">&#128190;&nbsp;Speicher</button>
<button class="btn sm ghost danger" style="margin-left:.4rem" onclick="resetPorts()">&#8635;&nbsp;Ports zurücksetzen</button>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="pex-grid"> <div class="pex-grid">
@@ -2571,6 +2612,17 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div> </div>
<!-- Speicher-Panel -->
<div id="storage-panel" style="display:none;margin-top:.85rem;padding:.75rem;background:var(--bg2);border:1px solid var(--brd);border-radius:var(--r)">
<div style="display:flex;align-items:center;gap:.5rem;margin-bottom:.65rem">
<span style="font-size:.82rem;font-weight:700;color:var(--txt)">&#128190;&nbsp;Speicherübersicht</span>
<button class="btn sm ghost" style="margin-left:auto" onclick="loadStorageInfo()">&#8635;&nbsp;Aktualisieren</button>
</div>
<div id="storage-list" style="display:flex;flex-direction:column;gap:.55rem">
<div style="color:var(--sub);font-size:.82rem">Wird geladen…</div>
</div>
</div>
<!-- Nicht zugewiesene Geräte --> <!-- Nicht zugewiesene Geräte -->
<div id="unassigned-wrap" style="display:none;margin-top:.85rem"> <div id="unassigned-wrap" style="display:none;margin-top:.85rem">
<div class="sec">Weitere verbundene Geräte</div> <div class="sec">Weitere verbundene Geräte</div>
@@ -3818,6 +3870,60 @@ async function pollSysinfo(){
}catch(e){} }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='<div style="color:var(--sub);font-size:.82rem">Wird geladen…</div>';
try{
const items=await api('/storage-info');
if(!items||!items.length){
list.innerHTML='<div style="color:var(--sub);font-size:.82rem">Keine Geräte konfiguriert.</div>';
return;
}
list.innerHTML=items.map(it=>{
const icon=it.type==='source'?'&#9650;':'&#9660;';
const typeLabel=it.type==='source'?'Quelle':'Ziel';
const color=it.type==='source'?'var(--grn)':'var(--acc)';
if(!it.mounted||it.total==null){
return `<div style="display:flex;align-items:center;gap:.55rem;padding:.4rem .5rem;background:var(--surf);border:1px solid var(--brd);border-radius:.4rem">
<span style="font-size:.8rem;color:${color}">${icon}</span>
<div style="min-width:0;flex:1">
<div style="font-size:.79rem;font-weight:600;color:var(--txt)">${it.label}</div>
<div style="font-size:.73rem;color:var(--sub)">${typeLabel} · Port ${it.port==='__internal__'?'intern':it.port} · nicht verbunden</div>
</div>
</div>`;
}
const pct=it.pct||0;
const barCls=pct>=90?'hot':pct>=75?'warn':'ok';
return `<div style="padding:.45rem .5rem;background:var(--surf);border:1px solid var(--brd);border-radius:.4rem">
<div style="display:flex;align-items:center;gap:.55rem;margin-bottom:.35rem">
<span style="font-size:.8rem;color:${color}">${icon}</span>
<div style="min-width:0;flex:1">
<div style="font-size:.79rem;font-weight:600;color:var(--txt)">${it.label}</div>
<div style="font-size:.73rem;color:var(--sub)">${typeLabel} · ${fmtBytes(it.used)} belegt · ${fmtBytes(it.free)} frei · ${fmtBytes(it.total)} gesamt</div>
</div>
<span style="font-size:.76rem;font-weight:700;color:var(--${barCls==='hot'?'red':barCls==='warn'?'ylw':'grn'});flex-shrink:0">${pct}%</span>
</div>
<div class="si-bar" style="margin:0"><div class="si-fill ${barCls}" style="width:${pct}%"></div></div>
</div>`;
}).join('');
}catch(e){
list.innerHTML='<div style="color:var(--red);font-size:.82rem">Fehler beim Laden.</div>';
}
}
// -- Kopier-Verlauf ----------------------------------------------------------- // -- Kopier-Verlauf -----------------------------------------------------------
function fmtDur(s){ function fmtDur(s){
if(s<60) return s+'s'; if(s<60) return s+'s';

View File

@@ -1 +1 @@
1.0.67 1.0.68