Files
felhom-controller/controller/internal/web/templates/stacks.html
T
admin 364dc50794 controller v0.92.0: NAS network storage Part A2 (registry + UI + per-share health)
Controller-side of NAS network storage, proxying to agent A1 /netstorage/*. Distinct
'network' storage kind (no drive lifecycle); add/list/remove + per-share health UI;
unreachable NAS is a recoverable warning, never the drive missing/stop cascade; SMB
creds pass through to the agent, never persisted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
2026-06-30 14:40:47 +02:00

150 lines
7.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "stacks"}}
{{template "layout_start" .}}
<div class="page-header">
<h2>Alkalmazások</h2>
<span class="domain-badge">{{.Domain}}</span>
<a href="/import" class="btn btn-sm btn-outline" title="Alkalmazás visszaállítása exportált csomagból">Importálás</a>
<button class="btn btn-sm btn-outline" id="sync-btn" onclick="syncTemplates()" title="Sablonok frissítése a központi katalógusból">↻ Sablonok frissítése</button>
</div>
<div id="sync-toast" class="sync-toast" style="display:none"></div>
<div class="filter-bar" id="filter-bar">
<button class="filter-btn active" data-filter="all">Mind <span class="filter-count" id="count-all"></span></button>
<button class="filter-btn" data-filter="running">Futó <span class="filter-count" id="count-running"></span></button>
<button class="filter-btn" data-filter="stopped">Leállítva <span class="filter-count" id="count-stopped"></span></button>
<button class="filter-btn" data-filter="available">Telepíthető <span class="filter-count" id="count-available"></span></button>
</div>
<div class="stack-grid">
{{range .Stacks}}
<div class="stack-detail-card stack-state-{{stateColor .State}}" data-filter-state="{{filterCategory .State .Deployed}}"{{if .Meta.Slug}} data-href="/apps/{{.Meta.Slug}}"{{end}}>
<div class="stack-detail-header">
<div class="stack-title-row">
<img class="stack-logo-lg" src="{{logoURL .Meta.Slug}}"
alt="" onerror="this.onerror=function(){this.style.display='none'};this.src='{{logoPNGURL .Meta.Slug}}'">
<div>
<h3>{{.Meta.DisplayName}}</h3>
{{$subdomain := index $.Subdomains .Name}}
{{if and $subdomain (or .Deployed .Protected)}}
<a class="subdomain-link" href="https://{{$subdomain}}.{{$.Domain}}" target="_blank">
{{$subdomain}}.{{$.Domain}} ↗
</a>
{{if and .Deployed (routeUnpublished .State)}}
<span class="route-unpublished" title="A proxy (Traefik) csak egészséges konténerhez publikál nyilvános útvonalat. Amíg az alkalmazás nem egészséges, az URL nem érhető el (404), pedig a konténer fut.">⚠ URL nem elérhető útvonal nincs publikálva</span>
{{end}}
{{end}}
</div>
</div>
<span class="stack-state-badge state-{{stateColor .State}}">{{stateLabel .State}}</span>
{{if .Orphaned}}<span class="badge badge-orphaned">Elavult</span>{{end}}
{{$ms := index $.MissingStorage .Name}}{{if $ms}}<span class="badge badge-missing-storage" title="Az alkalmazás adattárolója nem elérhető. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat.">⚠ Hiányzó tárhely: {{$ms}}</span>{{end}}
{{$nw := index $.NetworkWarnings .Name}}{{if $nw}}<span class="badge badge-warn" title="A hálózati tárhely (NAS) jelenleg nem érhető el. Az alkalmazás fut; az adatok elérése a NAS visszatértével helyreáll.">⚠ Hálózati tárhely nem elérhető: {{$nw}}</span>{{end}}
</div>
{{if .Meta.Description}}
<p class="stack-detail-desc">{{.Meta.Description}}</p>
{{end}}
<div class="stack-meta-badges">
{{if .Meta.Resources.MemRequest}}<span class="meta-badge">~{{.Meta.Resources.MemRequest}}</span>{{end}}
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{end}}
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge">HDD szükséges</span>{{end}}
{{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">Magyar felület</span>{{end}}
{{if and .Deployed (index $.StorageLabels .Name)}}<span class="meta-badge meta-badge-storage" title="Adattároló: {{index $.StorageLabels .Name}}">💾 {{index $.StorageLabels .Name}}</span>{{end}}
</div>
{{if .Containers}}
<div class="container-list">
{{range .Containers}}
<div class="container-row">
<span class="container-name">{{.Name}}</span>
<span class="container-status state-text-{{stateColor .State}}">{{.Status}}</span>
</div>
{{end}}
</div>
{{end}}
<div class="stack-detail-actions">
{{if .Protected}}
<span class="badge badge-protected">Védett rendszerkomponens</span>
{{if isOperational .State}}
<button class="btn btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')">Újraindítás</button>
{{end}}
{{if .Meta.Slug}}
<a href="/apps/{{.Meta.Slug}}" class="btn btn-outline">Részletek</a>
{{end}}
{{else if not .Deployed}}
<a href="/stacks/{{.Name}}/deploy" class="btn btn-primary" onclick="return checkBeforeDeploy(event, '{{.Name}}')">Telepítés</a>
<a href="{{appPageURL .Meta.Slug}}" class="btn btn-outline">Részletek</a>
{{else}}
{{if isOperational .State}}
{{if not .Orphaned}}<button class="btn btn-success" onclick="stackAction(event, '{{.Name}}', 'update')">Frissítés</button>{{end}}
<button class="btn btn-warning" onclick="stackAction(event, '{{.Name}}', 'restart')">Újraindítás</button>
<button class="btn btn-danger" onclick="stackAction(event, '{{.Name}}', 'stop')">Leállítás</button>
{{else}}
<button class="btn btn-success" onclick="stackAction(event, '{{.Name}}', 'start')">Indítás</button>
{{if not .Orphaned}}<button class="btn btn-danger" onclick="removeStack('{{.Name}}')">Eltávolítás</button>{{end}}
{{end}}
<a href="/stacks/{{.Name}}/logs" class="btn btn-outline">Naplók</a>
{{if not .Orphaned}}<a href="{{appPageURL .Meta.Slug}}" class="btn btn-outline">Részletek</a>{{end}}
{{if .Orphaned}}<button class="btn btn-danger" onclick="deleteOrphanStack('{{.Name}}')">Törlés</button>{{end}}
{{end}}
</div>
</div>
{{end}}
</div>
<script>
(function() {
var cards = document.querySelectorAll('.stack-detail-card[data-filter-state]');
var counts = {all: cards.length, running: 0, stopped: 0, available: 0};
cards.forEach(function(card) {
var cat = card.getAttribute('data-filter-state');
if (counts[cat] !== undefined) counts[cat]++;
});
['all', 'running', 'stopped', 'available'].forEach(function(f) {
var el = document.getElementById('count-' + f);
if (el) el.textContent = '(' + counts[f] + ')';
});
function applyFilter(filter) {
cards.forEach(function(card) {
if (filter === 'all' || card.getAttribute('data-filter-state') === filter) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
document.querySelectorAll('.filter-btn').forEach(function(btn) {
btn.classList.toggle('active', btn.getAttribute('data-filter') === filter);
});
var url = new URL(window.location);
if (filter === 'all') {
url.searchParams.delete('filter');
} else {
url.searchParams.set('filter', filter);
}
history.replaceState(null, '', url);
}
document.querySelectorAll('.filter-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
applyFilter(this.getAttribute('data-filter'));
});
});
var urlFilter = new URLSearchParams(window.location.search).get('filter');
if (urlFilter && ['running', 'stopped', 'available'].indexOf(urlFilter) !== -1) {
applyFilter(urlFilter);
}
})();
</script>
{{template "layout_end" .}}
{{end}}