feat: Versionsnummer auf 1.0.20 erhöht und Kommentare im Code aktualisiert
This commit is contained in:
142
app.py
142
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"""<!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()">▶ Kopieren starten</button>
|
||||
<button id="btn-cancel" class="btn danger" onclick="cancelCopy()" style="display:none">■ Abbrechen</button>
|
||||
<button class="btn ghost" onclick="refreshDevices()">↻ Geräte neu laden</button>
|
||||
<button class="btn ghost" onclick="refreshDevices()">-> 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 & 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')">✓ 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')">✓ 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">+ NAS-Ziel hinzufügen</button>
|
||||
<button class="btn" onclick="utToggleForm()" id="ut-add-btn">+ 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()">🔍 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()">↺ 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()"><- 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...');
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.19
|
||||
1.0.20
|
||||
Reference in New Issue
Block a user