feat: Speicher-Panel und API zur Anzeige von Speicherinformationen hinzugefügt; Versionsnummer auf 1.0.68 erhöht
This commit is contained in:
108
app.py
108
app.py
@@ -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">⇄</div>
|
<div class="card-icon green">⇄</div>
|
||||||
<span class="card-title">USB Ports & Datei-Explorer</span>
|
<span class="card-title">USB Ports & Datei-Explorer</span>
|
||||||
<button class="btn sm ghost danger" style="margin-left:auto" onclick="resetPorts()">↻ Ports zurücksetzen</button>
|
<button class="btn sm ghost" style="margin-left:auto" onclick="toggleStoragePanel()">💾 Speicher</button>
|
||||||
|
<button class="btn sm ghost danger" style="margin-left:.4rem" onclick="resetPorts()">↻ 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)">💾 Speicherübersicht</span>
|
||||||
|
<button class="btn sm ghost" style="margin-left:auto" onclick="loadStorageInfo()">↻ 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'?'▲':'▼';
|
||||||
|
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';
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.0.67
|
1.0.68
|
||||||
|
|||||||
Reference in New Issue
Block a user