c9a88afcef
New selfupdate package: version parsing, audit state file, updater with
Gitea registry V2 check, docker pull + compose rewrite + compose up flow.
- API: /api/selfupdate/{status,check,update} with session+bearer auth
- UI: Settings "Verzió és frissítés" card with check/install buttons + JS polling
- Scheduler: periodic check (6h default) + optional daily auto-update
- Notifications: success/failure on post-update startup verification
- Alert: info banner when update available
- docker-compose.yml: add directory bind mount for compose file access
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
444 lines
22 KiB
HTML
444 lines
22 KiB
HTML
{{define "settings"}}
|
||
{{template "layout_start" .}}
|
||
|
||
<div class="page-header">
|
||
<h2>Beállítások</h2>
|
||
</div>
|
||
|
||
<!-- Section A: System Configuration (read-only) -->
|
||
<div class="settings-card">
|
||
<h3>Rendszer konfiguráció</h3>
|
||
<p class="settings-card-desc">Az üzemeltető által beállított értékek. Módosításhoz kérd az üzemeltetőt.</p>
|
||
<div class="settings-grid">
|
||
<div class="settings-row">
|
||
<span class="settings-label">Ügyfél azonosító</span>
|
||
<span class="settings-value mono">{{.CustomerID}}</span>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Ügyfél neve</span>
|
||
<span class="settings-value">{{.CustomerName}}</span>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Domain</span>
|
||
<span class="settings-value mono">{{.CustomerDomain}}</span>
|
||
</div>
|
||
{{if .GitRepoURL}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Alkalmazás sablon forrás</span>
|
||
<span class="settings-value mono settings-value-truncate">{{.GitRepoURL}}</span>
|
||
</div>
|
||
{{end}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Sablon szinkronizálás</span>
|
||
<span class="settings-value mono">{{.GitSyncInterval}}</span>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Biztonsági mentés</span>
|
||
<span class="settings-value">{{if .BackupEnabled}}<span class="state-text-green">✅ Aktív</span>{{else}}<span class="state-text-red">❌ Inaktív</span>{{end}}</span>
|
||
</div>
|
||
{{if .BackupEnabled}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Mentés ütemezés</span>
|
||
<span class="settings-value mono">{{.DBDumpSchedule}} / {{.ResticSchedule}}</span>
|
||
</div>
|
||
{{end}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Monitoring</span>
|
||
<span class="settings-value">{{if .MonitoringEnabled}}<span class="state-text-green">✅ Aktív</span>{{else}}<span class="state-text-red">❌ Inaktív</span>{{end}}</span>
|
||
</div>
|
||
{{if .MonitoringEnabled}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Healthchecks URL</span>
|
||
<span class="settings-value mono settings-value-truncate">{{if .HealthchecksBase}}{{.HealthchecksBase}}{{else}}–{{end}}</span>
|
||
</div>
|
||
{{end}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Hub jelentés</span>
|
||
<span class="settings-value">{{if .HubEnabled}}<span class="state-text-green">✅ Aktív</span>{{else}}–{{end}}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section: Version & Update -->
|
||
<div class="settings-card">
|
||
<h3>Verzió és frissítés</h3>
|
||
<div class="settings-grid">
|
||
<div class="settings-row">
|
||
<span class="settings-label">Jelenlegi verzió</span>
|
||
<span class="settings-value mono">{{.Version}}</span>
|
||
</div>
|
||
{{if .SelfUpdateEnabled}}
|
||
{{if .LatestVersion}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Legújabb verzió</span>
|
||
<span class="settings-value mono">
|
||
{{.LatestVersion}}
|
||
{{if .UpdateAvailable}}
|
||
<span class="state-text-green" style="margin-left:0.5em;">● Frissítés elérhető</span>
|
||
{{else}}
|
||
<span style="margin-left:0.5em; color:#888;">— naprakész</span>
|
||
{{end}}
|
||
</span>
|
||
</div>
|
||
{{end}}
|
||
{{if .LastCheckTime}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Utolsó ellenőrzés</span>
|
||
<span class="settings-value mono">{{.LastCheckTime}}</span>
|
||
</div>
|
||
{{end}}
|
||
{{if .LastCheckError}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Hiba</span>
|
||
<span class="settings-value state-text-red">{{.LastCheckError}}</span>
|
||
</div>
|
||
{{end}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Automatikus frissítés</span>
|
||
<span class="settings-value">
|
||
{{if .AutoUpdateEnabled}}<span class="state-text-green">✅ Aktív</span> <span class="mono">({{.AutoUpdateTime}})</span>{{else}}–{{end}}
|
||
</span>
|
||
</div>
|
||
{{with .LastUpdateState}}
|
||
<div class="settings-row">
|
||
<span class="settings-label">Utolsó frissítés</span>
|
||
<span class="settings-value">
|
||
{{if eq .Status "success"}}<span class="state-text-green">✅ Sikeres</span> ({{.PreviousVersion}} → {{.TargetVersion}})
|
||
{{else if eq .Status "failed"}}<span class="state-text-red">❌ Sikertelen</span> — {{.Error}}
|
||
{{else if eq .Status "pending"}}<span class="state-text-yellow">⏳ Folyamatban</span>
|
||
{{end}}
|
||
</span>
|
||
</div>
|
||
{{end}}
|
||
<div class="settings-row" style="padding-top: 0.5em;">
|
||
<span class="settings-label"></span>
|
||
<span class="settings-value">
|
||
<button class="btn btn-secondary btn-sm" id="btn-check-update" onclick="checkUpdate()">Frissítés keresése</button>
|
||
{{if .UpdateAvailable}}
|
||
<button class="btn btn-primary btn-sm" id="btn-trigger-update" onclick="triggerUpdate()" style="margin-left:0.5em;">Frissítés telepítése</button>
|
||
{{end}}
|
||
<span id="update-status-msg" style="margin-left:0.5em; display:none;"></span>
|
||
</span>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function checkUpdate() {
|
||
var btn = document.getElementById('btn-check-update');
|
||
var msg = document.getElementById('update-status-msg');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Ellenőrzés...';
|
||
msg.style.display = 'none';
|
||
fetch('/api/selfupdate/check', {method:'POST'})
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(data) {
|
||
if (data.ok) {
|
||
location.reload();
|
||
} else {
|
||
msg.textContent = data.error || 'Hiba történt';
|
||
msg.style.display = 'inline';
|
||
btn.disabled = false;
|
||
btn.textContent = 'Frissítés keresése';
|
||
}
|
||
})
|
||
.catch(function() {
|
||
msg.textContent = 'Kapcsolódási hiba';
|
||
msg.style.display = 'inline';
|
||
btn.disabled = false;
|
||
btn.textContent = 'Frissítés keresése';
|
||
});
|
||
}
|
||
|
||
function triggerUpdate() {
|
||
if (!confirm('Biztosan frissíti a controllert?\n\nA folyamat alatt a vezérlőpult rövid időre elérhetetlenné válik.')) return;
|
||
var btn = document.getElementById('btn-trigger-update');
|
||
var checkBtn = document.getElementById('btn-check-update');
|
||
var msg = document.getElementById('update-status-msg');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Frissítés...';
|
||
if (checkBtn) checkBtn.disabled = true;
|
||
msg.textContent = 'Frissítés folyamatban...';
|
||
msg.style.display = 'inline';
|
||
fetch('/api/selfupdate/update', {method:'POST'})
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(data) {
|
||
if (data.ok) {
|
||
msg.textContent = 'Újraindulás...';
|
||
pollUntilBack();
|
||
} else {
|
||
msg.textContent = data.error || 'Hiba történt';
|
||
btn.disabled = false;
|
||
btn.textContent = 'Frissítés telepítése';
|
||
if (checkBtn) checkBtn.disabled = false;
|
||
}
|
||
})
|
||
.catch(function() {
|
||
msg.textContent = 'Kapcsolódási hiba';
|
||
pollUntilBack();
|
||
});
|
||
}
|
||
|
||
function pollUntilBack() {
|
||
var iv = setInterval(function() {
|
||
fetch('/api/health')
|
||
.then(function(r) {
|
||
if (r.ok) {
|
||
clearInterval(iv);
|
||
location.reload();
|
||
}
|
||
})
|
||
.catch(function() {});
|
||
}, 3000);
|
||
}
|
||
</script>
|
||
|
||
<!-- Section: Storage Paths -->
|
||
<div class="settings-card">
|
||
<h3>Adattárolók</h3>
|
||
<p class="settings-card-desc">Külső meghajtók kezelése alkalmazásadatok tárolásához.</p>
|
||
|
||
{{if .StorageError}}<div class="alert alert-error">{{.StorageError}}</div>{{end}}
|
||
{{if .StorageSuccess}}<div class="alert alert-info">{{.StorageSuccess}}</div>{{end}}
|
||
|
||
{{if .StoragePaths}}
|
||
<div class="storage-paths-list">
|
||
{{range .StoragePaths}}
|
||
<div class="storage-path-item">
|
||
<div class="storage-path-header">
|
||
<div class="storage-path-info">
|
||
<div class="storage-path-label-wrap" id="label-wrap-{{.Path}}">
|
||
<span class="storage-path-label" id="label-display-{{.Path}}">{{.Label}}</span>
|
||
<button class="btn btn-xs btn-ghost" onclick="editStorageLabel('{{.Path}}', '{{.Label}}')" title="Átnevezés">✏️</button>
|
||
</div>
|
||
<span class="storage-path-path mono">{{.Path}}</span>
|
||
</div>
|
||
<div class="storage-path-badges">
|
||
{{if .IsDefault}}<span class="badge state-green">Alapértelmezett</span>{{end}}
|
||
{{if .Schedulable}}<span class="badge" style="background:rgba(0,136,204,0.15);color:var(--accent-light)">Aktív</span>{{else}}<span class="badge state-gray">Inaktív</span>{{end}}
|
||
{{if not .IsMounted}}<span class="badge badge-warn">Rendszermeghajtón</span>{{end}}
|
||
</div>
|
||
</div>
|
||
<div class="storage-path-details">
|
||
{{if .DiskInfo}}
|
||
<div class="storage-path-disk">
|
||
<div class="system-info-header">
|
||
<span class="system-info-value">{{.DiskInfo.UsedHuman}} / {{.DiskInfo.TotalHuman}}</span>
|
||
</div>
|
||
<div class="system-bar">
|
||
<div class="system-bar-fill {{if ge .DiskInfo.UsedPercent 90.0}}system-bar-red{{else if ge .DiskInfo.UsedPercent 70.0}}system-bar-yellow{{else}}system-bar-green{{end}}"
|
||
style="width:{{printf "%.0f" .DiskInfo.UsedPercent}}%"></div>
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
{{if .FSInfo}}
|
||
<div class="storage-path-fsinfo mono form-hint">
|
||
{{.FSInfo.FSType}} · {{.FSInfo.Device}}{{if .FSInfo.Model}} · {{.FSInfo.Model}}{{end}}
|
||
</div>
|
||
{{end}}
|
||
<div class="storage-path-meta">
|
||
{{if .AppDetails}}
|
||
<details class="storage-app-details">
|
||
<summary class="form-hint" style="cursor:pointer">
|
||
{{.AppCount}} alkalmazás használja
|
||
</summary>
|
||
<div class="storage-app-list">
|
||
{{range .AppDetails}}
|
||
<div class="storage-app-row">
|
||
<a href="/apps/{{.Stack}}" class="storage-app-link">{{.Name}}</a>
|
||
{{if .SizeHuman}}<span class="mono form-hint">{{.SizeHuman}}</span>{{end}}
|
||
<a href="/stacks/{{.Stack}}/migrate" class="btn btn-xs btn-outline" title="Adatok áthelyezése másik tárolóra">📦 Mozgatás</a>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
</details>
|
||
{{else}}
|
||
<span class="form-hint">Nincs alkalmazás ezen a tárolón</span>
|
||
{{end}}
|
||
</div>
|
||
</div>
|
||
<div class="storage-path-actions">
|
||
{{if not .IsDefault}}
|
||
<form method="POST" action="/settings/storage/default" style="display:inline">
|
||
<input type="hidden" name="storage_path" value="{{.Path}}">
|
||
<button type="submit" class="btn btn-xs btn-outline">Legyen alapértelmezett</button>
|
||
</form>
|
||
{{end}}
|
||
{{if .Schedulable}}
|
||
<form method="POST" action="/settings/storage/schedulable" style="display:inline">
|
||
<input type="hidden" name="storage_path" value="{{.Path}}">
|
||
<input type="hidden" name="schedulable" value="false">
|
||
<button type="submit" class="btn btn-xs btn-outline">Letiltás</button>
|
||
</form>
|
||
{{else}}
|
||
<form method="POST" action="/settings/storage/schedulable" style="display:inline">
|
||
<input type="hidden" name="storage_path" value="{{.Path}}">
|
||
<input type="hidden" name="schedulable" value="true">
|
||
<button type="submit" class="btn btn-xs btn-outline">Engedélyezés</button>
|
||
</form>
|
||
{{end}}
|
||
{{if and (not .IsDefault) (eq .AppCount 0)}}
|
||
<form method="POST" action="/settings/storage/remove" style="display:inline"
|
||
onsubmit="return confirm('Biztosan eltávolítja a(z) {{.Path}} adattárolót?')">
|
||
<input type="hidden" name="storage_path" value="{{.Path}}">
|
||
<button type="submit" class="btn btn-xs btn-danger-outline">Eltávolítás</button>
|
||
</form>
|
||
{{end}}
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
{{else}}
|
||
<div class="empty-state" style="padding:1.5rem">
|
||
Nincs regisztrált adattároló. Adjon hozzá egyet az alábbi űrlappal.
|
||
</div>
|
||
{{end}}
|
||
|
||
<div style="margin-top:1rem;display:flex;gap:.75rem;flex-wrap:wrap">
|
||
<a href="/settings/storage/init" class="btn btn-sm btn-outline">🔧 Új meghajtó inicializálása</a>
|
||
<a href="/settings/storage/attach" class="btn btn-sm btn-outline">🔗 Meglévő meghajtó csatolása</a>
|
||
</div>
|
||
|
||
<details class="storage-add-details">
|
||
<summary class="btn btn-sm btn-outline" style="margin-top:.75rem;cursor:pointer">Már csatlakoztatott tárhely hozzáadása kézzel</summary>
|
||
<form method="POST" action="/settings/storage/add" class="storage-add-form">
|
||
<div class="form-group">
|
||
<label for="storage_path">Elérési út</label>
|
||
<input type="text" id="storage_path" name="storage_path" class="form-control"
|
||
placeholder="/mnt/hdd_1" required>
|
||
<span class="form-hint">Pl. /mnt/hdd_1 — a meghajtónak már csatolva kell lennie</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="storage_label">Megnevezés (opcionális)</label>
|
||
<input type="text" id="storage_label" name="storage_label" class="form-control"
|
||
placeholder="Külső HDD 1TB">
|
||
</div>
|
||
<label class="toggle" style="margin-bottom:1rem">
|
||
<input type="checkbox" name="storage_default" value="true">
|
||
<span class="toggle-label">Legyen alapértelmezett új telepítéseknél</span>
|
||
</label>
|
||
<button type="submit" class="btn btn-primary">Hozzáadás</button>
|
||
</form>
|
||
</details>
|
||
</div>
|
||
|
||
<!-- Section B: Password Change -->
|
||
<div class="settings-card">
|
||
<h3>Jelszó módosítás</h3>
|
||
{{if .AuthEnabled}}
|
||
{{if .PasswordError}}<div class="alert alert-error">{{.PasswordError}}</div>{{end}}
|
||
<form method="POST" action="/settings/password">
|
||
<div class="form-group">
|
||
<label for="current_password">Jelenlegi jelszó</label>
|
||
<input type="password" id="current_password" name="current_password" required
|
||
placeholder="Adja meg a jelenlegi jelszavát" class="form-control">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="new_password">Új jelszó</label>
|
||
<input type="password" id="new_password" name="new_password" required minlength="8"
|
||
placeholder="Legalább 8 karakter" class="form-control">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="confirm_password">Új jelszó megerősítése</label>
|
||
<input type="password" id="confirm_password" name="confirm_password" required minlength="8"
|
||
placeholder="Jelszó mégegyszer" class="form-control">
|
||
</div>
|
||
<button type="submit" class="btn btn-primary">Jelszó módosítása</button>
|
||
</form>
|
||
{{else}}
|
||
<div class="alert alert-info">
|
||
A jelszavas védelem nincs beállítva. Kérd az üzemeltetőt a beállításhoz.
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
<!-- Section C: Notification Preferences -->
|
||
<div class="settings-card">
|
||
<h3>Értesítések</h3>
|
||
{{if .HubEnabled}}
|
||
{{if .NotificationSuccess}}<div class="alert alert-info">{{.NotificationSuccess}}</div>{{end}}
|
||
{{if .NotificationError}}<div class="alert alert-error">{{.NotificationError}}</div>{{end}}
|
||
<form method="POST" action="/settings/notifications">
|
||
<div class="form-group">
|
||
<label for="notification_email">E-mail cím</label>
|
||
<input type="email" id="notification_email" name="notification_email"
|
||
value="{{with .NotificationPrefs}}{{.Email}}{{end}}"
|
||
placeholder="pelda@email.hu" class="form-control">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Az alábbi eseményekről kapjon értesítést:</label>
|
||
<div class="checkbox-group">
|
||
<label class="toggle">
|
||
<input type="checkbox" name="event_disk_warning" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "disk_warning"}}checked{{end}}{{end}}{{end}}>
|
||
<span class="toggle-label">Lemez figyelmeztetés (80%+)</span>
|
||
</label>
|
||
<label class="toggle">
|
||
<input type="checkbox" name="event_backup_failed" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "backup_failed"}}checked{{end}}{{end}}{{end}}>
|
||
<span class="toggle-label">Biztonsági mentés sikertelen</span>
|
||
</label>
|
||
<label class="toggle">
|
||
<input type="checkbox" name="event_update_available" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "update_available"}}checked{{end}}{{end}}{{end}}>
|
||
<span class="toggle-label">Frissítés elérhető</span>
|
||
</label>
|
||
<label class="toggle">
|
||
<input type="checkbox" name="event_security_update" {{with .NotificationPrefs}}{{range .EnabledEvents}}{{if eq . "security_update"}}checked{{end}}{{end}}{{end}}>
|
||
<span class="toggle-label">Biztonsági frissítés</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="cooldown_hours">Értesítési szünet</label>
|
||
<div class="form-inline">
|
||
<input type="number" id="cooldown_hours" name="cooldown_hours" min="1" max="168"
|
||
value="{{with .NotificationPrefs}}{{.CooldownHours}}{{end}}"
|
||
class="form-control form-control-narrow">
|
||
<span class="form-hint">óra (azonos probléma esetén ennyi ideig nem küld újat)</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">Mentés</button>
|
||
<button type="submit" formaction="/settings/notifications/test" class="btn btn-outline">Teszt email küldése</button>
|
||
</div>
|
||
</form>
|
||
{{else}}
|
||
<div class="alert alert-info">
|
||
Az értesítések a központi rendszeren keresztül működnek, ami jelenleg nincs bekapcsolva.
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
<script>
|
||
function editStorageLabel(path, currentLabel) {
|
||
var wrap = document.getElementById('label-wrap-' + path);
|
||
if (!wrap) return;
|
||
wrap.innerHTML = '<form method="POST" action="/settings/storage/label" style="display:inline-flex;gap:.5rem;align-items:center">' +
|
||
'<input type="hidden" name="storage_path" value="' + path + '">' +
|
||
'<input type="text" name="storage_label" class="form-control" value="' + currentLabel.replace(/"/g, '"') + '" style="width:200px;padding:.3rem .5rem;font-size:.9rem" maxlength="50">' +
|
||
'<button type="submit" class="btn btn-xs btn-primary">OK</button>' +
|
||
'<button type="button" class="btn btn-xs btn-outline" onclick="cancelEditLabel(\'' + path + '\', \'' + currentLabel.replace(/'/g, "\\'") + '\')">✕</button>' +
|
||
'</form>';
|
||
wrap.querySelector('input[name=storage_label]').focus();
|
||
}
|
||
function cancelEditLabel(path, label) {
|
||
var wrap = document.getElementById('label-wrap-' + path);
|
||
if (!wrap) return;
|
||
// M11: Use DOM manipulation with textContent to prevent XSS if label contains HTML.
|
||
wrap.innerHTML = '';
|
||
var span = document.createElement('span');
|
||
span.className = 'storage-path-label';
|
||
span.id = 'label-display-' + path;
|
||
span.textContent = label;
|
||
var btn = document.createElement('button');
|
||
btn.className = 'btn btn-xs btn-ghost';
|
||
btn.setAttribute('title', 'Átnevezés');
|
||
btn.textContent = '✏️';
|
||
btn.addEventListener('click', function() { editStorageLabel(path, label); });
|
||
wrap.appendChild(span);
|
||
wrap.appendChild(document.createTextNode(' '));
|
||
wrap.appendChild(btn);
|
||
}
|
||
</script>
|
||
{{template "layout_end" .}}
|
||
{{end}}
|