diff --git a/app.py b/app.py index 32a84b5..21e77c4 100644 --- a/app.py +++ b/app.py @@ -59,7 +59,6 @@ DEFAULT_CONFIG = { 'dest_port': None, 'dest_label': '', 'dest_type': 'usb', 'internal_dest_label': 'Interner Speicher', 'internal_share_enabled': False, - 'ui_lang': 'de', 'folder_format': '%Y-%m-%d', 'add_time': True, 'subfolder': True, 'auto_copy': True, 'file_filter': '', 'exclude_system': True, @@ -1975,11 +1974,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys .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)} .topbar-wifi{margin-left:auto;display:flex;align-items:center;gap:.6rem;font-size:.82rem;background:var(--surf);border:1px solid var(--brd);border-radius:9999px;padding:.3rem .75rem} -.topbar-wifi~.topbar-wifi,.topbar-wifi~.lang-toggle{margin-left:0} -.lang-toggle{display:inline-flex;border:1px solid var(--brd);border-radius:9999px;overflow:hidden;background:var(--surf);flex-shrink:0} -.lang-toggle button{border:0;background:transparent;color:var(--sub);font-size:.74rem;font-weight:700;padding:.32rem .55rem;cursor:pointer} -.lang-toggle button.on{background:var(--acc);color:#fff} -.wdot{width:7px;height:7px;border-radius:50%;transition:.3s;flex-shrink:0} +.topbar-wifi~.topbar-wifi,.wdot{width:7px;height:7px;border-radius:50%;transition:.3s;flex-shrink:0} .wdot.c{background:var(--grn);box-shadow:0 0 6px var(--grn)} .wdot.a{background:var(--pur)} .wdot.d{background:var(--brd2)} @@ -2155,18 +2150,14 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
PiCopy -
+
verfügbar
- Verbinde... + Verbinde...
-
- - -
Bereit
@@ -2311,7 +2302,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -2461,8 +2452,8 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
-
Heimnetz
-
Hotspot (AP)
+
Heimnetz
+
Hotspot (AP)
Heimnetz für die Router-Verbindung. Ohne Verbindung startet PiCopy automatisch einen eigenen Hotspot.
@@ -2470,7 +2461,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -2484,9 +2475,9 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
-
Direkt öffnen
+
Direkt öffnen
http://10.42.0.1:8080
-
Im PiCopy-Hotspot mit dem Handy scannen und die Oberfläche öffnen.
+
Im PiCopy-Hotspot mit dem Handy scannen und die Oberfläche öffnen.
@@ -2525,7 +2516,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
Installiere WireGuard...
-
apt-get läuft – bitte warten (bis 60 s)
+
apt-get läuft – bitte warten (bis 60 s)
@@ -2556,7 +2547,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
- +
@@ -2602,243 +2593,6 @@ const api = async (p, m='GET', b=null) => { return (await fetch('/api'+p,o)).json(); }; -const I18N = { - de: { - 'wifi.connecting':'Verbinde...', 'wifi.none':'Kein WLAN', 'wifi.connected':'Verbunden', - 'wifi.hotspot':'Hotspot: ', 'vpn.active':'VPN aktiv', 'vpn.disconnected':'Getrennt', - 'copy.ready':'Bereit', 'copy.copying':'Kopiert... ', 'copy.verify':'Verifiziere... ', - 'copy.delete':'Quelle wird geleert...', 'copy.done':'✓ Abgeschlossen', 'copy.files':' Dateien', - 'copy.checked':' geprüft', 'qr.title':'Direkt öffnen', - 'qr.sub':'Im PiCopy-Hotspot mit dem Handy scannen und die Oberfläche öffnen.', - 'share.on':'Freigabe stoppen', 'share.off':'Freigeben', 'share.inactive':'Nicht freigegeben', - 'share.install':'Installiere...', 'share.installing':'Samba wird installiert. ', - 'flash.saving':'Speichere...', 'flash.testing':'Teste Verbindung – Schreibzugriff wird geprüft...', - 'flash.ok_test':'✓ Verbindung OK – Lesen & Schreiben erfolgreich', - 'flash.fail_timeout':'✗ Test fehlgeschlagen (Server-Timeout)', - 'flash.no_source':'Keine Quelle ausgewählt – bitte mindestens eine Quelle anhaken.', - 'flash.no_device':'Bitte zuerst ein Gerät wählen.', - 'flash.port_as_target':'Port bereits als Ziel konfiguriert!', - 'flash.port_as_source':'Port bereits als Quelle konfiguriert!', - 'flash.port_src_dup':'Port bereits als Quelle hinzugefügt!', - 'flash.no_server':'Server-Adresse fehlt', - 'flash.no_shares':'Verbunden, aber keine Freigaben gefunden', - 'flash.no_name':'Name fehlt', 'flash.no_share':'Bitte eine Freigabe wählen', - 'flash.wifi_connecting':'Verbinde... (bis 30s)', - 'flash.wifi_started':'Gestartet. Neue IP erscheint oben.', - 'flash.ap_saved':'Gespeichert! Hotspot startet neu.', - 'flash.connect_btn':'Verbinde...', - 'flash.src_added':'Quelle Port ${p} hinzugefügt.', - 'flash.dst_saved':'Port ${p} als Ziel gespeichert.', - 'flash.int_saved':'Interner Speicher als Ziel gespeichert.', - 'flash.no_ssid':'Bitte SSID eingeben', - 'flash.pw_short':'Min. 8 Zeichen', - 'btn.testing':'Teste...', 'btn.checking':'Prüfe...', - 'src.none':'Noch keine Quelle konfiguriert.', - 'src.copy_cb':'Kopieren', - 'ut.none':'Noch keine Fernziele konfiguriert', - 'expl.no_port':'Kein Port konfiguriert', 'expl.no_dev':'Gerät nicht verbunden', - 'expl.loading':'Lade...', 'expl.empty_drive':'Laufwerk leer', 'expl.empty_folder':'Ordner leer', - 'expl.conn_err':'Verbindungsfehler', - 'confirm.del_target':'"${n}" wirklich löschen?', - 'confirm.wg_install':'wireguard + wireguard-tools + openresolv jetzt per apt-get installieren?\n\nDauer: ca. 30–90 Sekunden.', - 'confirm.wg_uninstall':'WireGuard wirklich deinstallieren?\n\nDer aktive VPN-Tunnel wird vorher getrennt.\nDie Konfigurationsdatei bleibt erhalten.', - 'confirm.reboot':'Gerät jetzt neu starten?\n\nDas Web-Interface ist für ca. 30 Sekunden nicht erreichbar.', - 'confirm.update':'Update auf v${v} installieren?\n\nDas Web-Interface ist für ca. 10 Sekunden nicht erreichbar.', - 'reboot.wait':'↺ Gerät startet neu – bitte warten...', - 'wg.installing':'Starte Installation...', 'wg.removing':'Deinstalliere...', - 'wg.connecting':'Verbinde VPN...', 'wg.disconnecting':'Trenne VPN...', - 'wg.disc_failed':'Trennen fehlgeschlagen', 'wg.cfg_empty':'Konfiguration ist leer', - 'wg.saving':'Speichere...', 'wg.saved':'✓ Konfiguration gespeichert', - 'wg.invalid_cfg':'[Interface] fehlt', - 'wg.action_install':'Installiere', 'wg.action_remove':'Deinstalliere', - 'copy.cfg_saved':'Gespeichert!', - 'copy.error_prefix':'Fehler: ', - 'status.active':'Aktiv', 'status.inactive':'Inaktiv', - 'status.configured':'Konfiguriert', 'status.not_cfg':'Nicht konfiguriert', - 'size.free':' frei', - 'confirm.samba':'Samba installieren und /opt/picopy/internal als Netzwerkfreigabe PiCopy bereitstellen?\n\nDie Freigabe ist im Netzwerk lesbar erreichbar.', - 'share.activating':'Aktiviere Freigabe...', 'share.deactivating':'Deaktiviere Freigabe...', - 'share.active_ok':'✓ Freigabe aktiv', 'share.deactive_ok':'✓ Freigabe deaktiviert', - 'update.available':'Update v${v} verfügbar – über das Badge oben installieren.', - 'update.error_prefix':'Fehler: ', 'update.current':'PiCopy ist aktuell.', - 'update.installing':'↓ Installiere...', - 'btn.connect_load':'🔗 Verbinden & Freigaben laden', - 'ut.toggle_close':'✕ Abbrechen', 'ut.toggle_open':'+ NAS-Ziel hinzufügen', - 'check_update':'Nach Update suchen', - 'title.install':'Klicken zum Installieren', 'title.dismiss':'Meldung schließen', - 'title.reload':'Neu laden', 'title.scan_nets':'Netzwerke suchen', - 'title.wg_rm':'wireguard-Paket entfernen', - 'wg.apt_running':'apt-get läuft – bitte warten (bis 60 s)', - 'wg.not_installed':'Nicht installiert', - 'Heimnetz':'Heimnetz', - }, - en: { - 'wifi.connecting':'Connecting...', 'wifi.none':'No Wi-Fi', 'wifi.connected':'Connected', - 'wifi.hotspot':'Hotspot: ', 'vpn.active':'VPN active', 'vpn.disconnected':'Disconnected', - 'copy.ready':'Ready', 'copy.copying':'Copying... ', 'copy.verify':'Verifying... ', - 'copy.delete':'Clearing source...', 'copy.done':'✓ Complete', 'copy.files':' files', - 'copy.checked':' checked', 'qr.title':'Open directly', - 'qr.sub':'Scan while connected to the PiCopy hotspot to open the interface.', - 'share.on':'Stop share', 'share.off':'Share', 'share.inactive':'Not shared', - 'share.install':'Installing...', 'share.installing':'Installing Samba. ', - 'flash.saving':'Saving...', 'flash.testing':'Testing connection – checking write access...', - 'flash.ok_test':'✓ Connection OK – Read & Write successful', - 'flash.fail_timeout':'✗ Test failed (server timeout)', - 'flash.no_source':'No source selected – please check at least one source.', - 'flash.no_device':'Please select a device first.', - 'flash.port_as_target':'Port already configured as target!', - 'flash.port_as_source':'Port already configured as source!', - 'flash.port_src_dup':'Port already added as source!', - 'flash.no_server':'Server address missing', - 'flash.no_shares':'Connected, but no shares found', - 'flash.no_name':'Name missing', 'flash.no_share':'Please select a share', - 'flash.wifi_connecting':'Connecting... (up to 30s)', - 'flash.wifi_started':'Started. New IP will appear above.', - 'flash.ap_saved':'Saved! Hotspot restarting.', - 'flash.connect_btn':'Connecting...', - 'flash.src_added':'Source port ${p} added.', - 'flash.dst_saved':'Port ${p} saved as target.', - 'flash.int_saved':'Internal storage saved as target.', - 'flash.no_ssid':'Please enter SSID', - 'flash.pw_short':'Min. 8 characters', - 'btn.testing':'Testing...', 'btn.checking':'Checking...', - 'src.none':'No sources configured yet.', - 'src.copy_cb':'Copy', - 'ut.none':'No remote targets configured', - 'expl.no_port':'No port configured', 'expl.no_dev':'Device not connected', - 'expl.loading':'Loading...', 'expl.empty_drive':'Drive empty', 'expl.empty_folder':'Folder empty', - 'expl.conn_err':'Connection error', - 'confirm.del_target':'Really delete "${n}"?', - 'confirm.wg_install':'Install wireguard + wireguard-tools + openresolv via apt-get?\n\nDuration: approx. 30–90 seconds.', - 'confirm.wg_uninstall':'Really uninstall WireGuard?\n\nThe active VPN tunnel will be disconnected first.\nThe configuration file will be kept.', - 'confirm.reboot':'Restart device now?\n\nThe web interface will be unavailable for approx. 30 seconds.', - 'confirm.update':'Install update v${v}?\n\nThe web interface will be unavailable for approx. 10 seconds.', - 'reboot.wait':'↺ Device restarting – please wait...', - 'wg.installing':'Starting installation...', 'wg.removing':'Uninstalling...', - 'wg.connecting':'Connecting VPN...', 'wg.disconnecting':'Disconnecting VPN...', - 'wg.disc_failed':'Disconnect failed', 'wg.cfg_empty':'Configuration is empty', - 'wg.saving':'Saving...', 'wg.saved':'✓ Configuration saved', - 'wg.invalid_cfg':'[Interface] missing', - 'wg.action_install':'Installing', 'wg.action_remove':'Uninstalling', - 'copy.cfg_saved':'Saved!', - 'copy.error_prefix':'Error: ', - 'status.active':'Active', 'status.inactive':'Inactive', - 'status.configured':'Configured', 'status.not_cfg':'Not configured', - 'size.free':' free', - 'confirm.samba':'Install Samba and share /opt/picopy/internal as network share PiCopy?\n\nThe share will be accessible read-only on the network.', - 'share.activating':'Activating share...', 'share.deactivating':'Deactivating share...', - 'share.active_ok':'✓ Share active', 'share.deactive_ok':'✓ Share deactivated', - 'update.available':'Update v${v} available – install via the badge above.', - 'update.error_prefix':'Error: ', 'update.current':'PiCopy is up to date.', - 'update.installing':'↓ Installing...', - 'btn.connect_load':'🔗 Connect & Load Shares', - 'ut.toggle_close':'✕ Cancel', 'ut.toggle_open':'+ Add NAS Target', - 'check_update':'Check For Update', - 'title.install':'Click to install', 'title.dismiss':'Close message', - 'title.reload':'Reload', 'title.scan_nets':'Scan networks', - 'title.wg_rm':'Remove WireGuard package', - 'wg.apt_running':'apt-get running – please wait (up to 60 s)', - 'wg.not_installed':'Not installed', - 'Heimnetz':'Home Network', - } -}; -const STATIC_EN = { - // Card titles - 'Kopierstatus':'Copy Status', 'USB Ports & Datei-Explorer':'USB Ports & File Explorer', - 'Kopier-Einstellungen':'Copy Settings', 'Fernkopie - NAS / SMB':'Remote Copy - NAS / SMB', - 'WiFi-Einstellungen':'Wi-Fi Settings', 'WireGuard VPN':'WireGuard VPN', - 'System':'System', 'Logs':'Logs', - // Section headers - 'Ordnerstruktur':'Folder Structure', 'Dateifilter':'File Filter', - 'Duplikate':'Duplicates', 'Integrität & Aufräumen':'Integrity & Cleanup', - 'Heimnetz':'Home Network', 'Hotspot (AP)':'Hotspot (AP)', - 'Konfiguration':'Configuration', 'Fernkopie':'Remote Copy', - 'Weitere verbundene Geräte':'Other Connected Devices', - 'Schritt 1 – Server-Verbindung':'Step 1 – Server Connection', - 'Schritt 2 – Freigabe & Details':'Step 2 – Share & Details', - // Labels - 'Bezeichnung':'Label', 'Zieltyp':'Target Type', 'Datumsformat':'Date Format', - 'Uhrzeit im Ordnernamen':'Add Time To Folder Name', - 'Unterordner pro Quelle':'Subfolder Per Source', 'Automatisch kopieren':'Auto Copy', - 'Nur diese Typen kopieren (leer = alle)':'Only These Types (empty = all)', - 'Systemdateien ausschließen':'Exclude System Files', - 'Wenn Zieldatei bereits existiert':'When Target File Exists', - 'Dateien nach Kopieren per MD5 verifizieren':'Verify Files With MD5 After Copy', - 'Quelldateien nach Kopieren löschen':'Delete Source Files After Copy', - 'Netzwerk (SSID)':'Network (SSID)', 'Passwort':'Password', - 'Hotspot-Name (SSID)':'Hotspot Name (SSID)', 'Passwort (min. 8 Zeichen)':'Password (min. 8 chars)', - 'Port lernen - Gerät wählen':'Learn Port - Select Device', - 'Beim Start automatisch verbinden':'Connect Automatically On Startup', - 'WireGuard Konfiguration (.conf)':'WireGuard Configuration (.conf)', - 'Server (IP / Hostname)':'Server (IP / Hostname)', 'Benutzer':'User', - 'Freigabe wählen':'Select Share', 'Name (Anzeigename)':'Name (display name)', - 'Ziel-Ordner auf dem NAS':'Destination Folder on NAS', - 'Als festes Ziel speichern':'Save As Fixed Target', 'SMB-Freigabe':'SMB Share', - 'Interner Speicher vom Raspberry Pi':'Raspberry Pi Internal Storage', - // Buttons - 'Kopieren starten':'Start Copy', 'Abbrechen':'Cancel', 'Speichern':'Save', - 'Speichern & Neustart':'Save & Restart', 'Verbinden & Speichern':'Connect & Save', - 'NAS-Ziel hinzufügen':'Add NAS Target', 'Quelle hinzufügen':'Add Source', - 'Nach Update suchen':'Check For Update', 'Gerät neu starten':'Restart Device', - 'Deinstallieren':'Uninstall', 'Installieren':'Install', - 'Freigeben':'Share', 'Freigabe stoppen':'Stop Share', - 'Alle':'All', '✕ Alle':'✕ All', '📷 Fotos':'📷 Photos', '🎬 Videos':'🎬 Videos', - 'Konfiguration speichern':'Save Configuration', - // Options - '- Gerät einstecken, dann hier wählen -':'- Insert device, then select here -', - 'USB-Laufwerk':'USB Drive', 'Interner Speicher':'Internal Storage', - 'Überspringen (empfohlen)':'Skip (recommended)', 'Überschreiben':'Overwrite', - 'Umbenennen (_1, _2 ...)':'Rename (_1, _2 ...)', - 'JJJJ-MM-TT (2024-01-15)':'YYYY-MM-DD (2024-01-15)', - 'JJJJ/MM/TT (Unterordner)':'YYYY/MM/DD (Subfolder)', - 'JJJJMMTT (20240115)':'YYYYMMDD (20240115)', - 'TT-MM-JJJJ (15-01-2024)':'DD-MM-YYYY (15-01-2024)', - // States / info - 'Kein Port konfiguriert':'No port configured', 'Gerät nicht verbunden':'Device not connected', - 'Noch keine Einträge':'No entries yet', - 'Port konfigurieren und Gerät verbinden':'Configure port and connect device', - 'Nicht freigegeben':'Not shared', - // Role tags & misc - '▼ Ziel':'▼ Target', '⬇ Ziel':'▼ Target', 'Ziel':'Target', - 'Fotos':'Photos', 'Videos':'Videos', -}; -function t(k){return (I18N[cfg.ui_lang||'de']&&I18N[cfg.ui_lang||'de'][k])||I18N.de[k]||k;} -function applyLang(){ - const lang=cfg.ui_lang||'de'; - document.documentElement.lang=lang; - $('lang-de').classList.toggle('on',lang==='de'); - $('lang-en').classList.toggle('on',lang==='en'); - document.querySelectorAll('[data-i18n]').forEach(el=>{el.textContent=t(el.dataset.i18n);}); - document.querySelectorAll('button,label,span,.card-title,.sec,.hint-box,.expl-empty,option').forEach(el=>{ - if(el.children.length>0) return; - if(!el.dataset.deText) el.dataset.deText=el.textContent.trim(); - const de=el.dataset.deText; - el.textContent=lang==='en'?(STATIC_EN[de]||de):de; - }); - const ph = { - 'src-label':['z.B. Kamera 1 / linker Port','e.g. Camera 1 / left port'], - 'dst-label':['z.B. Zielplatte oder Interner Speicher','e.g. target drive or internal storage'], - 'w-ssid':['WLAN-Name','Wi-Fi name'], - 'w-pw':['WLAN-Passwort','Wi-Fi password'], - 'ut-host':['192.168.1.100 oder nas.local','192.168.1.100 or nas.local'], - 'ut-user':['(leer = anonym)','(empty = anonymous)'], - 'ut-pass':['(leer = kein Passwort)','(empty = no password)'], - 'ut-name':['z.B. Heimserver NAS','e.g. Home server NAS'], - 'ut-dest':['PiCopy','PiCopy'], - 'c-fmt':['%Y-%m-%d','%Y-%m-%d'], - 'c-filter':['jpg, raw, mp4, mov ...','jpg, raw, mp4, mov ...'], - 'ap-ssid':['PiCopy','PiCopy'], - 'ap-pw':['PiCopy,','PiCopy,'], - }; - Object.entries(ph).forEach(([id,v])=>{const el=$(id); if(el) el.placeholder=lang==='en'?v[1]:v[0];}); - document.querySelectorAll('[data-title-i18n]').forEach(el=>{el.title=t(el.dataset.titleI18n);}); -} -async function setLang(lang){ - cfg.ui_lang=lang; - await api('/config','POST',cfg); - applyLang(); - poll(); -} function drawHotspotQR(){ const url='http://10.42.0.1:8080'; @@ -2932,8 +2686,7 @@ async function refreshDevices(){ renderSlot('dst', cfg.dest_port, cfg.dest_label); renderUnassigned(); populateSel(); - applyLang(); -} + } let selectedPortSet = new Set(); @@ -2955,14 +2708,14 @@ function renderSources(){ `; }).join('') + (ports.length === 0 - ? '
'+t('src.none')+'
' + ? '
'+'Noch keine Quelle konfiguriert.'+'
' : ''); renderExplorerTabs(); } @@ -3008,9 +2761,9 @@ function renderSlot(r, port, label){ 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:''); } - else { dot.className='dot off'; pi.textContent=t('expl.no_dev'); } + else { dot.className='dot off'; pi.textContent='Gerät nicht verbunden'; } } else { - dot.className='dot off'; pp.textContent='-'; pi.textContent=t('expl.no_port'); + dot.className='dot off'; pp.textContent='-'; pi.textContent='Kein Port konfiguriert'; } if(lb && !lb.dataset.dirty) lb.value=label||''; } @@ -3063,14 +2816,14 @@ function renderUnassigned(){ async function addSource(){ const port=$('src-select').value, label=$('src-label').value.trim(); - if(!port){flash('src-flash','err',t('flash.no_device'));return;} - if((cfg.dest_type||'usb')!=='internal' && port===cfg.dest_port){flash('src-flash','err',t('flash.port_as_target'));return;} - if((cfg.source_ports||[]).some(sp=>sp.port===port)){flash('src-flash','err',t('flash.port_src_dup'));return;} + if(!port){flash('src-flash','err','Bitte zuerst ein Gerät wählen.');return;} + if((cfg.dest_type||'usb')!=='internal' && port===cfg.dest_port){flash('src-flash','err','Port bereits als Ziel konfiguriert!');return;} + if((cfg.source_ports||[]).some(sp=>sp.port===port)){flash('src-flash','err','Port bereits als Quelle hinzugefügt!');return;} cfg.source_ports = [...(cfg.source_ports||[]), {port, label}]; selectedPortSet.add(port); await api('/config','POST',cfg); $('src-label').value=''; - flash('src-flash','ok','✓ '+t('flash.src_added').replace('${p}',port)); + flash('src-flash','ok','✓ '+'Quelle Port ${p} hinzugefügt.'.replace('${p}',port)); renderSources(); populateSel(); renderUnassigned(); } @@ -3092,17 +2845,17 @@ async function assignPort(role){ cfg[lk]=cfg.internal_dest_label; $(lid).dataset.dirty=''; await api('/config','POST',cfg); - flash(fid,'ok','✓ '+t('flash.int_saved')); + flash(fid,'ok','✓ '+'Interner Speicher als Ziel gespeichert.'); renderSlot('dst',cfg.dest_port,cfg.dest_label); renderExplorerTabs(); expl.reload(); return; } - if(!port){flash(fid,'err',t('flash.no_device'));return;} - if((cfg.source_ports||[]).some(sp=>sp.port===port)){flash(fid,'err',t('flash.port_as_source'));return;} + if(!port){flash(fid,'err','Bitte zuerst ein Gerät wählen.');return;} + if((cfg.source_ports||[]).some(sp=>sp.port===port)){flash(fid,'err','Port bereits als Quelle konfiguriert!');return;} cfg.dest_type='usb'; cfg[pk]=port; cfg[lk]=label; $(lid).dataset.dirty=''; await api('/config','POST',cfg); - flash(fid,'ok','✓ '+t('flash.dst_saved').replace('${p}',port)); + flash(fid,'ok','✓ '+'Port ${p} als Ziel gespeichert.'.replace('${p}',port)); renderSlot('dst',cfg.dest_port,cfg.dest_label); populateSel(); renderUnassigned(); } @@ -3115,7 +2868,7 @@ async function startCopy(){ _dismissed=false; if(_autoDismissTimer){ clearTimeout(_autoDismissTimer); _autoDismissTimer=null; } const ports=[...(cfg.source_ports||[]).map(sp=>sp.port).filter(p=>selectedPortSet.has(p))]; - if(!ports.length){flash('copy-hint','warn',t('flash.no_source'));return;} + if(!ports.length){flash('copy-hint','warn','Keine Quelle ausgewählt – bitte mindestens eine Quelle anhaken.');return;} const r=await api('/copy/start','POST',{ports}); if(r.error) flash('copy-hint','warn',r.error); else $('copy-hint').style.display='none'; @@ -3141,8 +2894,7 @@ async function loadCfg(){ $('w-ssid').value=cfg.wifi_ssid||''; $('ap-ssid').value=cfg.ap_ssid||'PiCopy'; $('dst-type').value=cfg.dest_type||'usb'; onDestTypeChange(false); - applyLang(); -} + } async function saveCopyCfg(){ cfg.folder_format=$('c-fmt').value; cfg.add_time=$('c-time').checked; cfg.subfolder=$('c-sub').checked; cfg.auto_copy=$('c-auto').checked; @@ -3152,7 +2904,7 @@ async function saveCopyCfg(){ cfg.verify_checksum=$('c-verify').checked; cfg.delete_source=$('c-delsrc').checked; await api('/config','POST',cfg); - const m=$('copy-cfg-msg'); m.textContent=t('copy.cfg_saved'); m.style.display='block'; + const m=$('copy-cfg-msg'); m.textContent='Gespeichert!'; m.style.display='block'; setTimeout(()=>m.style.display='none',2500); } function setFilter(v){ $('c-filter').value=v; } @@ -3170,28 +2922,28 @@ async function scanNets(){ function pickNet(s){$('w-ssid').value=s;$('net-list').style.display='none';$('w-pw').focus();} async function connectWifi(){ const ssid=$('w-ssid').value.trim(),pw=$('w-pw').value; - if(!ssid){flash('wifi-flash','err',t('flash.no_ssid'));return;} - flash('wifi-flash','ok',t('flash.wifi_connecting')); + if(!ssid){flash('wifi-flash','err','Bitte SSID eingeben');return;} + 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',t('flash.wifi_started')); + else flash('wifi-flash','ok','Gestartet. Neue IP erscheint oben.'); } async function saveAP(){ const s=$('ap-ssid').value.trim(),p=$('ap-pw').value; - if(!s){flash('ap-flash','err',t('flash.no_name'));return;} - if(p.length<8){flash('ap-flash','err',t('flash.pw_short'));return;} + if(!s){flash('ap-flash','err','Name fehlt');return;} + if(p.length<8){flash('ap-flash','err','Min. 8 Zeichen');return;} const r=await api('/wifi/ap','POST',{ssid:s,password:p}); if(r.error) flash('ap-flash','err',r.error); - else flash('ap-flash','ok',t('flash.ap_saved')); + else flash('ap-flash','ok','Gespeichert! Hotspot startet neu.'); } // -- Upload-Ziele -------------------------------------------------------------- let utTargets=[], _utConn={}; -async function loadUTs(){utTargets=await api('/upload/targets');renderUTs();applyLang();} +async function loadUTs(){utTargets=await api('/upload/targets');renderUTs();} function renderUTs(){ const el=$('ut-list'); - if(!utTargets.length){el.innerHTML='
'+t('ut.none')+'
';return;} + if(!utTargets.length){el.innerHTML='
'+'Noch keine Fernziele konfiguriert'+'
';return;} el.innerHTML=utTargets.map(t=>`
🖧 @@ -3201,7 +2953,7 @@ function renderUTs(){
- +
@@ -3210,7 +2962,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?t('ut.toggle_close'):t('ut.toggle_open'); + b.innerHTML=show?'✕ Abbrechen':'+ NAS-Ziel hinzufügen'; if(show){ $('ut-step1').style.display=''; $('ut-step2').style.display='none'; ['ut-host','ut-user','ut-pass','ut-name'].forEach(id=>{$(id).value='';}); @@ -3221,16 +2973,16 @@ function utToggleForm(){ } async function utConnect(){ const host=$('ut-host').value.trim(); - if(!host){flash('ut-form-flash','err',t('flash.no_server'));return;} + if(!host){flash('ut-form-flash','err','Server-Adresse fehlt');return;} const btn=$('ut-connect-btn'); - btn.disabled=true; btn.textContent=t('flash.connect_btn'); + btn.disabled=true; btn.textContent='Verbinde...'; $('ut-form-flash').style.display='none'; const r=await api('/upload/browse','POST',{ host, user:$('ut-user').value.trim(), pass:$('ut-pass').value }); - btn.disabled=false; btn.innerHTML=t('btn.connect_load'); + btn.disabled=false; btn.innerHTML='🔗 Verbinden & Freigaben laden'; if(r.error){flash('ut-form-flash','err','✗ '+r.error);return;} - if(!r.shares||!r.shares.length){flash('ut-form-flash','warn',t('flash.no_shares'));return;} + if(!r.shares||!r.shares.length){flash('ut-form-flash','warn','Verbunden, aber keine Freigaben gefunden');return;} _utConn={host, user:$('ut-user').value.trim(), pass:$('ut-pass').value}; $('ut-share-sel').innerHTML=r.shares.map(s=>``).join(''); if(!$('ut-name').value) $('ut-name').value=host; @@ -3243,38 +2995,38 @@ function utBack(){ async function utSave(){ const name=$('ut-name').value.trim(), dest=$('ut-dest').value.trim()||'PiCopy'; const share=$('ut-share-sel').value; - if(!name){flash('ut-form-flash','err',t('flash.no_name'));return;} - if(!share){flash('ut-form-flash','err',t('flash.no_share'));return;} + if(!name){flash('ut-form-flash','err','Name fehlt');return;} + if(!share){flash('ut-form-flash','err','Bitte eine Freigabe wählen');return;} const body={type:'smb',name,dest_path:dest,share, host:_utConn.host, user:_utConn.user, pass:_utConn.pass}; - flash('ut-form-flash','warn',t('flash.saving')); + flash('ut-form-flash','warn','Speichere...'); const r=await api('/upload/targets','POST',body); if(r.error){flash('ut-form-flash','err',r.error);return;} - flash('ut-form-flash','warn',t('flash.testing')); + flash('ut-form-flash','warn','Teste Verbindung – Schreibzugriff wird geprüft...'); try{ const tr=await api('/upload/targets/'+r.id+'/test','POST'); - if(tr.ok){flash('ut-form-flash','ok',t('flash.ok_test'));utToggleForm();await loadUTs();} - else flash('ut-form-flash','err','✗ '+(tr.error||t('flash.fail_timeout').slice(2))); - }catch(e){flash('ut-form-flash','err',t('flash.fail_timeout'));} + if(tr.ok){flash('ut-form-flash','ok','✓ Verbindung OK – Lesen & Schreiben erfolgreich');utToggleForm();await loadUTs();} + else flash('ut-form-flash','err','✗ '+(tr.error||'✗ Test fehlgeschlagen (Server-Timeout)'.slice(2))); + }catch(e){flash('ut-form-flash','err','✗ Test fehlgeschlagen (Server-Timeout)');} } async function utTest(id){ const btn=$('ut-test-'+id), res=$('ut-test-result-'+id); - btn.disabled=true; btn.textContent=t('btn.testing'); + btn.disabled=true; btn.textContent='Teste...'; res.style.display='none'; const r=await api('/upload/targets/'+id+'/test','POST'); btn.disabled=false; btn.innerHTML='🔍 Test'; res.style.display='block'; if(r.ok){ res.style.background='rgba(52,211,153,.12)'; res.style.color='var(--grn)'; - res.textContent=t('flash.ok_test'); + res.textContent='✓ Verbindung OK – Lesen & Schreiben erfolgreich'; } else { res.style.background='rgba(248,113,113,.1)'; res.style.color='var(--red)'; - res.textContent='✗ ' + (r.error||t('flash.fail_timeout').slice(2)); + res.textContent='✗ ' + (r.error||'✗ Test fehlgeschlagen (Server-Timeout)'.slice(2)); } } async function utToggle(id){await api('/upload/targets/'+id+'/toggle','POST');await loadUTs();} async function utDel(id,name){ - if(!confirm(t('confirm.del_target').replace('${n}',name)))return; + if(!confirm('"${n}" wirklich löschen?'.replace('${n}',name)))return; await api('/upload/targets/'+id,'DELETE');await loadUTs(); } @@ -3282,17 +3034,17 @@ async function updateInternalShareBox(state=null){ if(!$('internal-share-box'))return; const s=state||await api('/internal-share/status'); const btn=$('internal-share-btn'), detail=$('internal-share-detail'); - const free=s.free!=null?fmtBytes(s.free)+t('size.free'):''; + const free=s.free!=null?fmtBytes(s.free)+' frei':''; if(s.pkg_running){ - btn.disabled=true; btn.textContent=t('share.install'); - detail.textContent=t('share.installing')+free; + btn.disabled=true; btn.textContent='Installiere...'; + detail.textContent='Samba wird installiert. '+free; return; } btn.disabled=false; - btn.textContent=s.enabled?t('share.on'):t('share.off'); + btn.textContent=s.enabled?'Freigabe stoppen':'Freigeben'; const status=s.enabled - ? ((s.active?t('status.active'):t('status.configured'))+' | \\\\'+(location.hostname||'picopy')+'\\PiCopy') - : t('share.inactive'); + ? ((s.active?'Aktiv':'Konfiguriert')+' | \\\\'+(location.hostname||'picopy')+'\\PiCopy') + : 'Nicht freigegeben'; detail.textContent=status+(free?' | '+free:''); } @@ -3300,12 +3052,12 @@ async function toggleInternalShare(){ const current=await api('/internal-share/status'); const enable=!current.enabled; if(enable && !current.installed){ - if(!confirm(t('confirm.samba')))return; + if(!confirm('Samba installieren und /opt/picopy/internal als Netzwerkfreigabe PiCopy bereitstellen?\n\nDie Freigabe ist im Netzwerk lesbar erreichbar.'))return; } - flash('internal-share-flash','ok',enable?t('share.activating'):t('share.deactivating')); + flash('internal-share-flash','ok',enable?'Aktiviere Freigabe...':'Deaktiviere Freigabe...'); const r=await api('/internal-share','POST',{enabled:enable}); if(r.error){flash('internal-share-flash','err',r.error);return;} - flash('internal-share-flash','ok',enable?t('share.active_ok'):t('share.deactive_ok')); + flash('internal-share-flash','ok',enable?'✓ Freigabe aktiv':'✓ Freigabe deaktiviert'); updateInternalShareBox(r.status); } @@ -3329,19 +3081,19 @@ const expl={ port=cfg.source_ports&&cfg.source_ports[idx]?cfg.source_ports[idx].port:null; } const body=$('expl-body'), bread=$('expl-bread'); - if(!port){body.innerHTML='
'+t('expl.no_port')+'
';bread.innerHTML='';return;} + if(!port){body.innerHTML='
'+'Kein Port konfiguriert'+'
';bread.innerHTML='';return;} const dev=port==='__internal__' - ? {usb_port:'__internal__',label:t('Interner Speicher')||'Interner Speicher',device:'internal'} + ? {usb_port:'__internal__',label:'Interner Speicher',device:'internal'} : devs.find(d=>d.usb_port===port); - if(!dev){body.innerHTML='
'+t('expl.no_dev')+'
';bread.innerHTML='Port '+port+'';return;} - body.innerHTML='
'+t('expl.loading')+'
'; + if(!dev){body.innerHTML='
'+'Gerät nicht verbunden'+'
';bread.innerHTML='Port '+port+'';return;} + body.innerHTML='
'+'Lade...'+'
'; try{ const data=await api('/browse?port='+encodeURIComponent(port)+'&path='+encodeURIComponent(path)); if(data.error){body.innerHTML='
⚠ '+data.error+'
';return;} this.paths[this.role]=data.path||''; // role z.B. 'src_0', 'dst' this._bread(data.path||'',dev.label||dev.device); this._list(data.entries||[],data.path||''); - }catch(e){body.innerHTML='
'+t('expl.conn_err')+'
';} + }catch(e){body.innerHTML='
'+'Verbindungsfehler'+'
';} }, _bread(path,label){ const el=$('expl-bread'); @@ -3362,8 +3114,8 @@ const expl={ const par=cur.includes('/')?cur.substring(0,cur.lastIndexOf('/')):''; h+=`
<-..
`; } - if(!entries.length&&!cur){body.innerHTML='
'+t('expl.empty_drive')+'
';return;} - if(!entries.length){body.innerHTML=h+'
'+t('expl.empty_folder')+'
';return;} + if(!entries.length&&!cur){body.innerHTML='
'+'Laufwerk leer'+'
';return;} + if(!entries.length){body.innerHTML=h+'
'+'Ordner leer'+'
';return;} entries.forEach(e=>{ const ico=e.dir?'📁':fileIcon(e.name); const np=(cur?cur+'/':'')+e.name; @@ -3427,7 +3179,7 @@ async function poll(){ vp.style.display='none'; } else if(!v.installed){ ni.style.display='block'; pp.style.display='none'; ui.style.display='none'; - $('wg-status-sub').textContent=t('wg.not_installed')||'Nicht installiert'; + $('wg-status-sub').textContent='Nicht installiert'||'Nicht installiert'; vp.style.display='none'; if(v.pkg_error) flash('wg-flash','err',v.pkg_error); } else { @@ -3436,26 +3188,26 @@ async function poll(){ const bc=$('wg-btn-connect'),bd=$('wg-btn-disconnect'); if(v.connected){ vp.style.display='flex'; vdot.className='wdot c'; - vl.textContent=t('vpn.active'); vi.textContent=v.ip||''; - wgd.className='wdot c'; wgl.textContent=t('wifi.connected'); + 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):'')):''; bc.style.display='none'; bd.style.display=''; bd.disabled=false; $('wg-status-sub').textContent=v.ip||''; } else { vp.style.display=v.has_config?'flex':'none'; vdot.className='wdot d'; vl.textContent='VPN'; vi.textContent=''; - wgd.className='wdot d'; wgl.textContent=t('vpn.disconnected'); + wgd.className='wdot d'; wgl.textContent='Getrennt'; wgdet.textContent=v.error||''; bc.style.display=v.has_config?'':'none'; bc.disabled=false; bd.style.display='none'; - $('wg-status-sub').textContent=v.has_config?t('status.configured'):t('status.not_cfg'); + $('wg-status-sub').textContent=v.has_config?'Konfiguriert':'Nicht konfiguriert'; } } } // WiFi const wd=$('wdot'),wl=$('wifi-label'),wi=$('wifi-ip'); - if(w.mode==='client'){wd.className='wdot c';wl.textContent=w.ssid||t('wifi.connected');wi.textContent=w.ip||'';} - else if(w.mode==='ap'){wd.className='wdot a';wl.textContent=t('wifi.hotspot')+(w.ssid||'PiCopy');wi.textContent='10.42.0.1';} - else{wd.className='wdot d';wl.textContent=t('wifi.none');wi.textContent='';} + if(w.mode==='client'){wd.className='wdot c';wl.textContent=w.ssid||'Verbunden';wi.textContent=w.ip||'';} + else if(w.mode==='ap'){wd.className='wdot a';wl.textContent='Hotspot: '+(w.ssid||'PiCopy');wi.textContent='10.42.0.1';} + else{wd.className='wdot d';wl.textContent='Kein WLAN';wi.textContent='';} const qrBox=$('hotspot-qr-box'); if(qrBox){qrBox.style.display=w.mode==='ap'?'block':'none'; if(w.mode==='ap')drawHotspotQR();} // Copy @@ -3466,22 +3218,22 @@ async function poll(){ if(c.running){ const ph=c.phase||'copy'; if(ph==='verify'){ - tx.className='st-headline st-run'; tx.textContent=t('copy.verify')+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+t('copy.checked'); + 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=t('copy.delete'); + 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=t('copy.copying')+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+t('copy.files'); + pfiles.style.display=''; pfiles.textContent=c.done+' / '+c.total+' Dateien'; if(c.bytes_total>0){pbytes.style.display='';pbytes.textContent=fmtBytes(c.bytes_done)+' / '+fmtBytes(c.bytes_total);}else pbytes.style.display='none'; const e=fmtETA(c.eta_sec); eta.style.display=e?'':'none'; eta.textContent=e?'⏱ '+e:''; const s=fmtSpd(c.speed_bps); spd.style.display=s?'':'none'; spd.textContent=s?'⚡ '+s:''; @@ -3492,13 +3244,13 @@ async function poll(){ bS.style.display=''; bC.style.display='none'; cf.textContent=''; eta.style.display='none'; spd.style.display='none'; pfiles.style.display='none'; pbytes.style.display='none'; pp.style.display='none'; if(c.error){ - tx.className='st-headline st-err'; tx.textContent=t('copy.error_prefix')+c.error; + tx.className='st-headline st-err'; tx.textContent='Fehler: '+c.error; pf.className='prog-fill err'; pw.style.display='block'; pf.style.width='100%'; sum.textContent=''; time.textContent=''; }else if(c.last_copy && !_dismissed){ - tx.className='st-headline st-ok'; tx.textContent=t('copy.done'); + 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+t('copy.files')+' | '+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 @@ -3508,7 +3260,7 @@ async function poll(){ _autoDismissTimer=setTimeout(dismissStatus, remaining*1000); } }else{ - tx.className='st-headline st-idle'; tx.textContent=t('copy.ready'); + tx.className='st-headline st-idle'; tx.textContent='Bereit'; pw.style.display='none'; sum.textContent=''; time.textContent=''; $('st-dismiss').style.display='none'; } @@ -3546,7 +3298,7 @@ let _dismissed = false, _autoDismissTimer = null; function dismissStatus(){ _dismissed = true; if(_autoDismissTimer){ clearTimeout(_autoDismissTimer); _autoDismissTimer=null; } - $('st-text').className='st-headline st-idle'; $('st-text').textContent=t('copy.ready'); + $('st-text').className='st-headline st-idle'; $('st-text').textContent='Bereit'; $('prog-wrap').style.display='none'; $('st-summary').textContent=''; $('st-time').textContent=''; $('st-dismiss').style.display='none'; @@ -3569,9 +3321,9 @@ async function pollUpdate() { async function installUpdate() { const u = await api('/update/status'); const latest = (u.latest || '?'); - if (!confirm(t('confirm.update').replace('${v}',latest))) return; + if (!confirm('Update auf v${v} installieren?\n\nDas Web-Interface ist für ca. 10 Sekunden nicht erreichbar.'.replace('${v}',latest))) return; - $('upd-badge').innerHTML = t('update.installing'); + $('upd-badge').innerHTML = '↓ Installiere...'; $('upd-badge').style.pointerEvents = 'none'; try { @@ -3591,7 +3343,7 @@ async function installUpdate() { async function checkUpdate() { const btn = event.currentTarget; - btn.disabled = true; btn.innerHTML = '🔍 '+t('btn.checking'); + btn.disabled = true; btn.innerHTML = '🔍 '+'Prüfe...'; try { await api('/update/check', 'POST'); // Warten bis der Server-Check abgeschlossen ist (max 15 s, alle 500 ms) @@ -3604,26 +3356,26 @@ async function checkUpdate() { await pollUpdate(); // Badge sofort aktualisieren const fl = $('sys-update-flash'); if (u.available && u.latest) { - fl.className = 'flash warn'; fl.textContent = t('update.available').replace('${v}',u.latest); + fl.className = 'flash warn'; fl.textContent = 'Update v${v} verfügbar – über das Badge oben installieren.'.replace('${v}',u.latest); } else if (u.error) { - fl.className = 'flash err'; fl.textContent = t('update.error_prefix') + u.error; + fl.className = 'flash err'; fl.textContent = 'Fehler: ' + u.error; } else { - fl.className = 'flash ok'; fl.textContent = t('update.current'); + fl.className = 'flash ok'; fl.textContent = 'PiCopy ist aktuell.'; } fl.style.display = 'block'; if (fl.className.includes('ok')) setTimeout(() => fl.style.display = 'none', 3500); } catch(e) { const fl = $('sys-update-flash'); - fl.className = 'flash err'; fl.textContent = t('expl.conn_err'); fl.style.display = 'block'; + fl.className = 'flash err'; fl.textContent = 'Verbindungsfehler'; fl.style.display = 'block'; } finally { - btn.disabled = false; btn.innerHTML = '🔍 '+t('check_update'); + btn.disabled = false; btn.innerHTML = '🔍 '+'Nach Update suchen'; } } async function rebootDevice() { - if (!confirm(t('confirm.reboot'))) return; + 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 = '
'+t('reboot.wait')+'
'; + document.body.innerHTML = '
'+'↺ Gerät startet neu – bitte warten...'+'
'; setTimeout(async function waitForRestart() { try { await fetch('/api/update/status'); location.reload(); } catch(e) { setTimeout(waitForRestart, 2000); } @@ -3632,37 +3384,37 @@ async function rebootDevice() { // -- WireGuard VPN ------------------------------------------------------------- async function wgInstall(){ - if(!confirm(t('confirm.wg_install')))return; - flash('wg-flash','ok',t('wg.installing')); + if(!confirm('wireguard + wireguard-tools + openresolv 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(t('confirm.wg_uninstall')))return; - flash('wg-flash','ok',t('wg.removing')); + if(!confirm('WireGuard wirklich deinstallieren?\n\nDer aktive VPN-Tunnel wird vorher getrennt.\nDie Konfigurationsdatei bleibt erhalten.'))return; + 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',t('wg.connecting')); + flash('wg-flash','ok','Verbinde VPN...'); await api('/wireguard/connect','POST'); } async function wgDisconnect(){ $('wg-btn-disconnect').disabled=true; - flash('wg-flash','ok',t('wg.disconnecting')); + flash('wg-flash','ok','Trenne VPN...'); const r=await api('/wireguard/disconnect','POST'); - if(!r.ok) flash('wg-flash','err',t('wg.disc_failed')); + if(!r.ok) flash('wg-flash','err','Trennen fehlgeschlagen'); } async function wgSaveConfig(){ const content=$('wg-config').value.trim(); - if(!content){flash('wg-flash','err',t('wg.cfg_empty'));return;} - if(!content.includes('[Interface]')){flash('wg-flash','err',t('wg.invalid_cfg'));return;} + if(!content){flash('wg-flash','err','Konfiguration ist leer');return;} + if(!content.includes('[Interface]')){flash('wg-flash','err','[Interface] fehlt');return;} const auto=$('wg-auto').checked; - flash('wg-flash','ok',t('wg.saving')); + 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',t('wg.saved')); + flash('wg-flash','ok','✓ Konfiguration gespeichert'); } async function loadWgConfig(){ try{ diff --git a/version.txt b/version.txt index 61098d2..3c79fcb 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.55 \ No newline at end of file +1.0.56 \ No newline at end of file