diff --git a/app.py b/app.py index 8a1d635..39e0bf5 100644 --- a/app.py +++ b/app.py @@ -65,7 +65,7 @@ DEFAULT_CONFIG = { 'wireguard_auto': False, } -# ── Persistenter Kopierstatus ─────────────────────────────────────────────── +# -- Persistenter Kopierstatus ----------------------------------------------- copy_state = { 'running': False, 'progress': 0, @@ -101,7 +101,7 @@ def save_state(): except Exception: pass -# ── WiFi Status ───────────────────────────────────────────────────────────── +# -- WiFi Status ------------------------------------------------------------- wifi_state = { 'mode': 'unknown', # 'client' | 'ap' | 'disconnected' @@ -110,7 +110,7 @@ wifi_state = { } wifi_lock = threading.Lock() -# ── Config ─────────────────────────────────────────────────────────────────── +# -- Config ------------------------------------------------------------------- def load_cfg(): cfg = DEFAULT_CONFIG.copy() @@ -128,7 +128,7 @@ def load_cfg(): def save_cfg(cfg): _atomic_write(CONFIG_FILE, json.dumps(cfg, indent=2)) -# ── WiFi Hilfsfunktionen ───────────────────────────────────────────────────── +# -- WiFi Hilfsfunktionen ----------------------------------------------------- def nm(*args): return subprocess.run(['nmcli'] + list(args), @@ -216,7 +216,7 @@ def scan_wifi_networks(): nets.append({'ssid': ssid, 'signal': int(signal) if signal.isdigit() else 0, 'security': security}) return sorted(nets, key=lambda x: -x['signal']) -# ── WiFi Monitor Thread ─────────────────────────────────────────────────────── +# -- WiFi Monitor Thread ------------------------------------------------------- def update_wifi_state(): info = get_wlan0_info() @@ -271,7 +271,7 @@ def wifi_monitor(): time.sleep(30) -# ── WireGuard VPN ───────────────────────────────────────────────────────────── +# -- WireGuard VPN ------------------------------------------------------------- WG_CONF = Path('/etc/wireguard/picopy.conf') WG_IFACE = 'picopy' @@ -411,7 +411,7 @@ def wg_monitor(): time.sleep(10) -# ── USB Geräteerkennung ─────────────────────────────────────────────────────── +# -- USB Geräteerkennung ------------------------------------------------------- def usb_port_of(dev_name): """Gibt den physischen USB-Port-Pfad zurück (z.B. '2-2'). @@ -491,7 +491,7 @@ def ensure_mount(dev_info): return None, False return mp, True -# ── Kopier-Logik ────────────────────────────────────────────────────────────── +# -- Kopier-Logik -------------------------------------------------------------- def add_log(msg): log.info(msg) @@ -619,7 +619,7 @@ def do_copy(src_dev, dst_dev, cfg): 'source': src_dev.get('label', ''), })) - # ── Dateien sammeln & filtern ────────────────────────────────────── + # -- Dateien sammeln & filtern -------------------------------------- src_path = Path(src_mp) all_files = [f for f in src_path.rglob('*') if f.is_file()] files = [f for f in all_files if _should_copy(f, cfg)] @@ -640,7 +640,7 @@ def do_copy(src_dev, dst_dev, cfg): skipped = 0 io_errors = 0 - # ── Phase 1: Kopieren ────────────────────────────────────────────── + # -- Phase 1: Kopieren ---------------------------------------------- for i, f in enumerate(files): with copy_lock: cancelled = not copy_state['running'] @@ -713,7 +713,7 @@ def do_copy(src_dev, dst_dev, cfg): if io_errors: msg_parts.append(f'{io_errors} Fehler (I/O)') - # ── Phase 2: Verifizieren ────────────────────────────────────────── + # -- Phase 2: Verifizieren ------------------------------------------ verify_errors = 0 verified_pairs = list(copied_pairs) @@ -749,7 +749,7 @@ def do_copy(src_dev, dst_dev, cfg): else: add_log(f'Alle {len(verified_pairs)} Dateien verifiziert ✓') - # ── Phase 3: Quelle löschen ──────────────────────────────────────── + # -- Phase 3: Quelle löschen ---------------------------------------- if cfg.get('delete_source') and verified_pairs: if verify_errors: add_log('Quelldateien NICHT gelöscht (Prüfsummenfehler)') @@ -825,7 +825,7 @@ def usb_monitor(): except ImportError: log.warning('pyudev nicht verfügbar') -# ── Upload-Ziele (rclone) ───────────────────────────────────────────────────── +# -- Upload-Ziele (rclone) ----------------------------------------------------- RCLONE_CONF = BASE_DIR / 'rclone.conf' @@ -892,7 +892,7 @@ def run_uploads(local_dir: Path, cfg: dict): with upload_lock: upload_state['current'] = name - add_log(f'Upload → {name}...') + add_log(f'Upload -> {name}...') dest_root = t.get('dest_path', 'PiCopy').strip('/') dest = f'{_remote_name(t["id"])}:{dest_root}' @@ -913,7 +913,7 @@ def run_uploads(local_dir: Path, cfg: dict): upload_state['current'] = '' -# ── Flask Routes ────────────────────────────────────────────────────────────── +# -- Flask Routes -------------------------------------------------------------- @app.route('/') def index(): @@ -1030,7 +1030,7 @@ def r_wifi_status(): return jsonify(dict(wifi_state)) -# ── WireGuard Routes ───────────────────────────────────────────────────────── +# -- WireGuard Routes --------------------------------------------------------- @app.route('/api/wireguard/config', methods=['GET', 'POST']) def r_wg_config(): @@ -1087,7 +1087,7 @@ def r_wg_uninstall(): return jsonify(ok=True) -# ── Upload Routes ────────────────────────────────────────────────────────────── +# -- Upload Routes -------------------------------------------------------------- @app.route('/api/upload/targets', methods=['GET']) def r_upload_list(): @@ -1155,7 +1155,7 @@ def r_upload_status(): return jsonify(dict(upload_state)) -# ── Browse (persistente Mounts für File-Explorer) ───────────────────────────── +# -- Browse (persistente Mounts für File-Explorer) ----------------------------- _browse_mounts = {} # usb_port -> mount_point @@ -1193,7 +1193,7 @@ def get_browse_mp(dev): if mp: if _mp_is_alive(mp): return mp - _drop_browse_mount(port) # veraltet → aufräumen + _drop_browse_mount(port) # veraltet -> aufräumen # Frisch mounten mp = f'/mnt/picopy_br_{port}' @@ -1258,7 +1258,7 @@ def r_browse(): -# ── Update-System ───────────────────────────────────────────────────────────── +# -- Update-System ------------------------------------------------------------- update_state = { 'current': VERSION, @@ -1293,7 +1293,7 @@ def check_for_updates(): update_state.update(latest=latest, available=avail, last_checked=datetime.now().isoformat()) if avail: - log.info(f'Update verfügbar: {VERSION} → {latest}') + log.info(f'Update verfügbar: {VERSION} -> {latest}') except Exception as e: with update_lock: update_state['error'] = str(e) @@ -1366,7 +1366,7 @@ def r_update_install(): return jsonify(error=str(e)), 500 -# ── HTML Template ───────────────────────────────────────────────────────────── +# -- HTML Template ------------------------------------------------------------- HTML = r""" @@ -1375,7 +1375,7 @@ HTML = r""" PiCopy @@ -1589,7 +1589,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -1624,15 +1624,15 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
- +
-
+
<->
USB Ports & Datei-Explorer
@@ -1663,7 +1663,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
-
Gerät in den gewünschten Port → aus Liste wählen → Speichern. PiCopy merkt sich den physischen Port dauerhaft.
+
Gerät in den gewünschten Port -> aus Liste wählen -> Speichern. PiCopy merkt sich den physischen Port dauerhaft.
@@ -1688,7 +1688,7 @@ 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.
+
Gerät in den gewünschten Port -> aus Liste wählen -> Speichern. Ab dann wird dieser Port immer als Ziel verwendet.
@@ -1698,7 +1698,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -1716,7 +1716,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -1789,15 +1789,15 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
-
+
^
Fernkopie - NAS / SMB
- +
- +
@@ -1853,7 +1853,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -1919,7 +1919,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -1928,14 +1928,14 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
- +
-
+
=
Logs
@@ -1957,7 +1957,7 @@ const api = async (p, m='GET', b=null) => { return (await fetch('/api'+p,o)).json(); }; -// ── Tabs ───────────────────────────────────────────────────────────────────── +// -- Tabs --------------------------------------------------------------------- function swTab(show,hide){ $(show).classList.add('on'); $(hide).classList.remove('on'); document.querySelectorAll('.tab').forEach(t=> @@ -1965,7 +1965,7 @@ function swTab(show,hide){ ); } -// ── Port Slots ──────────────────────────────────────────────────────────────── +// -- Port Slots ---------------------------------------------------------------- async function refreshDevices(){ devs = await api('/devices'); renderSlot('src', cfg.source_port, cfg.source_label); @@ -2030,7 +2030,7 @@ async function assignPort(role){ const el=$(id); if(el) el.addEventListener('input',()=>el.dataset.dirty='1'); })); -// ── Copy ────────────────────────────────────────────────────────────────────── +// -- Copy ---------------------------------------------------------------------- async function startCopy(){ _dismissed=false; if(_autoDismissTimer){ clearTimeout(_autoDismissTimer); _autoDismissTimer=null; } @@ -2038,7 +2038,7 @@ async function startCopy(){ } async function cancelCopy(){ await api('/copy/cancel','POST'); } -// ── Config ──────────────────────────────────────────────────────────────────── +// -- Config -------------------------------------------------------------------- async function loadCfg(){ cfg=await api('/config'); $('c-fmt').value=cfg.folder_format||'%Y-%m-%d'; @@ -2064,7 +2064,7 @@ async function saveCopyCfg(){ } function setFilter(v){ $('c-filter').value=v; } -// ── WiFi ────────────────────────────────────────────────────────────────────── +// -- WiFi ---------------------------------------------------------------------- async function scanNets(){ $('net-list').style.display='flex'; $('net-list').innerHTML='
Suche...
'; const nets=await api('/wifi/scan'); @@ -2092,7 +2092,7 @@ async function saveAP(){ else flash('ap-flash','ok','Gespeichert! Hotspot startet neu.'); } -// ── Upload-Ziele ────────────────────────────────────────────────────────────── +// -- Upload-Ziele -------------------------------------------------------------- const UT_ICONS={smb:'🖧',onedrive:'☁',drive:'📄',dropbox:'📦'}; const UT_LABELS={smb:'SMB/NAS',onedrive:'OneDrive',drive:'Google Drive',dropbox:'Dropbox'}; const UT_CMD={onedrive:'rclone authorize "onedrive"',drive:'rclone authorize "drive"',dropbox:'rclone authorize "dropbox"'}; @@ -2119,7 +2119,7 @@ function renderUTs(){ function utToggleForm(){ const f=$('ut-form'),b=$('ut-add-btn'),show=f.style.display==='none'; f.style.display=show?'block':'none'; - b.innerHTML=show?'✕ Abbrechen':'+ Ziel hinzufügen'; + b.innerHTML=show?'✕ Abbrechen':'+ Ziel hinzufügen'; if(show){utSelectType('smb',document.querySelector('.sel-opt'));} } function utSelectType(type,el){ @@ -2164,7 +2164,7 @@ async function utDel(id,name){ await api('/upload/targets/'+id,'DELETE');await loadUTs(); } -// ── File Explorer ───────────────────────────────────────────────────────────── +// -- File Explorer ------------------------------------------------------------- const expl={ role:'src', paths:{src:'',dst:''}, switchRole(r){ @@ -2197,7 +2197,7 @@ const expl={ let acc=''; path.split('/').filter(Boolean).forEach(p=>{ acc+=(acc?'/':'')+p;const a=acc; - h+=`${p}`; + h+=` > ${p}`; }); } el.innerHTML=h; @@ -2207,7 +2207,7 @@ const expl={ let h=''; if(cur){ const par=cur.includes('/')?cur.substring(0,cur.lastIndexOf('/')):''; - h+=`
..
`; + h+=`
<-..
`; } if(!entries.length&&!cur){body.innerHTML='
Laufwerk leer
';return;} if(!entries.length){body.innerHTML=h+'
Ordner leer
';return;} @@ -2255,7 +2255,7 @@ function fmtSpd(bps){ return(bps/1048576).toFixed(1)+' MB/s'; } -// ── Poll ────────────────────────────────────────────────────────────────────── +// -- Poll ---------------------------------------------------------------------- async function poll(){ try{ const {copy:c,wifi:w,vpn:v}=await api('/status'); @@ -2382,7 +2382,7 @@ function dismissStatus(){ $('st-dismiss').style.display='none'; } -// ── Update ──────────────────────────────────────────────────────────────────── +// -- Update -------------------------------------------------------------------- async function pollUpdate() { try { const u = await api('/update/status'); @@ -2405,7 +2405,7 @@ async function installUpdate() { 'Das Web-Interface ist für ca. 10 Sekunden nicht erreichbar.' )) return; - $('upd-badge').innerHTML = '↻ Installiere...'; + $('upd-badge').innerHTML = '-> Installiere...'; $('upd-badge').style.pointerEvents = 'none'; try { @@ -2425,7 +2425,7 @@ async function installUpdate() { async function checkUpdate() { const btn = event.currentTarget; - btn.disabled = true; btn.textContent = '↻ Prüfe...'; + btn.disabled = true; btn.textContent = '-> Prüfe...'; try { await api('/update/check', 'POST'); // Warten bis der Server-Check abgeschlossen ist (max 15 s, alle 500 ms) @@ -2457,14 +2457,14 @@ async function checkUpdate() { async function rebootDevice() { if (!confirm('Gerät jetzt neu starten?\n\nDas Web-Interface ist für ca. 30 Sekunden nicht erreichbar.')) return; try { await api('/system/reboot', 'POST'); } catch(e) {} - document.body.innerHTML = '
↻ Gerät startet neu - bitte warten...
'; + document.body.innerHTML = '
-> Gerät startet neu - bitte warten...
'; setTimeout(async function waitForRestart() { try { await fetch('/api/update/status'); location.reload(); } catch(e) { setTimeout(waitForRestart, 2000); } }, 10000); } -// ── WireGuard VPN ───────────────────────────────────────────────────────────── +// -- WireGuard VPN ------------------------------------------------------------- async function wgInstall(){ if(!confirm('wireguard + wireguard-tools jetzt per apt-get installieren?\n\nDauer: ca. 30-90 Sekunden.'))return; flash('wg-flash','ok','Starte Installation...'); diff --git a/version.txt b/version.txt index e7ad390..450f88e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.19 \ No newline at end of file +1.0.20 \ No newline at end of file