diff --git a/app.py b/app.py
index 7adbafd..13cad44 100644
--- a/app.py
+++ b/app.py
@@ -2023,6 +2023,84 @@ def r_update_check():
return jsonify(ok=True)
+FORMAT_FILESYSTEMS = {
+ 'exfat': {
+ 'label': 'exFAT',
+ 'desc': 'Empfohlen – Mac & Windows, keine 4-GB-Dateigrößenbeschränkung',
+ 'cmd': lambda dev, name: ['mkfs.exfat', '-n', name, dev],
+ 'pkg': 'exfatprogs',
+ },
+ 'fat32': {
+ 'label': 'FAT32',
+ 'desc': 'Mac & Windows, max. 4 GB pro Datei',
+ 'cmd': lambda dev, name: ['mkfs.vfat', '-F', '32', '-n', name[:11], dev],
+ 'pkg': 'dosfstools',
+ },
+ 'ntfs': {
+ 'label': 'NTFS',
+ 'desc': 'Windows nativ, Mac nur lesen',
+ 'cmd': lambda dev, name: ['mkfs.ntfs', '-f', '-L', name[:32], dev],
+ 'pkg': 'ntfs-3g',
+ },
+}
+
+format_state = {'running': False, 'error': None, 'done': False, 'fs': '', 'device': ''}
+
+@app.route('/api/format/status')
+def r_format_status():
+ return jsonify(dict(format_state))
+
+@app.route('/api/format', methods=['POST'])
+def r_format():
+ if format_state['running']:
+ return jsonify(error='Formatierung läuft bereits'), 409
+ if copy_state.get('running'):
+ return jsonify(error='Kopiervorgang läuft – bitte warten'), 409
+
+ body = request.get_json(force=True)
+ fs = body.get('fs', '').lower()
+ name = (body.get('name') or 'PICOPY').upper()
+ dev = body.get('device', '')
+
+ if fs not in FORMAT_FILESYSTEMS:
+ return jsonify(error=f'Unbekanntes Dateisystem: {fs}'), 400
+ if not dev.startswith('/dev/'):
+ return jsonify(error='Ungültiges Gerät'), 400
+
+ # Sicherheitscheck: Gerät muss ein bekanntes USB-Gerät sein
+ known = [d['device'] for d in usb_devices()]
+ if dev not in known:
+ return jsonify(error='Gerät nicht als USB-Laufwerk erkannt'), 400
+
+ def do_format():
+ format_state.update(running=True, error=None, done=False, fs=fs, device=dev)
+ try:
+ # Aushängen falls gemountet
+ subprocess.run(['umount', dev], capture_output=True)
+
+ cmd = FORMAT_FILESYSTEMS[fs]['cmd'](dev, name)
+ r = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
+ if r.returncode != 0:
+ err = r.stderr.strip() or r.stdout.strip() or 'Unbekannter Fehler'
+ # Hilfreiche Meldung wenn Paket fehlt
+ pkg = FORMAT_FILESYSTEMS[fs]['pkg']
+ if 'not found' in err or r.returncode == 127:
+ err = f'Befehl nicht gefunden – bitte installieren: apt install {pkg}'
+ format_state.update(error=err)
+ return
+ format_state.update(done=True)
+ log.info(f'Formatierung {fs} auf {dev} abgeschlossen')
+ except subprocess.TimeoutExpired:
+ format_state.update(error='Timeout – Formatierung dauerte zu lange')
+ except Exception as e:
+ format_state.update(error=str(e))
+ finally:
+ format_state['running'] = False
+
+ threading.Thread(target=do_format, daemon=True).start()
+ return jsonify(ok=True)
+
+
@app.route('/api/system/reboot', methods=['POST'])
def r_system_reboot():
threading.Thread(target=lambda: (
@@ -2432,10 +2510,35 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
-
+
+
+
+
Gerät in den gewünschten Port → aus Liste wählen → Speichern. Ab dann wird dieser Port immer als Ziel verwendet.
+
+
+
🔢 Laufwerk formatieren
+
+
+
+
+
+
+
+
+
+ ⚠ Achtung: Alle Daten auf dem Laufwerk werden unwiderruflich gelöscht!
+
+
+
+
+
@@ -2971,6 +3074,8 @@ function populateSel(){
dstEl.innerHTML = blank('Gerät einstecken, dann hier wählen')
+ mkOpts(d => !srcSet.has(d.usb_port));
if(dstPrev && devs.find(d=>d.usb_port===dstPrev)) dstEl.value=dstPrev;
+ dstEl.onchange=updateFmtToggleBtn;
+ updateFmtToggleBtn();
}
function onDestTypeChange(markDirty=true){
@@ -2985,6 +3090,8 @@ function onDestTypeChange(markDirty=true){
updateInternalShareBox();
renderSlot('dst',cfg.dest_port,cfg.dest_label);
renderExplorerTabs();
+ updateFmtToggleBtn();
+ if(type==='internal'){$('fmt-box').style.display='none';}
}
function renderUnassigned(){
@@ -3267,6 +3374,50 @@ async function toggleInternalShare(){
updateInternalShareBox(r.status);
}
+// -- Format -------------------------------------------------------------------
+let fmtPolling=null;
+
+function toggleFmtBox(){
+ const box=$('fmt-box');
+ const visible=box.style.display!=='none';
+ box.style.display=visible?'none':'block';
+ if(!visible) $('fmt-flash').textContent='';
+}
+
+function updateFmtToggleBtn(){
+ const btn=$('fmt-toggle-btn');
+ if(!btn) return;
+ const sel=$('dst-select').value;
+ const isUsb=($('dst-type')||{}).value!=='internal';
+ btn.style.display=(isUsb && sel)?'':'none';
+}
+
+async function startFormat(){
+ const sel=$('dst-select').value;
+ if(!sel){flash('fmt-flash','err','Kein Gerät ausgewählt');return;}
+ const dev=devs.find(d=>d.usb_port===sel);
+ if(!dev){flash('fmt-flash','err','Gerät nicht gefunden');return;}
+ const fs=$('fmt-fs').value;
+ const name=($('fmt-name').value||'PICOPY').toUpperCase();
+ const fsLabel={'exfat':'exFAT','fat32':'FAT32','ntfs':'NTFS'}[fs]||fs;
+ if(!confirm(`Laufwerk "${dev.label||dev.device}" (${dev.size}) wirklich mit ${fsLabel} formatieren?\n\nAlle Daten werden gelöscht!`))return;
+
+ flash('fmt-flash','ok','Formatierung läuft...');
+ const r=await api('/format','POST',{fs,name,device:dev.device});
+ if(r.error){flash('fmt-flash','err',r.error);return;}
+
+ clearInterval(fmtPolling);
+ fmtPolling=setInterval(async()=>{
+ const s=await api('/format/status');
+ if(s.error){clearInterval(fmtPolling);flash('fmt-flash','err',s.error);return;}
+ if(s.done){
+ clearInterval(fmtPolling);
+ flash('fmt-flash','ok',`✓ Erfolgreich als ${fsLabel} formatiert`);
+ setTimeout(pollDevices,1500);
+ }
+ },800);
+}
+
// -- File Explorer -------------------------------------------------------------
const expl={
role:'src_0', paths:{dst:''},
diff --git a/version.txt b/version.txt
index 52fe959..7f2b140 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-1.0.65
\ No newline at end of file
+1.0.66