feat: Mehrsprachige Unterstützung durch Datenattribute für Übersetzungen verbessert; Versionsnummer auf 1.0.55 erhöht

This commit is contained in:
2026-05-09 15:12:33 +02:00
parent 8ca18f785e
commit f16c9c86eb
2 changed files with 77 additions and 34 deletions

109
app.py
View File

@@ -2155,7 +2155,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<div class="logo-dot"></div> <div class="logo-dot"></div>
PiCopy PiCopy
</div> </div>
<div id="upd-badge" class="upd-badge" onclick="installUpdate()" title="Klicken zum Installieren"> <div id="upd-badge" class="upd-badge" onclick="installUpdate()" title="Klicken zum Installieren" data-title-i18n="title.install">
&#8593; <span id="upd-version"></span> verfügbar &#8593; <span id="upd-version"></span> verfügbar
</div> </div>
<div class="topbar-wifi"> <div class="topbar-wifi">
@@ -2182,7 +2182,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<div class="card-icon blue">▶</div> <div class="card-icon blue">▶</div>
<span class="card-title">Kopierstatus</span> <span class="card-title">Kopierstatus</span>
<span class="card-sub" id="st-time"></span> <span class="card-sub" id="st-time"></span>
<button id="st-dismiss" onclick="dismissStatus()" title="Meldung schließen" style="display:none;margin-left:.5rem;background:transparent;border:1px solid var(--brd2);color:var(--sub);border-radius:.35rem;padding:.18rem .45rem;cursor:pointer;font-size:.8rem;line-height:1;transition:.15s" onmouseover="this.style.color='var(--txt)'" onmouseout="this.style.color='var(--sub)'">✕</button> <button id="st-dismiss" onclick="dismissStatus()" title="Meldung schließen" data-title-i18n="title.dismiss" style="display:none;margin-left:.5rem;background:transparent;border:1px solid var(--brd2);color:var(--sub);border-radius:.35rem;padding:.18rem .45rem;cursor:pointer;font-size:.8rem;line-height:1;transition:.15s" onmouseover="this.style.color='var(--txt)'" onmouseout="this.style.color='var(--sub)'">✕</button>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="st-headline st-idle" id="st-text">Bereit</div> <div class="st-headline st-idle" id="st-text">Bereit</div>
@@ -2311,7 +2311,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<div class="expl-bar"> <div class="expl-bar">
<div id="src-tabs" style="display:contents"></div> <div id="src-tabs" style="display:contents"></div>
<button class="etab" id="etab-dst" onclick="expl.switchRole('dst')">⬇ Ziel</button> <button class="etab" id="etab-dst" onclick="expl.switchRole('dst')">⬇ Ziel</button>
<button class="expl-reload" onclick="expl.reload()" title="Neu laden">&#8635;</button> <button class="expl-reload" onclick="expl.reload()" title="Neu laden" data-title-i18n="title.reload">&#8635;</button>
</div> </div>
<div class="expl-bread" id="expl-bread"></div> <div class="expl-bread" id="expl-bread"></div>
<div class="expl-scroll" id="expl-body"> <div class="expl-scroll" id="expl-body">
@@ -2461,8 +2461,8 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="tab-strip"> <div class="tab-strip">
<div class="tab on" onclick="swTab('tc','ta')">Heimnetz</div> <div class="tab on" data-tab="tc" onclick="swTab('tc','ta')" data-i18n="Heimnetz">Heimnetz</div>
<div class="tab" onclick="swTab('ta','tc')">Hotspot (AP)</div> <div class="tab" data-tab="ta" onclick="swTab('ta','tc')" data-i18n="Hotspot (AP)">Hotspot (AP)</div>
</div> </div>
<div id="tc" class="tpane on"> <div id="tc" class="tpane on">
<div style="font-size:.8rem;color:var(--sub);margin-bottom:.75rem;line-height:1.5">Heimnetz für die Router-Verbindung. Ohne Verbindung startet PiCopy automatisch einen eigenen Hotspot.</div> <div style="font-size:.8rem;color:var(--sub);margin-bottom:.75rem;line-height:1.5">Heimnetz für die Router-Verbindung. Ohne Verbindung startet PiCopy automatisch einen eigenen Hotspot.</div>
@@ -2470,7 +2470,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<label>Netzwerk (SSID)</label> <label>Netzwerk (SSID)</label>
<div style="display:flex;gap:.4rem"> <div style="display:flex;gap:.4rem">
<input type="text" id="w-ssid" placeholder="WLAN-Name" style="flex:1"> <input type="text" id="w-ssid" placeholder="WLAN-Name" style="flex:1">
<button class="btn ghost" onclick="scanNets()" title="Netzwerke suchen">🔍</button> <button class="btn ghost" onclick="scanNets()" title="Netzwerke suchen" data-title-i18n="title.scan_nets">🔍</button>
</div> </div>
</div> </div>
<div id="net-list" class="net-list" style="display:none"></div> <div id="net-list" class="net-list" style="display:none"></div>
@@ -2525,7 +2525,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<span style="font-size:1.3rem;flex-shrink:0" id="wg-pkg-icon">⏳</span> <span style="font-size:1.3rem;flex-shrink:0" id="wg-pkg-icon">⏳</span>
<div> <div>
<div style="font-weight:600;font-size:.87rem" id="wg-pkg-title">Installiere WireGuard...</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-size:.76rem;color:var(--sub);margin-top:.1rem" data-i18n="wg.apt_running">apt-get läuft bitte warten (bis 60 s)</div>
</div> </div>
</div> </div>
</div> </div>
@@ -2556,7 +2556,7 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<label class="tog"><input type="checkbox" id="wg-auto"><span>Beim Start automatisch verbinden</span></label> <label class="tog"><input type="checkbox" id="wg-auto"><span>Beim Start automatisch verbinden</span></label>
<div class="btn-row"> <div class="btn-row">
<button class="btn pri" onclick="wgSaveConfig()">✓&nbsp;Konfiguration speichern</button> <button class="btn pri" onclick="wgSaveConfig()">✓&nbsp;Konfiguration speichern</button>
<button class="btn danger" onclick="wgUninstall()" style="margin-left:auto" title="wireguard-Paket entfernen">✕&nbsp;Deinstallieren</button> <button class="btn danger" onclick="wgUninstall()" style="margin-left:auto" title="wireguard-Paket entfernen" data-title-i18n="title.wg_rm">✕&nbsp;Deinstallieren</button>
</div> </div>
</div> </div>
@@ -2649,7 +2649,28 @@ const I18N = {
'wg.connecting':'Verbinde VPN...', 'wg.disconnecting':'Trenne VPN...', 'wg.connecting':'Verbinde VPN...', 'wg.disconnecting':'Trenne VPN...',
'wg.disc_failed':'Trennen fehlgeschlagen', 'wg.cfg_empty':'Konfiguration ist leer', 'wg.disc_failed':'Trennen fehlgeschlagen', 'wg.cfg_empty':'Konfiguration ist leer',
'wg.saving':'Speichere...', 'wg.saved':'✓ Konfiguration gespeichert', '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.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':'&#8595; Installiere...',
'btn.connect_load':'&#128279;&nbsp;Verbinden &amp; 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: { en: {
'wifi.connecting':'Connecting...', 'wifi.none':'No Wi-Fi', 'wifi.connected':'Connected', 'wifi.connecting':'Connecting...', 'wifi.none':'No Wi-Fi', 'wifi.connected':'Connected',
@@ -2697,7 +2718,28 @@ const I18N = {
'wg.connecting':'Connecting VPN...', 'wg.disconnecting':'Disconnecting VPN...', 'wg.connecting':'Connecting VPN...', 'wg.disconnecting':'Disconnecting VPN...',
'wg.disc_failed':'Disconnect failed', 'wg.cfg_empty':'Configuration is empty', 'wg.disc_failed':'Disconnect failed', 'wg.cfg_empty':'Configuration is empty',
'wg.saving':'Saving...', 'wg.saved':'✓ Configuration saved', '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.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':'&#8595; Installing...',
'btn.connect_load':'&#128279;&nbsp;Connect &amp; 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 = { const STATIC_EN = {
@@ -2789,6 +2831,7 @@ function applyLang(){
'ap-pw':['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];}); 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){ async function setLang(lang){
cfg.ui_lang=lang; cfg.ui_lang=lang;
@@ -2877,8 +2920,8 @@ function qrFormatBits(ecl,mask){
// -- Tabs --------------------------------------------------------------------- // -- Tabs ---------------------------------------------------------------------
function swTab(show,hide){ function swTab(show,hide){
$(show).classList.add('on'); $(hide).classList.remove('on'); $(show).classList.add('on'); $(hide).classList.remove('on');
document.querySelectorAll('.tab').forEach(t=> document.querySelectorAll('.tab').forEach(tab=>
t.classList.toggle('on',t.textContent.trim().startsWith(show==='tc'?'Heim':'Hot')) tab.classList.toggle('on', tab.dataset.tab===show)
); );
} }
@@ -3158,7 +3201,7 @@ function renderUTs(){
</div> </div>
<div class="ut-acts"> <div class="ut-acts">
<button class="btn sm ghost" id="ut-test-${t.id}" onclick="utTest('${t.id}')">&#128269; Test</button> <button class="btn sm ghost" id="ut-test-${t.id}" onclick="utTest('${t.id}')">&#128269; Test</button>
<button class="btn sm ${t.enabled?'grn':'ghost'}" onclick="utToggle('${t.id}')">${t.enabled?'Aktiv':'Inaktiv'}</button> <button class="btn sm ${t.enabled?'grn':'ghost'}" onclick="utToggle('${t.id}')">${t.enabled?t('status.active'):t('status.inactive')}</button>
<button class="btn sm danger" onclick="utDel('${t.id}','${t.name}')">✕</button> <button class="btn sm danger" onclick="utDel('${t.id}','${t.name}')">✕</button>
</div> </div>
<div id="ut-test-result-${t.id}" style="display:none;font-size:.76rem;margin-top:.35rem;padding:.3rem .5rem;border-radius:.35rem"></div> <div id="ut-test-result-${t.id}" style="display:none;font-size:.76rem;margin-top:.35rem;padding:.3rem .5rem;border-radius:.35rem"></div>
@@ -3167,7 +3210,7 @@ function renderUTs(){
function utToggleForm(){ function utToggleForm(){
const f=$('ut-form'),b=$('ut-add-btn'),show=f.style.display==='none'; const f=$('ut-form'),b=$('ut-add-btn'),show=f.style.display==='none';
f.style.display=show?'block':'none'; f.style.display=show?'block':'none';
b.innerHTML=show?'✕ Abbrechen':'+ NAS-Ziel hinzufügen'; b.innerHTML=show?t('ut.toggle_close'):t('ut.toggle_open');
if(show){ if(show){
$('ut-step1').style.display=''; $('ut-step2').style.display='none'; $('ut-step1').style.display=''; $('ut-step2').style.display='none';
['ut-host','ut-user','ut-pass','ut-name'].forEach(id=>{$(id).value='';}); ['ut-host','ut-user','ut-pass','ut-name'].forEach(id=>{$(id).value='';});
@@ -3180,12 +3223,12 @@ async function utConnect(){
const host=$('ut-host').value.trim(); 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',t('flash.no_server'));return;}
const btn=$('ut-connect-btn'); const btn=$('ut-connect-btn');
btn.disabled=true; btn.textContent='Verbinde...'; btn.disabled=true; btn.textContent=t('flash.connect_btn');
$('ut-form-flash').style.display='none'; $('ut-form-flash').style.display='none';
const r=await api('/upload/browse','POST',{ const r=await api('/upload/browse','POST',{
host, user:$('ut-user').value.trim(), pass:$('ut-pass').value host, user:$('ut-user').value.trim(), pass:$('ut-pass').value
}); });
btn.disabled=false; btn.innerHTML='&#128279;&nbsp;Verbinden &amp; Freigaben laden'; btn.disabled=false; btn.innerHTML=t('btn.connect_load');
if(r.error){flash('ut-form-flash','err',''+r.error);return;} 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',t('flash.no_shares'));return;}
_utConn={host, user:$('ut-user').value.trim(), pass:$('ut-pass').value}; _utConn={host, user:$('ut-user').value.trim(), pass:$('ut-pass').value};
@@ -3239,7 +3282,7 @@ async function updateInternalShareBox(state=null){
if(!$('internal-share-box'))return; if(!$('internal-share-box'))return;
const s=state||await api('/internal-share/status'); const s=state||await api('/internal-share/status');
const btn=$('internal-share-btn'), detail=$('internal-share-detail'); const btn=$('internal-share-btn'), detail=$('internal-share-detail');
const free=s.free!=null?fmtBytes(s.free)+' frei':''; const free=s.free!=null?fmtBytes(s.free)+t('size.free'):'';
if(s.pkg_running){ if(s.pkg_running){
btn.disabled=true; btn.textContent=t('share.install'); btn.disabled=true; btn.textContent=t('share.install');
detail.textContent=t('share.installing')+free; detail.textContent=t('share.installing')+free;
@@ -3248,7 +3291,7 @@ async function updateInternalShareBox(state=null){
btn.disabled=false; btn.disabled=false;
btn.textContent=s.enabled?t('share.on'):t('share.off'); btn.textContent=s.enabled?t('share.on'):t('share.off');
const status=s.enabled const status=s.enabled
? ((s.active?'Aktiv':'Konfiguriert')+' | \\\\'+(location.hostname||'picopy')+'\\PiCopy') ? ((s.active?t('status.active'):t('status.configured'))+' | \\\\'+(location.hostname||'picopy')+'\\PiCopy')
: t('share.inactive'); : t('share.inactive');
detail.textContent=status+(free?' | '+free:''); detail.textContent=status+(free?' | '+free:'');
} }
@@ -3257,12 +3300,12 @@ async function toggleInternalShare(){
const current=await api('/internal-share/status'); const current=await api('/internal-share/status');
const enable=!current.enabled; const enable=!current.enabled;
if(enable && !current.installed){ if(enable && !current.installed){
if(!confirm('Samba installieren und /opt/picopy/internal als Netzwerkfreigabe PiCopy bereitstellen?\n\nDie Freigabe ist lesbar im Netzwerk erreichbar.'))return; if(!confirm(t('confirm.samba')))return;
} }
flash('internal-share-flash','ok',enable?'Aktiviere Freigabe...':'Deaktiviere Freigabe...'); flash('internal-share-flash','ok',enable?t('share.activating'):t('share.deactivating'));
const r=await api('/internal-share','POST',{enabled:enable}); const r=await api('/internal-share','POST',{enabled:enable});
if(r.error){flash('internal-share-flash','err',r.error);return;} if(r.error){flash('internal-share-flash','err',r.error);return;}
flash('internal-share-flash','ok',enable?'✓ Freigabe aktiv':'✓ Freigabe deaktiviert'); flash('internal-share-flash','ok',enable?t('share.active_ok'):t('share.deactive_ok'));
updateInternalShareBox(r.status); updateInternalShareBox(r.status);
} }
@@ -3377,14 +3420,14 @@ async function poll(){
const ni=$('wg-not-installed'),pp=$('wg-pkg-progress'),ui=$('wg-installed-ui'); const ni=$('wg-not-installed'),pp=$('wg-pkg-progress'),ui=$('wg-installed-ui');
if(v.pkg_running){ if(v.pkg_running){
ni.style.display='none'; pp.style.display='block'; ui.style.display='none'; ni.style.display='none'; pp.style.display='block'; ui.style.display='none';
const act=v.pkg_action==='remove'?'Deinstalliere':'Installiere'; const act=t(v.pkg_action==='remove'?'wg.action_remove':'wg.action_install');
$('wg-pkg-title').textContent=act+' WireGuard...'; $('wg-pkg-title').textContent=act+' WireGuard...';
$('wg-pkg-icon').textContent=''; $('wg-pkg-icon').textContent='';
$('wg-status-sub').textContent=act+'...'; $('wg-status-sub').textContent=act+'...';
vp.style.display='none'; vp.style.display='none';
} else if(!v.installed){ } else if(!v.installed){
ni.style.display='block'; pp.style.display='none'; ui.style.display='none'; ni.style.display='block'; pp.style.display='none'; ui.style.display='none';
$('wg-status-sub').textContent='Nicht installiert'; $('wg-status-sub').textContent=t('wg.not_installed')||'Nicht installiert';
vp.style.display='none'; vp.style.display='none';
if(v.pkg_error) flash('wg-flash','err',v.pkg_error); if(v.pkg_error) flash('wg-flash','err',v.pkg_error);
} else { } else {
@@ -3404,7 +3447,7 @@ async function poll(){
wgd.className='wdot d'; wgl.textContent=t('vpn.disconnected'); wgd.className='wdot d'; wgl.textContent=t('vpn.disconnected');
wgdet.textContent=v.error||''; wgdet.textContent=v.error||'';
bc.style.display=v.has_config?'':'none'; bc.disabled=false; bd.style.display='none'; bc.style.display=v.has_config?'':'none'; bc.disabled=false; bd.style.display='none';
$('wg-status-sub').textContent=v.has_config?'Konfiguriert':'Nicht konfiguriert'; $('wg-status-sub').textContent=v.has_config?t('status.configured'):t('status.not_cfg');
} }
} }
} }
@@ -3449,13 +3492,13 @@ async function poll(){
bS.style.display=''; bC.style.display='none'; cf.textContent=''; 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'; eta.style.display='none'; spd.style.display='none'; pfiles.style.display='none'; pbytes.style.display='none'; pp.style.display='none';
if(c.error){ if(c.error){
tx.className='st-headline st-err'; tx.textContent='Fehler: '+c.error; tx.className='st-headline st-err'; tx.textContent=t('copy.error_prefix')+c.error;
pf.className='prog-fill err'; pw.style.display='block'; pf.style.width='100%'; pf.className='prog-fill err'; pw.style.display='block'; pf.style.width='100%';
sum.textContent=''; time.textContent=''; sum.textContent=''; time.textContent='';
}else if(c.last_copy && !_dismissed){ }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=t('copy.done');
pf.className='prog-fill done'; pw.style.display='block'; pf.style.width='100%'; 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+t('copy.files')+' | '+fmtBytes(c.bytes_total);
time.textContent=new Date(c.last_copy).toLocaleString('de-DE'); time.textContent=new Date(c.last_copy).toLocaleString('de-DE');
$('st-dismiss').style.display=''; $('st-dismiss').style.display='';
// Auto-dismiss nach 5 Minuten // Auto-dismiss nach 5 Minuten
@@ -3503,7 +3546,7 @@ let _dismissed = false, _autoDismissTimer = null;
function dismissStatus(){ function dismissStatus(){
_dismissed = true; _dismissed = true;
if(_autoDismissTimer){ clearTimeout(_autoDismissTimer); _autoDismissTimer=null; } if(_autoDismissTimer){ clearTimeout(_autoDismissTimer); _autoDismissTimer=null; }
$('st-text').className='st-headline st-idle'; $('st-text').textContent='Bereit'; $('st-text').className='st-headline st-idle'; $('st-text').textContent=t('copy.ready');
$('prog-wrap').style.display='none'; $('prog-wrap').style.display='none';
$('st-summary').textContent=''; $('st-time').textContent=''; $('st-summary').textContent=''; $('st-time').textContent='';
$('st-dismiss').style.display='none'; $('st-dismiss').style.display='none';
@@ -3528,7 +3571,7 @@ async function installUpdate() {
const latest = (u.latest || '?'); const latest = (u.latest || '?');
if (!confirm(t('confirm.update').replace('${v}',latest))) return; if (!confirm(t('confirm.update').replace('${v}',latest))) return;
$('upd-badge').innerHTML = '&#8595; Installiere...'; $('upd-badge').innerHTML = t('update.installing');
$('upd-badge').style.pointerEvents = 'none'; $('upd-badge').style.pointerEvents = 'none';
try { try {
@@ -3548,7 +3591,7 @@ async function installUpdate() {
async function checkUpdate() { async function checkUpdate() {
const btn = event.currentTarget; const btn = event.currentTarget;
btn.disabled = true; btn.innerHTML = '&#128269;&nbsp;Prüfe...'; btn.disabled = true; btn.innerHTML = '&#128269;&nbsp;'+t('btn.checking');
try { try {
await api('/update/check', 'POST'); await api('/update/check', 'POST');
// Warten bis der Server-Check abgeschlossen ist (max 15 s, alle 500 ms) // Warten bis der Server-Check abgeschlossen ist (max 15 s, alle 500 ms)
@@ -3561,19 +3604,19 @@ async function checkUpdate() {
await pollUpdate(); // Badge sofort aktualisieren await pollUpdate(); // Badge sofort aktualisieren
const fl = $('sys-update-flash'); const fl = $('sys-update-flash');
if (u.available && u.latest) { 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 = t('update.available').replace('${v}',u.latest);
} else if (u.error) { } else if (u.error) {
fl.className = 'flash err'; fl.textContent = 'Fehler: ' + u.error; fl.className = 'flash err'; fl.textContent = t('update.error_prefix') + u.error;
} else { } else {
fl.className = 'flash ok'; fl.textContent = 'PiCopy ist aktuell.'; fl.className = 'flash ok'; fl.textContent = t('update.current');
} }
fl.style.display = 'block'; fl.style.display = 'block';
if (fl.className.includes('ok')) setTimeout(() => fl.style.display = 'none', 3500); if (fl.className.includes('ok')) setTimeout(() => fl.style.display = 'none', 3500);
} catch(e) { } catch(e) {
const fl = $('sys-update-flash'); const fl = $('sys-update-flash');
fl.className = 'flash err'; fl.textContent = 'Verbindung fehlgeschlagen.'; fl.style.display = 'block'; fl.className = 'flash err'; fl.textContent = t('expl.conn_err'); fl.style.display = 'block';
} finally { } finally {
btn.disabled = false; btn.innerHTML = '🔍&nbsp;Nach Update suchen'; btn.disabled = false; btn.innerHTML = '🔍&nbsp;'+t('check_update');
} }
} }
@@ -3614,7 +3657,7 @@ async function wgDisconnect(){
async function wgSaveConfig(){ async function wgSaveConfig(){
const content=$('wg-config').value.trim(); const content=$('wg-config').value.trim();
if(!content){flash('wg-flash','err',t('wg.cfg_empty'));return;} if(!content){flash('wg-flash','err',t('wg.cfg_empty'));return;}
if(!content.includes('[Interface]')){flash('wg-flash','err','[Interface] fehlt');return;} if(!content.includes('[Interface]')){flash('wg-flash','err',t('wg.invalid_cfg'));return;}
const auto=$('wg-auto').checked; const auto=$('wg-auto').checked;
flash('wg-flash','ok',t('wg.saving')); flash('wg-flash','ok',t('wg.saving'));
const r=await api('/wireguard/config','POST',{content,auto}); const r=await api('/wireguard/config','POST',{content,auto});

View File

@@ -1 +1 @@
1.0.54 1.0.55