v0.247.0: i18n spike — the dashboard can speak English, Hungarian byte-identical
gates / gates (push) Successful in 19s
gates / gates (push) Successful in 19s
Message bundles (internal/i18n) expanded into templates before parsing, one template set per language. Launcher, /backups, /apps/<slug> and the layout converted; household language setting, POST /settings/language, ?lang= override, report field. Parity test against fixtures captured from unconverted templates; copy gates read templates expanded; new i18n_missing_gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -3,27 +3,27 @@
|
||||
|
||||
<div class="page-header">
|
||||
<div style="display:flex;align-items:center;gap:1rem">
|
||||
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
|
||||
<a href="/stacks" class="btn btn-sm btn-outline">{{T "app_info.alkalmazasok"}}</a>
|
||||
<h2>{{.Meta.DisplayName}}</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
{{if .Stack.Deployed}}
|
||||
<span class="stack-state-badge state-{{stateColor .Stack.State}}">{{stateLabel .Stack.State}}</span>
|
||||
{{if .Stack.Orphaned}}<span class="badge badge-orphaned">Elavult</span>{{end}}
|
||||
{{if .Stack.Orphaned}}<span class="badge badge-orphaned">{{T "app_info.elavult"}}</span>{{end}}
|
||||
{{template "meta_badge" (lifecycleBadge .Meta)}}
|
||||
{{template "meta_badge" (updateBadge .Stack)}}
|
||||
{{if .EffectiveSubdomain}}<a href="https://{{.EffectiveSubdomain}}.{{.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>{{end}}
|
||||
<a href="/stacks/{{.Stack.Name}}/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
{{if .EffectiveSubdomain}}<a href="https://{{.EffectiveSubdomain}}.{{.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-outline">{{T "app_info.megnyitas"}}</a>{{end}}
|
||||
<a href="/stacks/{{.Stack.Name}}/logs" class="btn btn-sm btn-outline">{{T "app_info.naplo"}}</a>
|
||||
{{if .Stack.Orphaned}}
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Stack.Name}}')">Törlés</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Stack.Name}}')">{{T "app_info.torles"}}</button>
|
||||
{{else}}
|
||||
<a href="/stacks/{{.Stack.Name}}/export" class="btn btn-sm btn-outline">Exportálás</a>
|
||||
<a href="/stacks/{{.Stack.Name}}/deploy" class="btn btn-sm btn-outline">Beállítások</a>
|
||||
<a href="/stacks/{{.Stack.Name}}/export" class="btn btn-sm btn-outline">{{T "app_info.exportalas"}}</a>
|
||||
<a href="/stacks/{{.Stack.Name}}/deploy" class="btn btn-sm btn-outline">{{T "app_info.beallitasok"}}</a>
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{/* Server-side the deploy endpoint refuses a non-installable template; hide the button
|
||||
so the page never offers an action that would be rejected. */}}
|
||||
{{if canInstall .Meta}}<a href="/stacks/{{.Stack.Name}}/deploy" class="btn btn-sm btn-primary" onclick="return checkBeforeDeploy(event, '{{.Stack.Name}}')">Telepítés</a>{{end}}
|
||||
{{if canInstall .Meta}}<a href="/stacks/{{.Stack.Name}}/deploy" class="btn btn-sm btn-primary" onclick="return checkBeforeDeploy(event, '{{.Stack.Name}}')">{{T "app_info.telepites"}}</a>{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,15 +31,15 @@
|
||||
{{if .Stack.Updating}}
|
||||
<div class="alert alert-info" style="margin-top:1rem" data-update-phase="{{.Stack.UpdatePhase}}">{{.Stack.UpdatePhaseLabel}}</div>
|
||||
{{else if .Stack.HoldReason}}
|
||||
<div class="alert alert-error" style="margin-top:1rem" data-held="true">{{.Stack.HoldReason}} <a href="/backups/apps" class="btn btn-sm btn-outline">Mentések</a></div>
|
||||
<div class="alert alert-error" style="margin-top:1rem" data-held="true">{{.Stack.HoldReason}} <a href="/backups/apps" class="btn btn-sm btn-outline">{{T "app_info.mentesek"}}</a></div>
|
||||
{{else if .Stack.UpdateError}}
|
||||
<div class="alert alert-warning" style="margin-top:1rem" data-update-error="true">{{.Stack.UpdateError}}</div>
|
||||
{{end}}
|
||||
|
||||
{{if .MissingStorageLabel}}
|
||||
<div class="alert alert-warning" style="margin-top:1rem">
|
||||
<strong><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> Hiányzó tárhely: {{.MissingStorageLabel}}</strong><br>
|
||||
Ennek az alkalmazásnak az adattárolója jelenleg nem elérhető, ezért le van állítva. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat egy másik tárhelyre.
|
||||
<strong><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "app_info.hianyzo_tarhely"}} {{.MissingStorageLabel}}</strong><br>
|
||||
{{T "app_info.ennek_az_alkalmazasnak_az_adattaroloja"}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -57,14 +57,13 @@
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~{{.Meta.Resources.MemRequest}} RAM</span>
|
||||
<span class="meta-badge">{{.Meta.Category}}</span>
|
||||
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge meta-badge-warn">HDD szükséges</span>{{end}}
|
||||
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{else}}<span class="meta-badge meta-badge-warn">Csak x86</span>{{end}}
|
||||
{{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">Magyar felület</span>{{end}}
|
||||
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge meta-badge-warn">{{T "app_info.hdd_szukseges"}}</span>{{end}}
|
||||
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">{{T "app_info.pi_kompatibilis"}}</span>{{else}}<span class="meta-badge meta-badge-warn">{{T "app_info.csak_x86"}}</span>{{end}}
|
||||
{{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">{{T "app_info.magyar_felulet"}}</span>{{end}}
|
||||
</div>
|
||||
{{if .Meta.IsAbandoned}}
|
||||
<div class="alert alert-warning" style="margin-top:.75rem">
|
||||
Az alkalmazás fejlesztője felhagyott a fejlesztéssel. A telepített verzió továbbra is használható,
|
||||
de frissítések és biztonsági javítások már nem érkeznek hozzá.
|
||||
{{T "app_info.az_alkalmazas_fejlesztoje_felhagyott_a"}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -82,8 +81,8 @@
|
||||
|
||||
{{if .DataPathCards}}
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Hova tegyem a fájlokat?</h3>
|
||||
<p class="form-hint">Ezeket a mappákat a Fájlkezelőben éred el. Előfordulhat, hogy először be kell jelentkezned a Fájlkezelőbe.</p>
|
||||
<h3>{{T "app_info.hova_tegyem_a_fajlokat"}}</h3>
|
||||
<p class="form-hint">{{T "app_info.ezeket_a_mappakat_a_fajlkezeloben"}}</p>
|
||||
<div class="datapath-list">
|
||||
{{range .DataPathCards}}
|
||||
<div class="datapath-row">
|
||||
@@ -100,21 +99,21 @@
|
||||
|
||||
{{if and .Stack.Deployed .MigrateTargets}}
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Áthelyezés másik tárhelyre</h3>
|
||||
<p class="form-hint">Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át. Az alkalmazás az áthelyezés alatt rövid időre leáll, az adatok pedig csak az ellenőrzés és a sikeres újraindítás után törlődnek a régi helyről.</p>
|
||||
<h3>{{T "app_info.athelyezes_masik_tarhelyre"}}</h3>
|
||||
<p class="form-hint">{{T "app_info.ennek_az_alkalmazasnak_az_adatait"}}</p>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
|
||||
<select id="app-migrate-target" class="btn btn-sm btn-outline">
|
||||
<option value="">Válassz céltárhelyet…</option>
|
||||
<option value="">{{T "app_info.valassz_celtarhelyet"}}</option>
|
||||
{{range .MigrateTargets}}<option value="{{.Path}}">{{.Label}} ({{.Path}})</option>{{end}}
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'{{.Stack.Name}}','{{.Meta.DisplayName}}')">Áthelyezés</button>
|
||||
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'{{.Stack.Name}}','{{.Meta.DisplayName}}')">{{T "app_info.athelyezes"}}</button>
|
||||
</div>
|
||||
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
|
||||
</div>
|
||||
<script>
|
||||
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
|
||||
function appMigRender(job){
|
||||
var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'};
|
||||
var names={stop:'{{T "app_info.leallitas"}}',copy:'{{T "app_info.adatok_masolasa"}}',verify:'{{T "app_info.ellenorzes"}}',flip:'{{T "app_info.ujratelepites"}}',redeploy:'{{T "app_info.ujratelepites"}}',cleanup:'{{T "app_info.regi_adatok_torlese"}}'};
|
||||
var s=names[job.phase]||job.phase;
|
||||
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
|
||||
return s+'…';
|
||||
@@ -125,10 +124,10 @@ function appMigWatch(){
|
||||
function tick(){
|
||||
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
|
||||
var job=d.data&&d.data.job;
|
||||
if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; }
|
||||
if(!job){ if(panel) panel.textContent='{{T "app_info.nincs_folyamatban_athelyezes"}}'; return; }
|
||||
if(panel) panel.innerHTML=appMigRender(job);
|
||||
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
|
||||
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A régi adatok érintetlenek.'; return; }
|
||||
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>{{T "app_info.kesz"}}</strong>'; setTimeout(function(){location.reload();},1500); return; }
|
||||
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">{{T "app_info.megszakadt"}} '+(job.error||'')+'</strong><br>{{T "app_info.a_regi_adatok_erintetlenek"}}'; return; }
|
||||
setTimeout(tick,1500);
|
||||
}).catch(function(){ setTimeout(tick,2000); });
|
||||
}
|
||||
@@ -137,11 +136,11 @@ function appMigWatch(){
|
||||
function appMigrate(btn,app,label){
|
||||
var sel=document.getElementById('app-migrate-target');
|
||||
var target=sel?sel.value:'';
|
||||
if(!target){ showAlert('Válassz céltárhelyet.'); return; }
|
||||
felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'? Az alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.',function(){
|
||||
if(!target){ showAlert('{{T "app_info.valassz_celtarhelyet_2"}}'); return; }
|
||||
felhomConfirm(btn,'{{T "app_info.athelyezed_a_z"}} '+label+' {{T "app_info.adatait_ide"}} '+target+'{{T "app_info.az_alkalmazas_rovid_idore_leall"}}',function(){
|
||||
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
|
||||
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } })
|
||||
.catch(function(e){ showAlert('Hiba: '+e); });
|
||||
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('{{T "app_info.hiba"}} '+(d.error||'{{T "app_info.ismeretlen"}}')); } })
|
||||
.catch(function(e){ showAlert('{{T "app_info.hiba"}} '+e); });
|
||||
});
|
||||
}
|
||||
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
|
||||
@@ -152,7 +151,7 @@ function appMigrate(btn,app,label){
|
||||
<div class="app-info-grid">
|
||||
{{if .AppInfo.UseCases}}
|
||||
<div class="app-info-card">
|
||||
<h3>Mire használható?</h3>
|
||||
<h3>{{T "app_info.mire_hasznalhato"}}</h3>
|
||||
<ul class="app-info-list">
|
||||
{{range .AppInfo.UseCases}}<li>{{.}}</li>{{end}}
|
||||
</ul>
|
||||
@@ -161,7 +160,7 @@ function appMigrate(btn,app,label){
|
||||
|
||||
{{if .AppInfo.FirstSteps}}
|
||||
<div class="app-info-card">
|
||||
<h3>Első lépések</h3>
|
||||
<h3>{{T "app_info.elso_lepesek"}}</h3>
|
||||
<ol class="app-info-list">
|
||||
{{range .AppInfo.FirstSteps}}<li>{{.}}</li>{{end}}
|
||||
</ol>
|
||||
@@ -170,7 +169,7 @@ function appMigrate(btn,app,label){
|
||||
|
||||
{{if .AppInfo.Prerequisites}}
|
||||
<div class="app-info-card">
|
||||
<h3>Előfeltételek</h3>
|
||||
<h3>{{T "app_info.elofeltetelek"}}</h3>
|
||||
<ul class="app-info-list">
|
||||
{{range .AppInfo.Prerequisites}}<li>{{.}}</li>{{end}}
|
||||
</ul>
|
||||
@@ -179,51 +178,51 @@ function appMigrate(btn,app,label){
|
||||
|
||||
{{if .InitialCreds}}
|
||||
<div class="app-info-card">
|
||||
<h3>Kezdeti belépési adatok</h3>
|
||||
<h3>{{T "app_info.kezdeti_belepesi_adatok"}}</h3>
|
||||
<table class="initcred-table">
|
||||
{{if .InitialCreds.Username}}
|
||||
<tr>
|
||||
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">Felhasználónév</td>
|
||||
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">{{T "app_info.felhasznalonev"}}</td>
|
||||
<td><code class="initcred-user" style="user-select:all">{{.InitialCreds.Username}}</code></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{if .InitialCredsOperatorSet}}
|
||||
<tr>
|
||||
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">Jelszó</td>
|
||||
<td>az üzemeltető állította be</td>
|
||||
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap">{{T "app_info.jelszo"}}</td>
|
||||
<td>{{T "app_info.az_uzemelteto_allitotta_be"}}</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap;vertical-align:middle">Jelszó</td>
|
||||
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap;vertical-align:middle">{{T "app_info.jelszo"}}</td>
|
||||
<!-- R-254: the value is NOT in this page. It used to be rendered into a `hidden`
|
||||
span, which stops a browser drawing it and nothing else — a fetch of this page
|
||||
returned a real per-install password. Both buttons now ask the server. -->
|
||||
<td style="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap">
|
||||
<code id="initcred-pw">••••••••••••</code>
|
||||
<button type="button" class="btn btn-sm btn-outline" id="initcred-reveal" onclick="icRevealPw(this)">Megjelenítés</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="icCopyPw(this)">Másolás</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" id="initcred-reveal" onclick="icRevealPw(this)">{{T "app_info.megjelenites"}}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="icCopyPw(this)">{{T "app_info.masolas"}}</button>
|
||||
<span id="initcred-err" class="form-hint" style="display:none;color:var(--red)"></span>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
{{if .InitialCreds.Note}}<p class="app-info-creds-warn">{{.InitialCreds.Note}}</p>{{end}}
|
||||
{{if not .InitialCredsOperatorSet}}<p class="app-info-creds-warn">Az első bejelentkezés után azonnal változtasd meg! Ez a kezdeti, automatikusan generált jelszó — ha már megváltoztattad, hagyd figyelmen kívül.</p>{{end}}
|
||||
{{if not .InitialCredsOperatorSet}}<p class="app-info-creds-warn">{{T "app_info.az_elso_bejelentkezes_utan_azonnal"}}</p>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .AppInfo.DefaultCreds}}
|
||||
<div class="app-info-card">
|
||||
<h3>Alapértelmezett belépés</h3>
|
||||
<h3>{{T "app_info.alapertelmezett_belepes"}}</h3>
|
||||
<p class="app-info-creds">{{.AppInfo.DefaultCreds}}</p>
|
||||
<p class="app-info-creds-warn">Az első bejelentkezés után azonnal változtasd meg!</p>
|
||||
<p class="app-info-creds-warn">{{T "app_info.az_elso_bejelentkezes_utan_azonnal_2"}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .AppInfo.DocsURL}}
|
||||
<div class="app-info-card">
|
||||
<h3>Dokumentáció</h3>
|
||||
<p><a href="{{.AppInfo.DocsURL}}" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
|
||||
<h3>{{T "app_info.dokumentacio"}}</h3>
|
||||
<p><a href="{{.AppInfo.DocsURL}}" target="_blank" class="app-info-link">{{T "app_info.hivatalos_dokumentacio"}}</a></p>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -240,7 +239,7 @@ function icFetchPw() {
|
||||
headers: {'X-CSRF-Token': '{{.CSRFToken}}'},
|
||||
credentials: 'same-origin'
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
if (!j.ok) { throw new Error(j.error || 'A kezdeti jelszó beolvasása nem sikerült.'); }
|
||||
if (!j.ok) { throw new Error(j.error || '{{T "app_info.a_kezdeti_jelszo_beolvasasa_nem"}}'); }
|
||||
return j.data.password;
|
||||
});
|
||||
}
|
||||
@@ -256,7 +255,7 @@ function icRevealPw(btn) {
|
||||
if (code.dataset.shown === '1') { // hide: drop the value out of the DOM again
|
||||
code.textContent = '••••••••••••';
|
||||
code.dataset.shown = '0';
|
||||
btn.textContent = 'Megjelenítés';
|
||||
btn.textContent = '{{T "app_info.megjelenites"}}';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
@@ -264,7 +263,7 @@ function icRevealPw(btn) {
|
||||
btn.disabled = false;
|
||||
code.textContent = pw;
|
||||
code.dataset.shown = '1';
|
||||
btn.textContent = 'Elrejtés';
|
||||
btn.textContent = '{{T "app_info.elrejtes"}}';
|
||||
}).catch(function (e) { btn.disabled = false; icErr(e.message); });
|
||||
}
|
||||
function icCopyPw(btn) {
|
||||
@@ -274,7 +273,7 @@ function icCopyPw(btn) {
|
||||
return navigator.clipboard.writeText(pw).then(function () {
|
||||
btn.disabled = false;
|
||||
var orig = btn.textContent;
|
||||
btn.textContent = 'Másolva';
|
||||
btn.textContent = '{{T "app_info.masolva"}}';
|
||||
setTimeout(function () { btn.textContent = orig; }, 1500);
|
||||
});
|
||||
}).catch(function (e) { btn.disabled = false; icErr(e.message); });
|
||||
|
||||
Reference in New Issue
Block a user