feat: Versionsnummer auf 1.0.19 erhöht und kleinere Textkorrekturen vorgenommen

This commit is contained in:
2026-05-09 11:20:39 +02:00
parent aa05dca668
commit 631cf21195
2 changed files with 57 additions and 57 deletions

112
app.py
View File

@@ -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')">✓&nbsp;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')">✓&nbsp;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 &nbsp;(_1, _2 )</option>
<option value="rename">Umbenennen &nbsp;(_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. 3090 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');

View File

@@ -1 +1 @@
1.0.18
1.0.19