Files
admin 95c821deb2 feat: comprehensive debug logging across all controller modules
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>
2026-02-26 18:14:43 +01:00

224 lines
8.6 KiB
HTML

{{define "app_export"}}
{{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">&larr; Alkalmazások</a>
<h2>{{.Stack.Meta.DisplayName}} &mdash; Exportálás</h2>
</div>
</div>
<div class="card" style="max-width:700px">
<h3>Mentés helye</h3>
<select id="destDrive" onchange="loadEstimate()" style="width:100%;padding:.5rem;margin-bottom:1rem">
<option value="">Válassz tárolót...</option>
{{range .Drives}}
<option value="{{.Path}}">{{if .Label}}{{.Label}} ({{.Path}}){{else}}{{.Path}}{{end}}</option>
{{end}}
</select>
<div id="estimateBox" style="display:none;background:var(--bg-secondary);border-radius:8px;padding:1rem;margin-bottom:1.5rem">
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Konfiguráció:</span>
<span id="estConfig">-</span>
</div>
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
<span>Felhasználói adatok:</span>
<span id="estData">-</span>
</div>
<div style="display:flex;justify-content:space-between;font-weight:600;margin-bottom:.25rem;border-top:1px solid var(--border);padding-top:.5rem">
<span>Összesen:</span>
<span><span id="estTotal">-</span> <span id="estFree" style="color:var(--text-muted)">(szabad: -)</span></span>
</div>
<div style="display:flex;justify-content:space-between">
<span>Becsült idő:</span>
<span id="estTime">-</span>
</div>
<div id="estWarning" style="display:none;color:var(--danger);margin-top:.5rem;font-weight:600"></div>
</div>
<h3>Jelszó (opcionális)</h3>
<div style="display:flex;gap:.5rem;margin-bottom:1rem">
<input type="password" id="exportPassword" placeholder="Titkosítási jelszó" style="flex:1;padding:.5rem">
<button class="btn btn-sm btn-outline" onclick="togglePw()" type="button" title="Jelszó mutatása">&#128065;</button>
</div>
<label style="display:flex;align-items:flex-start;gap:.5rem;margin-bottom:.5rem;cursor:pointer">
<input type="checkbox" id="stopApp" checked style="margin-top:3px">
<div>
<strong>Alkalmazás leállítása mentés előtt (ajánlott)</strong>
<div style="color:var(--text-muted);font-size:.85rem">Az adatok konzisztenciája érdekében javasolt leállítani az alkalmazást mentés közben.</div>
</div>
</label>
<button id="startBtn" class="btn btn-primary" onclick="startExport()" style="margin-top:1rem;width:100%" disabled>
Exportálás indítása
</button>
</div>
<div id="progressCard" class="card" style="max-width:700px;display:none">
<h3>Folyamat</h3>
<div id="progressSteps"></div>
<div id="progressError" style="display:none;color:var(--danger);margin-top:1rem;font-weight:600"></div>
</div>
<div id="doneCard" class="card" style="max-width:700px;display:none">
<h3 style="color:var(--success)">Kész!</h3>
<div style="margin-bottom:1rem">
<span id="doneFile" style="font-weight:600"></span>
<span id="doneSize" style="color:var(--text-muted)"></span>
</div>
<a id="doneFBLink" href="#" target="_blank" class="btn btn-outline">Megnyitás FileBrowser-ben &nearr;</a>
</div>
<script>
var stackName = '{{.Stack.Name}}';
var domain = '{{.Stack.Meta.Subdomain}}' ? '{{.Stack.Meta.Subdomain}}.{{$.CSRFToken}}' : '';
var pollTimer = null;
function csrfH() {
var el = document.querySelector('meta[name="csrf-token"]');
return el ? {'X-CSRF-Token': el.content, 'Content-Type': 'application/json'} : {'Content-Type': 'application/json'};
}
function togglePw() {
var inp = document.getElementById('exportPassword');
inp.type = inp.type === 'password' ? 'text' : 'password';
}
async function loadEstimate() {
var drive = document.getElementById('destDrive').value;
var box = document.getElementById('estimateBox');
var btn = document.getElementById('startBtn');
if (!drive) {
box.style.display = 'none';
btn.disabled = true;
return;
}
try {
var resp = await fetch('/api/export/estimate?stack=' + encodeURIComponent(stackName) + '&drive=' + encodeURIComponent(drive));
var data = await resp.json();
if (!data.ok) {
box.style.display = 'none';
btn.disabled = true;
return;
}
var est = data.data;
document.getElementById('estConfig').textContent = est.config_size_human;
document.getElementById('estData').textContent = est.data_size_human;
document.getElementById('estTotal').textContent = '~' + est.total_size_human;
document.getElementById('estFree').textContent = '(szabad: ' + est.dest_free_human + ')';
document.getElementById('estTime').textContent = '~' + est.estimated_minutes + ' perc';
var warn = document.getElementById('estWarning');
if (!est.fits_on_dest) {
warn.textContent = 'Nincs elég szabad hely a kiválasztott tárolón!';
warn.style.display = 'block';
btn.disabled = true;
} else {
warn.style.display = 'none';
btn.disabled = false;
}
box.style.display = 'block';
} catch(e) {
console.error('Estimate error:', e);
}
}
async function startExport() {
var drive = document.getElementById('destDrive').value;
var password = document.getElementById('exportPassword').value;
var stopApp = document.getElementById('stopApp').checked;
document.getElementById('startBtn').disabled = true;
document.getElementById('progressCard').style.display = 'block';
document.getElementById('doneCard').style.display = 'none';
try {
var resp = await fetch('/api/export/start', {
method: 'POST',
headers: csrfH(),
body: JSON.stringify({
stack_name: stackName,
dest_drive: drive,
password: password,
stop_app: stopApp
})
});
var data = await resp.json();
if (!data.ok) {
showError(data.error || 'Hiba történt');
return;
}
pollStatus();
} catch(e) {
showError(e.message);
}
}
function pollStatus() {
if (pollTimer) clearInterval(pollTimer);
pollTimer = setInterval(async function() {
try {
var resp = await fetch('/api/export/status');
var data = await resp.json();
renderSteps(data.steps || []);
if (data.error) {
showError(data.error);
clearInterval(pollTimer);
return;
}
if (data.done && !data.error) {
clearInterval(pollTimer);
showDone(data);
}
} catch(e) {
console.error('Poll error:', e);
}
}, 1000);
}
function renderSteps(steps) {
var html = '';
for (var i = 0; i < steps.length; i++) {
var s = steps[i];
var icon = '&#9675;'; // pending
if (s.status === 'running') icon = '&#10227;';
if (s.status === 'done') icon = '&#10003;';
if (s.status === 'failed') icon = '&#10007;';
var cls = s.status === 'failed' ? 'color:var(--danger)' : s.status === 'done' ? 'color:var(--success)' : s.status === 'running' ? 'color:var(--primary)' : '';
html += '<div style="padding:.25rem 0;' + cls + '">' + icon + ' ' + s.label;
if (s.error) html += ' <span style="font-size:.85rem">(' + s.error + ')</span>';
html += '</div>';
}
document.getElementById('progressSteps').innerHTML = html;
}
function showError(msg) {
var el = document.getElementById('progressError');
el.textContent = msg;
el.style.display = 'block';
document.getElementById('startBtn').disabled = false;
}
function showDone(data) {
document.getElementById('progressCard').style.display = 'none';
document.getElementById('doneCard').style.display = 'block';
var fileName = (data.output_path || '').split('/').pop();
document.getElementById('doneFile').textContent = fileName;
document.getElementById('doneSize').textContent = data.output_size ? '(' + data.output_size + ')' : '';
// Build FileBrowser link to the exports directory
var drive = document.getElementById('destDrive').value;
var fbPath = drive + '/felhom-data/exports/';
document.getElementById('doneFBLink').href = 'https://files.' + location.hostname.split('.').slice(-2).join('.') + '/files' + fbPath;
}
</script>
{{template "layout_end" .}}
{{end}}