02650e3202
Controller: - internal/web/csrf.go (new): CsrfProtect middleware, csrfToken/csrfField helpers - auth.go: per-session CSRF token (csrfToken field, csrfTokenForSession method) - server.go: executeTemplate wrapper auto-injects CSRFField+CSRFToken - main.go: wire CsrfProtect on all routes; bump to v0.23.0 - handlers.go, storage_handlers.go, handler_restore.go: executeTemplate - All templates: CSRFField in forms, meta csrf-token, csrfHeaders() JS helper, fetch calls updated; sendBeacon→fetch+keepalive in storage_attach.html Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
183 lines
6.8 KiB
HTML
183 lines
6.8 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}}
|
|
<a href="https://{{.Meta.Subdomain}}.{{.Domain}}" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
|
|
<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}}/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>
|
|
|
|
<!-- 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}}
|
|
</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 .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 .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 .HasOptionalConfig}}
|
|
<div class="app-optional-config">
|
|
<h3>Opcionális beállítások</h3>
|
|
{{range .OptionalConfig}}
|
|
<div class="config-group">
|
|
<h4>{{.Group}}</h4>
|
|
{{if .Description}}<p class="config-group-desc">{{.Description}}</p>{{end}}
|
|
|
|
<div class="config-fields">
|
|
{{range .Fields}}
|
|
<div class="config-field">
|
|
<label for="opt-{{.EnvVar}}">{{.Label}}</label>
|
|
{{if .HelpText}}<p class="config-field-help">{{.HelpText}}</p>{{end}}
|
|
{{if .HelpURL}}<p class="config-field-help"><a href="{{.HelpURL}}" target="_blank">Regisztrációs útmutató ↗</a></p>{{end}}
|
|
<input type="{{if eq .Type "secret_input"}}password{{else}}text{{end}}"
|
|
id="opt-{{.EnvVar}}"
|
|
name="{{.EnvVar}}"
|
|
class="config-input"
|
|
value="{{index $.CurrentValues .EnvVar}}"
|
|
placeholder="{{.Label}}"
|
|
autocomplete="off">
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="config-actions">
|
|
<button class="btn btn-primary" id="save-optional-config" onclick="saveOptionalConfig('{{.Stack.Name}}')">
|
|
Mentés
|
|
</button>
|
|
<span id="config-save-status" class="config-save-status"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
async function saveOptionalConfig(stackName) {
|
|
const btn = document.getElementById('save-optional-config');
|
|
const status = document.getElementById('config-save-status');
|
|
const inputs = document.querySelectorAll('.config-input');
|
|
|
|
const values = {};
|
|
inputs.forEach(function(input) {
|
|
values[input.name] = input.value;
|
|
});
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'Mentés...';
|
|
status.textContent = '';
|
|
status.className = 'config-save-status';
|
|
|
|
try {
|
|
const resp = await fetch('/api/stacks/' + stackName + '/optional-config', {
|
|
method: 'POST',
|
|
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
|
body: JSON.stringify({values: values})
|
|
});
|
|
const data = await resp.json();
|
|
|
|
if (data.ok) {
|
|
status.textContent = (data.message || 'Mentve');
|
|
status.className = 'config-save-status config-save-ok';
|
|
} else {
|
|
status.textContent = (data.error || 'Hiba');
|
|
status.className = 'config-save-status config-save-err';
|
|
}
|
|
} catch(err) {
|
|
status.textContent = 'Hálózati hiba';
|
|
status.className = 'config-save-status config-save-err';
|
|
}
|
|
|
|
btn.disabled = false;
|
|
btn.textContent = 'Mentés';
|
|
|
|
setTimeout(function() { status.textContent = ''; }, 5000);
|
|
}
|
|
</script>
|
|
{{end}}
|
|
|
|
{{template "layout_end" .}}
|
|
{{end}}
|