5fdd2039fd
The previous commit landed only the new test/badge files: a 'git stash' used to compare REUSE.md ref-check output silently dropped the staged index, so every modification to an existing file was left behind and that commit does not build. This adds the metadata field, the predicates, the fail-closed deploy gate, the catalog filter, the funcmap entries, the template edits and the docs that those tests exercise.
230 lines
11 KiB
HTML
230 lines
11 KiB
HTML
{{define "app_info"}}
|
|
{{template "layout_start" .}}
|
|
|
|
<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>
|
|
<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}}
|
|
{{template "meta_badge" (lifecycleBadge .Meta)}}
|
|
{{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 .Stack.Orphaned}}
|
|
<button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Stack.Name}}')">Törlés</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>
|
|
{{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}}
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
{{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.
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Hero section -->
|
|
<div class="app-info-hero">
|
|
<img class="app-info-logo" src="{{logoURL .Meta.Slug}}"
|
|
alt="{{.Meta.DisplayName}}"
|
|
onerror="this.onerror=function(){this.style.display='none'};this.src='{{logoPNGURL .Meta.Slug}}'">
|
|
<div class="app-info-hero-text">
|
|
{{if .AppInfo.Tagline}}
|
|
<p class="app-info-tagline">{{.AppInfo.Tagline}}</p>
|
|
{{else}}
|
|
<p class="app-info-tagline">{{.Meta.Description}}</p>
|
|
{{end}}
|
|
<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}}
|
|
</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á.
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Screenshots (graceful — hidden if assets don't exist) -->
|
|
<div class="app-screenshots" id="screenshots">
|
|
<img src="{{screenshotURL .Meta.Slug 1}}" alt="" class="app-screenshot"
|
|
onerror="this.style.display='none'">
|
|
<img src="{{screenshotURL .Meta.Slug 2}}" alt="" class="app-screenshot"
|
|
onerror="this.style.display='none'">
|
|
<img src="{{screenshotURL .Meta.Slug 3}}" alt="" class="app-screenshot"
|
|
onerror="this.style.display='none'">
|
|
</div>
|
|
|
|
{{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>
|
|
<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>
|
|
{{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>
|
|
</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 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+'…';
|
|
}
|
|
function appMigWatch(){
|
|
var panel=document.getElementById('app-migrate-progress');
|
|
if(panel) panel.style.display='block';
|
|
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(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; }
|
|
setTimeout(tick,1500);
|
|
}).catch(function(){ setTimeout(tick,2000); });
|
|
}
|
|
tick();
|
|
}
|
|
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(){
|
|
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); });
|
|
});
|
|
}
|
|
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
|
|
</script>
|
|
{{end}}
|
|
|
|
{{if .HasAppInfo}}
|
|
<div class="app-info-grid">
|
|
{{if .AppInfo.UseCases}}
|
|
<div class="app-info-card">
|
|
<h3>Mire használható?</h3>
|
|
<ul class="app-info-list">
|
|
{{range .AppInfo.UseCases}}<li>{{.}}</li>{{end}}
|
|
</ul>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .AppInfo.FirstSteps}}
|
|
<div class="app-info-card">
|
|
<h3>Első lépések</h3>
|
|
<ol class="app-info-list">
|
|
{{range .AppInfo.FirstSteps}}<li>{{.}}</li>{{end}}
|
|
</ol>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .AppInfo.Prerequisites}}
|
|
<div class="app-info-card">
|
|
<h3>Előfeltételek</h3>
|
|
<ul class="app-info-list">
|
|
{{range .AppInfo.Prerequisites}}<li>{{.}}</li>{{end}}
|
|
</ul>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .InitialCreds}}
|
|
<div class="app-info-card">
|
|
<h3>Kezdeti belépési 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><code class="initcred-user" style="user-select:all">{{.InitialCreds.Username}}</code></td>
|
|
</tr>
|
|
{{end}}
|
|
<tr>
|
|
<td class="initcred-label" style="padding:.25rem .75rem .25rem 0;color:var(--text-3);white-space:nowrap;vertical-align:middle">Jelszó</td>
|
|
<td style="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap">
|
|
<code id="initcred-pw">••••••••••••</code>
|
|
<span id="initcred-pw-val" hidden>{{.InitialCreds.Password}}</span>
|
|
<button type="button" class="btn btn-sm btn-outline" onclick="icRevealPw(this)">Megjelenítés</button>
|
|
<button type="button" class="btn btn-sm btn-outline" onclick="icCopyPw(this)">Másolás</button>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
{{if .InitialCreds.Note}}<p class="app-info-creds-warn">{{.InitialCreds.Note}}</p>{{end}}
|
|
<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>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .AppInfo.DefaultCreds}}
|
|
<div class="app-info-card">
|
|
<h3>Alapértelmezett belépés</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>
|
|
</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>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .InitialCreds}}
|
|
<script>
|
|
// Initial-credential password reveal/copy. The value lives in a hidden element (HTML-escaped by the
|
|
// template) so it's never inlined into a JS string literal.
|
|
function icPwVal() {
|
|
var el = document.getElementById('initcred-pw-val');
|
|
return el ? el.textContent : '';
|
|
}
|
|
function icRevealPw(btn) {
|
|
var code = document.getElementById('initcred-pw');
|
|
if (!code) return;
|
|
if (code.dataset.shown === '1') {
|
|
code.textContent = '••••••••••••';
|
|
code.dataset.shown = '0';
|
|
btn.textContent = 'Megjelenítés';
|
|
} else {
|
|
code.textContent = icPwVal();
|
|
code.dataset.shown = '1';
|
|
btn.textContent = 'Elrejtés';
|
|
}
|
|
}
|
|
function icCopyPw(btn) {
|
|
var val = icPwVal();
|
|
if (!val) return;
|
|
navigator.clipboard.writeText(val).then(function () {
|
|
var orig = btn.textContent;
|
|
btn.textContent = 'Másolva';
|
|
setTimeout(function () { btn.textContent = orig; }, 1500);
|
|
});
|
|
}
|
|
</script>
|
|
{{end}}
|
|
|
|
{{template "layout_end" .}}
|
|
{{end}}
|