feat: Versionsnummer auf 1.0.19 erhöht und kleinere Textkorrekturen vorgenommen
This commit is contained in:
112
app.py
112
app.py
@@ -540,7 +540,7 @@ def _file_md5(p: Path) -> str:
|
||||
|
||||
|
||||
def _atomic_write(path: Path, content: str) -> None:
|
||||
"""Schreibt atomar: erst .tmp, dann os.replace() – sicher bei Stromausfall."""
|
||||
"""Schreibt atomar: erst .tmp, dann os.replace() - sicher bei Stromausfall."""
|
||||
tmp = path.with_suffix(path.suffix + '.tmp')
|
||||
try:
|
||||
tmp.write_text(content, encoding='utf-8')
|
||||
@@ -722,7 +722,7 @@ def do_copy(src_dev, dst_dev, cfg):
|
||||
copy_state.update(phase='verify', progress=0, done=0,
|
||||
total=len(copied_pairs), current='',
|
||||
eta_sec=None, speed_bps=0)
|
||||
add_log(f'Verifiziere {len(copied_pairs)} Dateien…')
|
||||
add_log(f'Verifiziere {len(copied_pairs)} Dateien...')
|
||||
verified_pairs = []
|
||||
|
||||
for i, (src_f, dst_f) in enumerate(copied_pairs):
|
||||
@@ -756,7 +756,7 @@ def do_copy(src_dev, dst_dev, cfg):
|
||||
else:
|
||||
with copy_lock:
|
||||
copy_state.update(phase='delete', current='')
|
||||
add_log(f'Lösche {len(verified_pairs)} Quelldateien…')
|
||||
add_log(f'Lösche {len(verified_pairs)} Quelldateien...')
|
||||
del_errors = 0
|
||||
for src_f, _ in verified_pairs:
|
||||
try:
|
||||
@@ -906,7 +906,7 @@ def run_uploads(local_dir: Path, cfg: dict):
|
||||
|
||||
with upload_lock:
|
||||
upload_state['results'].append({'name': name, 'ok': ok, 'msg': err})
|
||||
add_log(f'Upload {name}: {"✓ OK" if ok else "✗ Fehler – " + err}')
|
||||
add_log(f'Upload {name}: {"✓ OK" if ok else "✗ Fehler - " + err}')
|
||||
|
||||
with upload_lock:
|
||||
upload_state['running'] = False
|
||||
@@ -1213,11 +1213,11 @@ def r_browse():
|
||||
devs = usb_devices()
|
||||
dev = next((d for d in devs if d['usb_port'] == port), None)
|
||||
if not dev:
|
||||
return jsonify(error='Gerät nicht verbunden – bitte neu einstecken'), 404
|
||||
return jsonify(error='Gerät nicht verbunden - bitte neu einstecken'), 404
|
||||
|
||||
mp = get_browse_mp(dev)
|
||||
if not mp:
|
||||
return jsonify(error='Gerät nicht lesbar – bitte neu einstecken'), 500
|
||||
return jsonify(error='Gerät nicht lesbar - bitte neu einstecken'), 500
|
||||
|
||||
try:
|
||||
base = Path(mp).resolve()
|
||||
@@ -1250,7 +1250,7 @@ def r_browse():
|
||||
if e.errno == _errno.EIO:
|
||||
# I/O-Fehler = Gerät abgezogen, Mount bereinigen
|
||||
_drop_browse_mount(port)
|
||||
return jsonify(error='Gerät nicht mehr erreichbar – bitte neu einstecken'), 503
|
||||
return jsonify(error='Gerät nicht mehr erreichbar - bitte neu einstecken'), 503
|
||||
return jsonify(error=str(e)), 500
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 500
|
||||
@@ -1334,7 +1334,7 @@ def r_system_reboot():
|
||||
@app.route('/api/update/install', methods=['POST'])
|
||||
def r_update_install():
|
||||
try:
|
||||
log.info('Update wird heruntergeladen…')
|
||||
log.info('Update wird heruntergeladen...')
|
||||
req = _urlreq.urlopen(f'{RAW_BASE}/app.py', timeout=60)
|
||||
new_code = req.read().decode()
|
||||
vreq = _urlreq.urlopen(f'{RAW_BASE}/version.txt', timeout=10)
|
||||
@@ -1353,7 +1353,7 @@ def r_update_install():
|
||||
with open(vtmp, 'rb') as fh:
|
||||
os.fsync(fh.fileno())
|
||||
os.replace(str(vtmp), '/opt/picopy/version.txt')
|
||||
log.info('Update installiert – starte Dienst neu…')
|
||||
log.info('Update installiert - starte Dienst neu...')
|
||||
|
||||
# Systemd startet den Dienst automatisch neu
|
||||
subprocess.Popen(['systemctl', 'restart', 'picopy'])
|
||||
@@ -1577,7 +1577,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
</div>
|
||||
<div class="topbar-wifi">
|
||||
<div class="wdot d" id="wdot"></div>
|
||||
<span id="wifi-label">Verbinde…</span>
|
||||
<span id="wifi-label">Verbinde...</span>
|
||||
<span id="wifi-ip"></span>
|
||||
</div>
|
||||
<div id="vpn-pill" class="topbar-wifi" style="display:none">
|
||||
@@ -1647,7 +1647,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<div class="port-display">
|
||||
<div class="dot off" id="src-dot"></div>
|
||||
<div style="min-width:0">
|
||||
<div class="port-path" id="src-port-path">—</div>
|
||||
<div class="port-path" id="src-port-path">-</div>
|
||||
<div class="port-info" id="src-dev-info">Kein Port konfiguriert</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1656,9 +1656,9 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<input type="text" id="src-label" placeholder="z.B. linker blauer Port">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Port lernen — Gerät wählen</label>
|
||||
<label>Port lernen - Gerät wählen</label>
|
||||
<select id="src-select">
|
||||
<option value="">— Gerät einstecken, dann hier wählen —</option>
|
||||
<option value="">- Gerät einstecken, dann hier wählen -</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn grn" style="width:100%" onclick="assignPort('source')">✓ Als feste Quelle speichern</button>
|
||||
@@ -1672,7 +1672,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<div class="port-display">
|
||||
<div class="dot off" id="dst-dot"></div>
|
||||
<div style="min-width:0">
|
||||
<div class="port-path" id="dst-port-path">—</div>
|
||||
<div class="port-path" id="dst-port-path">-</div>
|
||||
<div class="port-info" id="dst-dev-info">Kein Port konfiguriert</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1681,9 +1681,9 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<input type="text" id="dst-label" placeholder="z.B. rechter schwarzer Port">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Port lernen — Gerät wählen</label>
|
||||
<label>Port lernen - Gerät wählen</label>
|
||||
<select id="dst-select">
|
||||
<option value="">— Gerät einstecken, dann hier wählen —</option>
|
||||
<option value="">- Gerät einstecken, dann hier wählen -</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn pri" style="width:100%" onclick="assignPort('dest')">✓ Als festes Ziel speichern</button>
|
||||
@@ -1743,7 +1743,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<div class="sec">Dateifilter</div>
|
||||
<div class="field">
|
||||
<label>Nur diese Typen kopieren (leer = alle)</label>
|
||||
<input type="text" id="c-filter" placeholder="jpg, raw, mp4, mov …">
|
||||
<input type="text" id="c-filter" placeholder="jpg, raw, mp4, mov ...">
|
||||
</div>
|
||||
<div style="display:flex;gap:.35rem;flex-wrap:wrap;margin-top:-.35rem;margin-bottom:.85rem">
|
||||
<button class="btn sm ghost" onclick="setFilter('jpg,jpeg,heic,raw,cr2,nef,arw,dng,png')">📷 Fotos</button>
|
||||
@@ -1751,7 +1751,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<button class="btn sm ghost" onclick="setFilter('jpg,jpeg,heic,raw,cr2,nef,arw,dng,mp4,mov,mts,m2ts')">📷+🎬</button>
|
||||
<button class="btn sm ghost" onclick="setFilter('')">✕ Alle</button>
|
||||
</div>
|
||||
<label class="tog"><input type="checkbox" id="c-excl"><span>Systemdateien ausschließen<br><span style="font-size:.72rem;color:var(--sub)">.DS_Store, Thumbs.db, RECYCLER, System Volume Information …</span></span></label>
|
||||
<label class="tog"><input type="checkbox" id="c-excl"><span>Systemdateien ausschließen<br><span style="font-size:.72rem;color:var(--sub)">.DS_Store, Thumbs.db, RECYCLER, System Volume Information ...</span></span></label>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Spalte: Duplikate & Sicherheit -->
|
||||
@@ -1762,7 +1762,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<select id="c-dup">
|
||||
<option value="skip">Überspringen (empfohlen)</option>
|
||||
<option value="overwrite">Überschreiben</option>
|
||||
<option value="rename">Umbenennen (_1, _2 …)</option>
|
||||
<option value="rename">Umbenennen (_1, _2 ...)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -1770,7 +1770,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<label class="tog" style="margin-bottom:.85rem">
|
||||
<input type="checkbox" id="c-verify">
|
||||
<span>Dateien nach Kopieren per MD5 verifizieren<br>
|
||||
<span style="font-size:.72rem;color:var(--sub)">Stellt sicher dass jede Datei identisch ankam — dauert länger</span></span>
|
||||
<span style="font-size:.72rem;color:var(--sub)">Stellt sicher dass jede Datei identisch ankam - dauert länger</span></span>
|
||||
</label>
|
||||
<label class="tog">
|
||||
<input type="checkbox" id="c-delsrc">
|
||||
@@ -1793,7 +1793,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<div class="card-icon pur">↑</div>
|
||||
<span class="card-title">Fernkopie — NAS / SMB</span>
|
||||
<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>
|
||||
@@ -1879,8 +1879,8 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<div style="display:flex;align-items:center;gap:.75rem;padding:.75rem .9rem;background:rgba(79,142,247,.07);border:1px solid rgba(79,142,247,.25);border-radius:.5rem;margin-bottom:.6rem">
|
||||
<span style="font-size:1.3rem;flex-shrink:0" id="wg-pkg-icon">⏳</span>
|
||||
<div>
|
||||
<div style="font-weight:600;font-size:.87rem" id="wg-pkg-title">Installiere WireGuard…</div>
|
||||
<div style="font-size:.76rem;color:var(--sub);margin-top:.1rem">apt-get läuft – bitte warten (bis 60 s)</div>
|
||||
<div style="font-weight:600;font-size:.87rem" id="wg-pkg-title">Installiere WireGuard...</div>
|
||||
<div style="font-size:.76rem;color:var(--sub);margin-top:.1rem">apt-get läuft - bitte warten (bis 60 s)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1902,7 +1902,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<!-- Konfiguration -->
|
||||
<div class="sec" style="margin-top:0">Konfiguration</div>
|
||||
<div style="font-size:.8rem;color:var(--sub);margin-bottom:.65rem;line-height:1.5">
|
||||
WireGuard .conf einfügen — wird als <code style="background:var(--bg2);padding:.1rem .3rem;border-radius:.25rem">/etc/wireguard/picopy.conf</code> gespeichert (Permissions 600). Der private Schlüssel wird maskiert angezeigt.
|
||||
WireGuard .conf einfügen - wird als <code style="background:var(--bg2);padding:.1rem .3rem;border-radius:.25rem">/etc/wireguard/picopy.conf</code> gespeichert (Permissions 600). Der private Schlüssel wird maskiert angezeigt.
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>WireGuard Konfiguration (.conf)</label>
|
||||
@@ -1981,20 +1981,20 @@ function renderSlot(r, port, label){
|
||||
sl.classList.toggle('src-on', isSrc && !!port);
|
||||
sl.classList.toggle('dst-on', !isSrc && !!port);
|
||||
if(port){
|
||||
pp.textContent='Port '+port+(label?' · '+label:'');
|
||||
if(dev){ dot.className='dot on'; pi.textContent=(dev.label||dev.device)+(dev.size?' · '+dev.size:'')+(dev.mount?' · '+dev.mount:''); }
|
||||
pp.textContent='Port '+port+(label?' | '+label:'');
|
||||
if(dev){ dot.className='dot on'; pi.textContent=(dev.label||dev.device)+(dev.size?' | '+dev.size:'')+(dev.mount?' | '+dev.mount:''); }
|
||||
else { dot.className='dot off'; pi.textContent='Gerät nicht verbunden'; }
|
||||
} else {
|
||||
dot.className='dot off'; pp.textContent='—'; pi.textContent='Kein Port konfiguriert';
|
||||
dot.className='dot off'; pp.textContent='-'; pi.textContent='Kein Port konfiguriert';
|
||||
}
|
||||
if(lb && !lb.dataset.dirty) lb.value=label||'';
|
||||
}
|
||||
|
||||
function populateSel(){
|
||||
const opts=devs.map(d=>`<option value="${d.usb_port}">Port ${d.usb_port||'?'} — ${d.label||d.device} (${d.size})</option>`).join('');
|
||||
const opts=devs.map(d=>`<option value="${d.usb_port}">Port ${d.usb_port||'?'} - ${d.label||d.device} (${d.size})</option>`).join('');
|
||||
['src-select','dst-select'].forEach(id=>{
|
||||
const el=$(id),prev=el.value;
|
||||
el.innerHTML='<option value="">— Gerät einstecken, dann hier wählen —</option>'+opts;
|
||||
el.innerHTML='<option value="">- Gerät einstecken, dann hier wählen -</option>'+opts;
|
||||
if(prev && devs.find(d=>d.usb_port===prev)) el.value=prev;
|
||||
});
|
||||
}
|
||||
@@ -2008,7 +2008,7 @@ function renderUnassigned(){
|
||||
<div style="display:flex;align-items:center;gap:.65rem;padding:.5rem .75rem;background:var(--bg2);border-radius:.45rem;font-size:.83rem">
|
||||
<div style="width:8px;height:8px;border-radius:50%;background:var(--ylw);flex-shrink:0"></div>
|
||||
<span style="font-weight:600">${d.label||d.device}</span>
|
||||
<span style="color:var(--sub);font-size:.73rem">${d.device} · Port ${d.usb_port||'?'} · ${d.size}</span>
|
||||
<span style="color:var(--sub);font-size:.73rem">${d.device} | Port ${d.usb_port||'?'} | ${d.size}</span>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
@@ -2066,7 +2066,7 @@ function setFilter(v){ $('c-filter').value=v; }
|
||||
|
||||
// ── WiFi ──────────────────────────────────────────────────────────────────────
|
||||
async function scanNets(){
|
||||
$('net-list').style.display='flex'; $('net-list').innerHTML='<div class="expl-empty" style="padding:.5rem">Suche…</div>';
|
||||
$('net-list').style.display='flex'; $('net-list').innerHTML='<div class="expl-empty" style="padding:.5rem">Suche...</div>';
|
||||
const nets=await api('/wifi/scan');
|
||||
if(!nets.length){$('net-list').innerHTML='<div class="expl-empty" style="padding:.5rem">Keine Netzwerke</div>';return;}
|
||||
$('net-list').innerHTML=nets.map(n=>{
|
||||
@@ -2078,7 +2078,7 @@ function pickNet(s){$('w-ssid').value=s;$('net-list').style.display='none';$('w-
|
||||
async function connectWifi(){
|
||||
const ssid=$('w-ssid').value.trim(),pw=$('w-pw').value;
|
||||
if(!ssid){flash('wifi-flash','err','Bitte SSID eingeben');return;}
|
||||
flash('wifi-flash','ok','Verbinde… (bis 30s)');
|
||||
flash('wifi-flash','ok','Verbinde... (bis 30s)');
|
||||
const r=await api('/wifi/connect','POST',{ssid,password:pw});
|
||||
if(r.error) flash('wifi-flash','err',r.error);
|
||||
else flash('wifi-flash','ok','Gestartet. Neue IP erscheint oben.');
|
||||
@@ -2107,7 +2107,7 @@ function renderUTs(){
|
||||
<span class="ut-ico">🖧</span>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div class="ut-nm">${t.name}</div>
|
||||
<div class="ut-meta">SMB/NAS · ${t.dest_path}</div>
|
||||
<div class="ut-meta">SMB/NAS | ${t.dest_path}</div>
|
||||
</div>
|
||||
<div class="ut-acts">
|
||||
<button class="btn sm ghost" onclick="utTest('${t.id}','${t.name}')">Test</button>
|
||||
@@ -2147,7 +2147,7 @@ async function utSave(){
|
||||
body.token=$('ut-token').value.trim();
|
||||
if(!body.token){flash('ut-form-flash','err','Token fehlt');return;}
|
||||
}
|
||||
flash('ut-form-flash','ok','Speichere…');
|
||||
flash('ut-form-flash','ok','Speichere...');
|
||||
const r=await api('/upload/targets','POST',body);
|
||||
if(r.error){flash('ut-form-flash','err',r.error);return;}
|
||||
const t=await api('/upload/targets/'+r.id+'/test','POST');
|
||||
@@ -2180,8 +2180,8 @@ const expl={
|
||||
const body=$('expl-body'), bread=$('expl-bread');
|
||||
if(!port){body.innerHTML='<div class="expl-empty">Kein Port konfiguriert</div>';bread.innerHTML='';return;}
|
||||
const dev=devs.find(d=>d.usb_port===port);
|
||||
if(!dev){body.innerHTML='<div class="expl-empty">Gerät nicht verbunden</div>';bread.innerHTML='<span style="color:var(--sub)">Port '+port+' — nicht verbunden</span>';return;}
|
||||
body.innerHTML='<div class="expl-empty">Lade…</div>';
|
||||
if(!dev){body.innerHTML='<div class="expl-empty">Gerät nicht verbunden</div>';bread.innerHTML='<span style="color:var(--sub)">Port '+port+' - nicht verbunden</span>';return;}
|
||||
body.innerHTML='<div class="expl-empty">Lade...</div>';
|
||||
try{
|
||||
const data=await api('/browse?port='+encodeURIComponent(port)+'&path='+encodeURIComponent(path));
|
||||
if(data.error){body.innerHTML='<div class="expl-empty">⚠ '+data.error+'</div>';return;}
|
||||
@@ -2266,9 +2266,9 @@ async function poll(){
|
||||
if(v.pkg_running){
|
||||
ni.style.display='none'; pp.style.display='block'; ui.style.display='none';
|
||||
const act=v.pkg_action==='remove'?'Deinstalliere':'Installiere';
|
||||
$('wg-pkg-title').textContent=act+' WireGuard…';
|
||||
$('wg-pkg-title').textContent=act+' WireGuard...';
|
||||
$('wg-pkg-icon').textContent='⏳';
|
||||
$('wg-status-sub').textContent=act+'…';
|
||||
$('wg-status-sub').textContent=act+'...';
|
||||
vp.style.display='none';
|
||||
} else if(!v.installed){
|
||||
ni.style.display='block'; pp.style.display='none'; ui.style.display='none';
|
||||
@@ -2283,7 +2283,7 @@ async function poll(){
|
||||
vp.style.display='flex'; vdot.className='wdot c';
|
||||
vl.textContent='VPN aktiv'; vi.textContent=v.ip||'';
|
||||
wgd.className='wdot c'; wgl.textContent='Verbunden';
|
||||
wgdet.textContent=v.ip?(v.ip+(v.peer?' · peer …'+v.peer.slice(-8):'')):'';
|
||||
wgdet.textContent=v.ip?(v.ip+(v.peer?' | peer ...'+v.peer.slice(-8):'')):'';
|
||||
bc.style.display='none'; bd.style.display=''; bd.disabled=false;
|
||||
$('wg-status-sub').textContent=v.ip||'';
|
||||
} else {
|
||||
@@ -2309,19 +2309,19 @@ async function poll(){
|
||||
if(c.running){
|
||||
const ph=c.phase||'copy';
|
||||
if(ph==='verify'){
|
||||
tx.className='st-headline st-run'; tx.textContent='Verifiziere… '+c.progress+'%';
|
||||
tx.className='st-headline st-run'; tx.textContent='Verifiziere... '+c.progress+'%';
|
||||
pw.style.display='block'; pf.className='prog-fill'; pf.style.width=c.progress+'%';
|
||||
pp.style.display=''; pp.textContent=c.progress+'%';
|
||||
pfiles.style.display=''; pfiles.textContent=c.done+' / '+c.total+' geprüft';
|
||||
pbytes.style.display='none'; eta.style.display='none'; spd.style.display='none';
|
||||
cf.textContent=c.current||'';
|
||||
} else if(ph==='delete'){
|
||||
tx.className='st-headline st-run'; tx.textContent='Quelle wird geleert…';
|
||||
tx.className='st-headline st-run'; tx.textContent='Quelle wird geleert...';
|
||||
pw.style.display='none'; pp.style.display='none'; pfiles.style.display='none';
|
||||
pbytes.style.display='none'; eta.style.display='none'; spd.style.display='none';
|
||||
cf.textContent='';
|
||||
} else {
|
||||
tx.className='st-headline st-run'; tx.textContent='Kopiert… '+c.progress+'%';
|
||||
tx.className='st-headline st-run'; tx.textContent='Kopiert... '+c.progress+'%';
|
||||
pw.style.display='block'; pf.className='prog-fill'; pf.style.width=c.progress+'%';
|
||||
pp.style.display=''; pp.textContent=c.progress+'%';
|
||||
pfiles.style.display=''; pfiles.textContent=c.done+' / '+c.total+' Dateien';
|
||||
@@ -2341,7 +2341,7 @@ async function poll(){
|
||||
}else if(c.last_copy && !_dismissed){
|
||||
tx.className='st-headline st-ok'; tx.textContent='✓ Abgeschlossen';
|
||||
pf.className='prog-fill done'; pw.style.display='block'; pf.style.width='100%';
|
||||
sum.textContent=c.total+' Dateien · '+fmtBytes(c.bytes_total);
|
||||
sum.textContent=c.total+' Dateien | '+fmtBytes(c.bytes_total);
|
||||
time.textContent=new Date(c.last_copy).toLocaleString('de-DE');
|
||||
$('st-dismiss').style.display='';
|
||||
// Auto-dismiss nach 5 Minuten
|
||||
@@ -2366,8 +2366,8 @@ async function poll(){
|
||||
const ub=$('upload-block');
|
||||
if(u.running||u.results.length){
|
||||
ub.style.display='block';
|
||||
$('upload-current').innerHTML=u.running?'⚡ '+u.current+'…':'';
|
||||
$('upload-results').innerHTML=u.results.map(r=>`<div style="font-size:.79rem;color:${r.ok?'var(--grn)':'var(--red)'}">${r.ok?'✓':'✗'} ${r.name}${r.msg?' — '+r.msg:''}</div>`).join('');
|
||||
$('upload-current').innerHTML=u.running?'⚡ '+u.current+'...':'';
|
||||
$('upload-results').innerHTML=u.results.map(r=>`<div style="font-size:.79rem;color:${r.ok?'var(--grn)':'var(--red)'}">${r.ok?'✓':'✗'} ${r.name}${r.msg?' - '+r.msg:''}</div>`).join('');
|
||||
}else ub.style.display='none';
|
||||
}catch(e){}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -2438,7 +2438,7 @@ async function checkUpdate() {
|
||||
await pollUpdate(); // Badge sofort aktualisieren
|
||||
const fl = $('sys-update-flash');
|
||||
if (u.available && u.latest) {
|
||||
fl.className = 'flash warn'; fl.textContent = 'Update v' + u.latest + ' verfügbar – über das Badge oben installieren.';
|
||||
fl.className = 'flash warn'; fl.textContent = 'Update v' + u.latest + ' verfügbar - über das Badge oben installieren.';
|
||||
} else if (u.error) {
|
||||
fl.className = 'flash err'; fl.textContent = 'Fehler: ' + u.error;
|
||||
} else {
|
||||
@@ -2457,7 +2457,7 @@ 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); }
|
||||
@@ -2466,34 +2466,34 @@ async function rebootDevice() {
|
||||
|
||||
// ── 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…');
|
||||
if(!confirm('wireguard + wireguard-tools jetzt per apt-get installieren?\n\nDauer: ca. 30-90 Sekunden.'))return;
|
||||
flash('wg-flash','ok','Starte Installation...');
|
||||
const r=await api('/wireguard/install','POST');
|
||||
if(r.error) flash('wg-flash','err',r.error);
|
||||
}
|
||||
async function wgUninstall(){
|
||||
if(!confirm('WireGuard wirklich deinstallieren?\n\nDer aktive VPN-Tunnel wird vorher getrennt.\nDie Konfigurationsdatei bleibt erhalten.'))return;
|
||||
flash('wg-flash','ok','Deinstalliere…');
|
||||
flash('wg-flash','ok','Deinstalliere...');
|
||||
const r=await api('/wireguard/uninstall','POST');
|
||||
if(r.error) flash('wg-flash','err',r.error);
|
||||
}
|
||||
async function wgConnect(){
|
||||
$('wg-btn-connect').disabled=true;
|
||||
flash('wg-flash','ok','Verbinde VPN…');
|
||||
flash('wg-flash','ok','Verbinde VPN...');
|
||||
await api('/wireguard/connect','POST');
|
||||
}
|
||||
async function wgDisconnect(){
|
||||
$('wg-btn-disconnect').disabled=true;
|
||||
flash('wg-flash','ok','Trenne VPN…');
|
||||
flash('wg-flash','ok','Trenne VPN...');
|
||||
const r=await api('/wireguard/disconnect','POST');
|
||||
if(!r.ok) flash('wg-flash','err','Trennen fehlgeschlagen');
|
||||
}
|
||||
async function wgSaveConfig(){
|
||||
const content=$('wg-config').value.trim();
|
||||
if(!content){flash('wg-flash','err','Konfiguration ist leer');return;}
|
||||
if(!content.includes('[Interface]')){flash('wg-flash','err','Ungültige Konfiguration – [Interface] fehlt');return;}
|
||||
if(!content.includes('[Interface]')){flash('wg-flash','err','Ungültige Konfiguration - [Interface] fehlt');return;}
|
||||
const auto=$('wg-auto').checked;
|
||||
flash('wg-flash','ok','Speichere…');
|
||||
flash('wg-flash','ok','Speichere...');
|
||||
const r=await api('/wireguard/config','POST',{content,auto});
|
||||
if(r.error){flash('wg-flash','err',r.error);return;}
|
||||
flash('wg-flash','ok','✓ Konfiguration gespeichert');
|
||||
|
||||
Reference in New Issue
Block a user