220 lines
10 KiB
HTML
220 lines
10 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}}
|
|
{{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}}
|
|
<a href="/stacks/{{.Stack.Name}}/deploy" class="btn btn-sm btn-primary" onclick="return checkBeforeDeploy(event, '{{.Stack.Name}}')">Telepítés</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
{{if .MissingStorageLabel}}
|
|
<div class="alert alert-warning" style="margin-top:1rem">
|
|
<strong>⚠ 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>
|
|
</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('{{.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(--accent);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(app,label){
|
|
var sel=document.getElementById('app-migrate-target');
|
|
var target=sel?sel.value:'';
|
|
if(!target){ alert('Válassz céltárhelyet.'); return; }
|
|
if(!confirm('Áthelyezed a(z) '+label+' adatait ide: '+target+'?\n\nAz alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.')) return;
|
|
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 { alert('Hiba: '+(d.error||'ismeretlen')); } })
|
|
.catch(function(e){ alert('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-muted);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-muted);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}}
|