feat: Versionsnummer auf 1.0.20 erhöht und Kommentare im Code aktualisiert

This commit is contained in:
2026-05-09 11:26:54 +02:00
parent 631cf21195
commit 646858267b
2 changed files with 72 additions and 72 deletions

142
app.py
View File

@@ -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"""<!DOCTYPE html>
<html lang="de">
@@ -1375,7 +1375,7 @@ HTML = r"""<!DOCTYPE html>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PiCopy</title>
<style>
/* ── Reset & Tokens ── */
/* -- Reset & Tokens -- */
:root {
--bg: #0a0f1e;
--bg2: #111827;
@@ -1398,7 +1398,7 @@ HTML = r"""<!DOCTYPE html>
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;min-height:100vh;padding:0 0 4rem}
/* ── Topbar ── */
/* -- Topbar -- */
.topbar{background:var(--bg2);border-bottom:1px solid var(--brd);padding:.75rem 1.5rem;display:flex;align-items:center;gap:1rem;position:sticky;top:0;z-index:100;backdrop-filter:blur(8px)}
.logo{display:flex;align-items:center;gap:.55rem;font-size:1rem;font-weight:700;letter-spacing:-.02em;color:var(--txt)}
.logo-dot{width:8px;height:8px;border-radius:50%;background:var(--acc);box-shadow:0 0 8px var(--acc)}
@@ -1412,7 +1412,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
#wifi-label{font-weight:600;color:var(--txt)}
#wifi-ip{color:var(--sub);font-family:monospace;font-size:.76rem}
/* ── Layout ── */
/* -- Layout -- */
.page{max-width:1120px;margin:0 auto;padding:1.25rem 1.25rem 0;display:grid;gap:1rem;grid-template-columns:1fr}
@media(min-width:640px){.page{grid-template-columns:1fr 1fr}}
.col2{grid-column:1/-1}
@@ -1421,7 +1421,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
.site-footer a:hover{color:var(--txt)}
.site-version{font-family:ui-monospace,monospace;color:var(--brd2)}
/* ── Cards ── */
/* -- Cards -- */
.card{background:var(--surf);border:1px solid var(--brd);border-radius:var(--r2);overflow:hidden}
.card-head{display:flex;align-items:center;gap:.6rem;padding:.75rem 1.1rem;border-bottom:1px solid var(--brd);background:var(--surf2)}
.card-icon{width:28px;height:28px;border-radius:.45rem;display:flex;align-items:center;justify-content:center;font-size:.95rem;flex-shrink:0}
@@ -1434,7 +1434,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
.card-sub{font-size:.74rem;color:var(--sub);margin-left:auto}
.card-body{padding:1.1rem}
/* ── Buttons ── */
/* -- Buttons -- */
.btn{display:inline-flex;align-items:center;gap:.35rem;padding:.42rem .9rem;border:1px solid var(--brd2);border-radius:.45rem;background:transparent;color:var(--txt);font-size:.83rem;font-weight:500;cursor:pointer;transition:all .15s;white-space:nowrap;line-height:1.2}
.btn:hover{border-color:var(--acc);color:var(--acc);background:rgba(79,142,247,.07)}
.btn.pri{background:var(--acc);border-color:var(--acc);color:#fff}
@@ -1449,7 +1449,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
.btn:disabled{opacity:.35;cursor:default;pointer-events:none}
.btn-row{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.85rem}
/* ── Progress ── */
/* -- Progress -- */
.prog-track{height:5px;background:var(--bg);border-radius:9999px;overflow:hidden;margin:.65rem 0 .3rem}
.prog-fill{height:100%;background:linear-gradient(90deg,var(--acc),var(--grn));border-radius:9999px;transition:width .5s ease}
.prog-fill.err{background:var(--red)}
@@ -1460,11 +1460,11 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
.pill.grn{border-color:rgba(52,211,153,.4);color:var(--grn);background:rgba(52,211,153,.08)}
.pill.red{border-color:rgba(248,113,113,.4);color:var(--red);background:rgba(248,113,113,.08)}
/* ── Status text ── */
/* -- Status text -- */
.st-headline{font-size:1.05rem;font-weight:700;letter-spacing:-.01em}
.st-run{color:var(--acc)}.st-ok{color:var(--grn)}.st-err{color:var(--red)}.st-idle{color:var(--sub)}
/* ── Form fields ── */
/* -- Form fields -- */
.field{margin-bottom:.85rem}
.field:last-child{margin-bottom:0}
.field label{display:block;font-size:.76rem;font-weight:600;color:var(--sub);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.35rem}
@@ -1477,7 +1477,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
.flash{font-size:.78rem;min-height:1rem;padding:.2rem 0}
.flash.ok{color:var(--grn)}.flash.err{color:var(--red)}.flash.warn{color:#f4a332}
/* ── Port Slots ── */
/* -- Port Slots -- */
/* port-pair: immer echtes 1fr 1fr, unabhängig vom Explorer */
.port-pair{display:grid;grid-template-columns:1fr 1fr;gap:.85rem;align-items:start}
@media(max-width:500px){.port-pair{grid-template-columns:1fr}}
@@ -1495,13 +1495,13 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
.port-info{font-size:.72rem;color:var(--sub);margin-top:.1rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hint-box{font-size:.72rem;color:var(--sub);margin-top:.65rem;padding:.45rem .65rem;background:var(--bg2);border-radius:.4rem;border-left:3px solid var(--brd2);line-height:1.5}
/* ── Port+Explorer grid ── */
/* -- Port+Explorer grid -- */
/* pex-grid: port-pair links, explorer rechts */
.pex-grid{display:grid;gap:.85rem;grid-template-columns:1fr}
@media(min-width:960px){.pex-grid{grid-template-columns:1fr auto}.pex-grid .expl-wrap{width:320px}}
.expl-wrap{border:1px solid var(--brd);border-radius:var(--r);overflow:hidden;display:flex;flex-direction:column}
/* ── File Explorer ── */
/* -- File Explorer -- */
.expl-bar{display:flex;align-items:center;gap:.4rem;padding:.55rem .8rem;background:var(--surf2);border-bottom:1px solid var(--brd);flex-shrink:0}
.etab{padding:.22rem .6rem;border-radius:.35rem;font-size:.76rem;font-weight:600;cursor:pointer;border:1px solid transparent;color:var(--sub);transition:.15s}
.etab.on{background:var(--acc);color:#fff;border-color:var(--acc)}
@@ -1528,14 +1528,14 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
@media(max-width:360px){.expl-dt{display:none}}
.expl-empty{padding:1.5rem;text-align:center;color:var(--sub);font-size:.84rem}
/* ── Log ── */
/* -- Log -- */
.log-wrap{font-family:ui-monospace,monospace;font-size:.75rem;max-height:300px;overflow-y:auto;background:var(--bg2);border-radius:.45rem;padding:.5rem}
.log-row{display:flex;gap:.5rem;padding:.18rem 0;border-bottom:1px solid rgba(42,54,80,.5)}
.log-row:last-child{border-bottom:none}
.log-t{color:var(--brd2);flex-shrink:0}
.log-m{color:var(--sub)}
/* ── Upload targets ── */
/* -- Upload targets -- */
.ut-row{display:flex;align-items:center;gap:.55rem;padding:.6rem .75rem;border:1px solid var(--brd);border-radius:.5rem;transition:.15s}
.ut-row.on{border-color:rgba(79,142,247,.35);background:rgba(79,142,247,.04)}
.ut-ico{font-size:1.1rem;flex-shrink:0}
@@ -1544,7 +1544,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
.ut-acts{display:flex;gap:.3rem;margin-left:auto;flex-shrink:0}
.add-panel{border:1px solid var(--brd);border-radius:.6rem;padding:.9rem;margin-top:.75rem;background:var(--bg2)}
/* ── Tabs (WiFi) ── */
/* -- Tabs (WiFi) -- */
.tab-strip{display:flex;gap:.25rem;margin-bottom:.9rem;border-bottom:1px solid var(--brd);padding-bottom:.6rem}
.tab{padding:.28rem .7rem;border-radius:.35rem;font-size:.8rem;font-weight:500;cursor:pointer;color:var(--sub);transition:.15s;border:1px solid transparent}
.tab.on{background:var(--acc);color:#fff;border-color:var(--acc)}
@@ -1555,12 +1555,12 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
.net-row:hover{border-color:var(--acc);background:rgba(79,142,247,.06)}
.net-sig{font-size:.7rem;color:var(--sub);margin-left:auto}
/* ── Divider ── */
/* -- Divider -- */
.sec{font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--sub);padding:.1rem 0;margin:.7rem 0 .5rem;display:flex;align-items:center;gap:.5rem}
.sec::after{content:'';flex:1;height:1px;background:var(--brd)}
.empty{color:var(--sub);font-size:.85rem;padding:.3rem 0}
/* ── WireGuard VPN ── */
/* -- WireGuard VPN -- */
.wdot.vpn{background:var(--pur);box-shadow:0 0 6px var(--pur)}
</style>
</head>
@@ -1589,7 +1589,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<main class="page">
<!-- ── Kopierstatus ── -->
<!-- -- Kopierstatus -- -->
<div class="card col2">
<div class="card-head">
<div class="card-icon blue">▶</div>
@@ -1624,15 +1624,15 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<div class="btn-row">
<button id="btn-start" class="btn pri" onclick="startCopy()">▶&nbsp;Kopieren starten</button>
<button id="btn-cancel" class="btn danger" onclick="cancelCopy()" style="display:none">■&nbsp;Abbrechen</button>
<button class="btn ghost" onclick="refreshDevices()">&nbsp;Geräte neu laden</button>
<button class="btn ghost" onclick="refreshDevices()">->&nbsp;Geräte neu laden</button>
</div>
</div>
</div>
<!-- ── USB Ports + Explorer ── -->
<!-- -- USB Ports + Explorer -- -->
<div class="card col2">
<div class="card-head">
<div class="card-icon green"></div>
<div class="card-icon green"><-></div>
<span class="card-title">USB Ports &amp; Datei-Explorer</span>
</div>
<div class="card-body">
@@ -1663,7 +1663,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div>
<button class="btn grn" style="width:100%" onclick="assignPort('source')">✓&nbsp;Als feste Quelle speichern</button>
<div id="src-flash" class="flash" style="margin-top:.4rem"></div>
<div class="hint-box">Gerät in den gewünschten Port aus Liste wählen Speichern. PiCopy merkt sich den physischen Port dauerhaft.</div>
<div class="hint-box">Gerät in den gewünschten Port -> aus Liste wählen -> Speichern. PiCopy merkt sich den physischen Port dauerhaft.</div>
</div>
<!-- Ziel -->
@@ -1688,7 +1688,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div>
<button class="btn pri" style="width:100%" onclick="assignPort('dest')">✓&nbsp;Als festes Ziel speichern</button>
<div id="dst-flash" class="flash" style="margin-top:.4rem"></div>
<div class="hint-box">Gerät in den gewünschten Port aus Liste wählen Speichern. Ab dann wird dieser Port immer als Ziel verwendet.</div>
<div class="hint-box">Gerät in den gewünschten Port -> aus Liste wählen -> Speichern. Ab dann wird dieser Port immer als Ziel verwendet.</div>
</div>
</div><!-- /port-pair -->
@@ -1698,7 +1698,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<div class="expl-bar">
<button class="etab on" id="etab-src" onclick="expl.switchRole('src')">⬆ Quelle</button>
<button class="etab" id="etab-dst" onclick="expl.switchRole('dst')">⬇ Ziel</button>
<button class="expl-reload" onclick="expl.reload()" title="Neu laden"></button>
<button class="expl-reload" onclick="expl.reload()" title="Neu laden">-></button>
</div>
<div class="expl-bread" id="expl-bread"></div>
<div class="expl-scroll" id="expl-body">
@@ -1716,7 +1716,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div>
</div>
<!-- ── Kopier-Einstellungen ── -->
<!-- -- Kopier-Einstellungen -- -->
<div class="card col2">
<div class="card-head">
<div class="card-icon ylw">⚙</div>
@@ -1789,15 +1789,15 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div>
</div>
<!-- ── Upload-Ziele ── -->
<!-- -- Upload-Ziele -- -->
<div class="card">
<div class="card-head">
<div class="card-icon pur"></div>
<div class="card-icon pur">^</div>
<span class="card-title">Fernkopie - NAS / SMB</span>
</div>
<div class="card-body">
<div id="ut-list" style="display:flex;flex-direction:column;gap:.45rem;margin-bottom:.65rem"></div>
<button class="btn" onclick="utToggleForm()" id="ut-add-btn">&nbsp;NAS-Ziel hinzufügen</button>
<button class="btn" onclick="utToggleForm()" id="ut-add-btn">+&nbsp;NAS-Ziel hinzufügen</button>
<div id="ut-form" class="add-panel" style="display:none">
<div class="sec" style="margin-top:0">SMB / Netzlaufwerk</div>
@@ -1818,7 +1818,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div>
</div>
<!-- ── WiFi + Log nebeneinander ── -->
<!-- -- WiFi + Log nebeneinander -- -->
<div class="card">
<div class="card-head">
<div class="card-icon acc" style="background:rgba(79,142,247,.15);color:var(--acc)">⌘</div>
@@ -1853,7 +1853,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div>
</div>
<!-- ── WireGuard VPN ── -->
<!-- -- WireGuard VPN -- -->
<div class="card">
<div class="card-head">
<div class="card-icon pur">⚿</div>
@@ -1919,7 +1919,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div>
</div>
<!-- ── System ── -->
<!-- -- System -- -->
<div class="card">
<div class="card-head">
<div class="card-icon" style="background:rgba(255,180,60,.1);color:#f4a332">⚙</div>
@@ -1928,14 +1928,14 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<div class="card-body" style="display:flex;flex-direction:column;gap:.6rem">
<button class="btn" style="width:100%" onclick="checkUpdate()">🔍&nbsp;Nach Update suchen</button>
<div id="sys-update-flash" class="flash" style="display:none"></div>
<button class="btn" style="width:100%;background:rgba(220,60,60,.12);color:#e05555;border-color:rgba(220,60,60,.25)" onclick="rebootDevice()">&nbsp;Gerät neu starten</button>
<button class="btn" style="width:100%;background:rgba(220,60,60,.12);color:#e05555;border-color:rgba(220,60,60,.25)" onclick="rebootDevice()"><-&nbsp;Gerät neu starten</button>
</div>
</div>
<!-- ── Logs ── -->
<!-- -- Logs -- -->
<div class="card col2">
<div class="card-head">
<div class="card-icon" style="background:rgba(139,154,181,.1);color:var(--sub)"></div>
<div class="card-icon" style="background:rgba(139,154,181,.1);color:var(--sub)">=</div>
<span class="card-title">Logs</span>
</div>
<div class="card-body" style="padding:.65rem .85rem">
@@ -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='<div class="expl-empty" style="padding:.5rem">Suche...</div>';
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+=`<span class="bsep"> </span><span class="bseg" onclick="expl.navigate('${a.replace(/'/g,"\\'")}')">${p}</span>`;
h+=`<span class="bsep"> > </span><span class="bseg" onclick="expl.navigate('${a.replace(/'/g,"\\'")}')">${p}</span>`;
});
}
el.innerHTML=h;
@@ -2207,7 +2207,7 @@ const expl={
let h='';
if(cur){
const par=cur.includes('/')?cur.substring(0,cur.lastIndexOf('/')):'';
h+=`<div class="expl-row up" onclick="expl.navigate('${par}')"><span class="expl-ico"></span><span class="expl-nm" style="color:var(--sub)">..</span><span></span><span></span></div>`;
h+=`<div class="expl-row up" onclick="expl.navigate('${par}')"><span class="expl-ico"><-</span><span class="expl-nm" style="color:var(--sub)">..</span><span></span><span></span></div>`;
}
if(!entries.length&&!cur){body.innerHTML='<div class="expl-empty">Laufwerk leer</div>';return;}
if(!entries.length){body.innerHTML=h+'<div class="expl-empty">Ordner leer</div>';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 = '<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;color:#888;font-size:1rem"> Gerät startet neu - bitte warten...</div>';
document.body.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;color:#888;font-size:1rem">-> Gerät startet neu - bitte warten...</div>';
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...');

View File

@@ -1 +1 @@
1.0.19
1.0.20