95c821deb2
Add detailed [DEBUG] logging to every controller module when logging.level is set to "debug". Each module with stateful debug uses SetDebug(bool) wired from main.go. Covers stacks, backup, cloudflare, integrations, system, monitor, settings, scheduler, web handlers, storage, metrics, API, selfupdate, and assets. Also includes the app export/import (.fab bundles) feature from v0.32.0 and its debug page integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
145 lines
7.0 KiB
HTML
145 lines
7.0 KiB
HTML
{{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>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
<span class="stack-state-badge state-{{stateColor .State}}">{{stateLabel .State}}</span>
|
|
{{if .Orphaned}}<span class="badge badge-orphaned">Elavult</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}}
|