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>
869 lines
46 KiB
HTML
869 lines
46 KiB
HTML
{{define "debug"}}
|
||
{{template "layout_start" .}}
|
||
|
||
<div class="page-header">
|
||
<h2>Debug</h2>
|
||
</div>
|
||
|
||
<div class="debug-banner">
|
||
⚠ Debug mód aktív — ez az oldal csak fejlesztéshez és hibaelhárításhoz
|
||
</div>
|
||
|
||
<!-- Section 1: System Diagnostic -->
|
||
<div class="card debug-section" id="section-diagnostic">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('diagnostic')">
|
||
<h3>Rendszer diagnosztika</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="diagnostic-content"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-secondary btn-sm" id="btn-download-dump" data-label="Nyers JSON letöltése" onclick="downloadDump()">Nyers JSON letöltése</button>
|
||
<button class="btn btn-secondary btn-sm" id="btn-refresh-diag" data-label="Frissítés" onclick="loadSectionData('diagnostic')">Frissítés</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 2: Notification & Event Testing -->
|
||
<div class="card debug-section" id="section-events">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('events')">
|
||
<h3>Értesítés teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div class="debug-form-row">
|
||
<label>Esemény típus:</label>
|
||
<select id="event-type">
|
||
<option value="test">test (info)</option>
|
||
<option value="backup_failed">backup_failed (error)</option>
|
||
<option value="db_dump_failed">db_dump_failed (error)</option>
|
||
<option value="backup_integrity_failed">backup_integrity_failed (error)</option>
|
||
<option value="crossdrive_failed">crossdrive_failed (error)</option>
|
||
<option value="disk_warning">disk_warning (warning)</option>
|
||
<option value="disk_critical">disk_critical (error)</option>
|
||
<option value="storage_disconnected">storage_disconnected (error)</option>
|
||
<option value="storage_reconnected">storage_reconnected (info)</option>
|
||
<option value="health_critical">health_critical (error)</option>
|
||
<option value="health_recovered">health_recovered (info)</option>
|
||
<option value="update_available">update_available (info)</option>
|
||
<option value="controller_started">controller_started (info)</option>
|
||
</select>
|
||
<label>Súlyosság:</label>
|
||
<select id="event-severity">
|
||
<option value="error">error</option>
|
||
<option value="warning">warning</option>
|
||
<option value="info">info</option>
|
||
</select>
|
||
<button class="btn btn-primary btn-sm" id="btn-test-event" data-label="Teszt esemény küldése" onclick="sendTestEvent()">Teszt esemény küldése</button>
|
||
<span class="debug-result" id="btn-test-event-result"></span>
|
||
</div>
|
||
<h4>Utolsó események</h4>
|
||
<div id="event-history"><span class="text-muted">Betöltés...</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 3: Backup Testing -->
|
||
<div class="card debug-section" id="section-backup">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('backup')">
|
||
<h3>Mentés teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="backup-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primary btn-sm" id="btn-full-backup" data-label="Teljes mentés" onclick="triggerAction('btn-full-backup','/api/backup/run','POST')">Teljes mentés</button>
|
||
<span class="debug-result" id="btn-full-backup-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-dbdump" data-label="Csak DB dump" onclick="triggerAction('btn-dbdump','/api/debug/backup/dbdump','POST')">Csak DB dump</button>
|
||
<span class="debug-result" id="btn-dbdump-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-crossdrive" data-label="Csak cross-drive" onclick="triggerAction('btn-crossdrive','/api/debug/backup/crossdrive','POST')">Csak cross-drive</button>
|
||
<span class="debug-result" id="btn-crossdrive-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-integrity" data-label="Restic integritás" onclick="triggerAction('btn-integrity','/api/debug/backup/integrity','POST')">Restic integritás</button>
|
||
<span class="debug-result" id="btn-integrity-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-infra-backup" data-label="Infra mentés" onclick="triggerAction('btn-infra-backup','/api/debug/backup/infra','POST')">Infra mentés</button>
|
||
<span class="debug-result" id="btn-infra-backup-result"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 4: Storage Testing -->
|
||
<div class="card debug-section" id="section-storage">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('storage')">
|
||
<h3>Tárhely teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="watchdog-status"><span class="text-muted">Betöltés...</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 5: Hub & Connectivity -->
|
||
<div class="card debug-section" id="section-hub">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('hub')">
|
||
<h3>Hub & Kapcsolatok</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="hub-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primary btn-sm" id="btn-hub-push" data-label="Hub jelentés küldése" onclick="triggerAction('btn-hub-push','/api/debug/hub/push','POST')">Hub jelentés küldése</button>
|
||
<span class="debug-result" id="btn-hub-push-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-hub-infra" data-label="Infra backup küldése" onclick="triggerAction('btn-hub-infra','/api/debug/hub/infra-push','POST')">Infra backup küldése</button>
|
||
<span class="debug-result" id="btn-hub-infra-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-hub-conn" data-label="Hub elérhetőség" onclick="triggerAction('btn-hub-conn','/api/debug/hub/test-connectivity','POST')">Hub elérhetőség</button>
|
||
<span class="debug-result" id="btn-hub-conn-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-pref-sync" data-label="Preferencia szinkron" onclick="triggerAction('btn-pref-sync','/api/debug/hub/preferences-sync','POST')">Preferencia szinkron</button>
|
||
<span class="debug-result" id="btn-pref-sync-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-gitea-conn" data-label="Gitea elérhetőség" onclick="triggerAction('btn-gitea-conn','/api/debug/gitea/test-connectivity','POST')">Gitea elérhetőség</button>
|
||
<span class="debug-result" id="btn-gitea-conn-result"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section: Telemetry Testing -->
|
||
<div class="card debug-section" id="section-telemetry">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('telemetry')">
|
||
<h3>Telemetria teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="telemetry-status"><span class="text-muted">Kattintson a gombra a telemetria futtatásához.</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primary btn-sm" id="btn-telemetry-run" data-label="Telemetria futtatása" onclick="runTelemetryTest()">Telemetria futtatása</button>
|
||
<span class="debug-result" id="btn-telemetry-run-result"></span>
|
||
</div>
|
||
<div id="telemetry-detail" style="display:none; margin-top:1rem;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 6: Self-Update Testing -->
|
||
<div class="card debug-section" id="section-selfupdate">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('selfupdate')">
|
||
<h3>Önfrissítés teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="selfupdate-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primary btn-sm" id="btn-check-update" data-label="Frissítés keresése" onclick="triggerAction('btn-check-update','/api/selfupdate/check','POST')">Frissítés keresése</button>
|
||
<span class="debug-result" id="btn-check-update-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-dryrun" data-label="Dry-run frissítés" onclick="triggerAction('btn-dryrun','/api/debug/selfupdate/dry-run','POST')">Dry-run frissítés</button>
|
||
<span class="debug-result" id="btn-dryrun-result"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 7: DR / Setup Wizard -->
|
||
<div class="card debug-section" id="section-dr">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('dr')">
|
||
<h3>DR / Telepítő varázsló</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="dr-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions" style="margin-top:1rem">
|
||
<div class="debug-dr-danger">
|
||
<p style="color:var(--red);font-weight:600">Vészhelyzet szimuláció</p>
|
||
<p class="text-muted" style="font-size:.85rem;margin-bottom:.5rem">Ez törli az ügyfél azonosítót és újraindítja a controllert setup módba. A konfigurációs fájlok megmaradnak a meghajtókon.</p>
|
||
<div class="debug-form-row">
|
||
<label>Írja be "RESET" a megerősítéshez:</label>
|
||
<input type="text" id="dr-confirm-input" class="form-control debug-confirm-input" placeholder="RESET" autocomplete="off">
|
||
<button class="btn btn-danger btn-sm" id="btn-dr-trigger" data-label="Újraindítás setup módban" onclick="triggerDR()">Újraindítás setup módban</button>
|
||
<span class="debug-result" id="btn-dr-trigger-result"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 9: App Export/Import -->
|
||
<div class="card debug-section" id="section-appexport">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('appexport')">
|
||
<h3>Alkalmazás Export/Import</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="appexport-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions" style="margin-top:.75rem">
|
||
<button class="btn btn-secondary btn-sm" id="btn-appexport-scan" data-label="Csomagok keresése" onclick="scanAppBundles()">Csomagok keresése</button>
|
||
<span class="debug-result" id="btn-appexport-scan-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-appexport-cleanup" data-label="Temp fájlok törlése" onclick="triggerAction('btn-appexport-cleanup','/api/debug/appexport/cleanup','POST')">Temp fájlok törlése</button>
|
||
<span class="debug-result" id="btn-appexport-cleanup-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-appexport-refresh" data-label="Frissítés" onclick="loadSectionData('appexport')">Frissítés</button>
|
||
</div>
|
||
<div id="appexport-bundles" style="display:none;margin-top:1rem"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 8: Log Viewer -->
|
||
<div class="card debug-section" id="section-logs">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('logs')">
|
||
<h3>Naplóviewer</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div class="debug-log-controls">
|
||
<div class="debug-log-filters">
|
||
<button class="btn btn-xs debug-log-filter active" data-level="DEBUG" onclick="setLogLevel('DEBUG',this)">DEBUG</button>
|
||
<button class="btn btn-xs debug-log-filter" data-level="INFO" onclick="setLogLevel('INFO',this)">INFO</button>
|
||
<button class="btn btn-xs debug-log-filter" data-level="WARN" onclick="setLogLevel('WARN',this)">WARN</button>
|
||
<button class="btn btn-xs debug-log-filter" data-level="ERROR" onclick="setLogLevel('ERROR',this)">ERROR</button>
|
||
</div>
|
||
<div>
|
||
<label style="font-size:.85rem;cursor:pointer">
|
||
<input type="checkbox" id="log-auto-refresh" checked onchange="toggleLogAutoRefresh()"> Automatikus frissítés
|
||
</label>
|
||
<button class="btn btn-xs btn-secondary" onclick="clearLogDisplay()">Törlés</button>
|
||
<span class="text-muted" id="log-count" style="font-size:.85rem;margin-left:.5rem"></span>
|
||
</div>
|
||
</div>
|
||
<div class="debug-log-viewer" id="log-viewer"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ── Section toggle ──
|
||
var pollingIntervals = {};
|
||
function toggleSection(id) {
|
||
var body = document.querySelector('#section-' + id + ' .debug-section-body');
|
||
var toggle = document.querySelector('#section-' + id + ' .section-toggle');
|
||
var visible = body.style.display !== 'none';
|
||
body.style.display = visible ? 'none' : 'block';
|
||
toggle.textContent = visible ? '▶' : '▼';
|
||
if (!visible && !body.dataset.loaded) {
|
||
body.dataset.loaded = 'true';
|
||
loadSectionData(id);
|
||
}
|
||
// Stop polling when section is collapsed
|
||
if (visible) {
|
||
stopPolling(id);
|
||
}
|
||
}
|
||
|
||
function startPolling(id, interval, fn) {
|
||
stopPolling(id);
|
||
fn();
|
||
pollingIntervals[id] = setInterval(fn, interval);
|
||
}
|
||
function stopPolling(id) {
|
||
if (pollingIntervals[id]) {
|
||
clearInterval(pollingIntervals[id]);
|
||
delete pollingIntervals[id];
|
||
}
|
||
}
|
||
window.addEventListener('beforeunload', function() {
|
||
for (var k in pollingIntervals) { clearInterval(pollingIntervals[k]); }
|
||
});
|
||
|
||
// ── Standard action button pattern ──
|
||
function triggerAction(buttonId, url, method, body) {
|
||
var btn = document.getElementById(buttonId);
|
||
var result = document.getElementById(buttonId + '-result');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Folyamatban...';
|
||
result.className = 'debug-result';
|
||
result.textContent = '';
|
||
fetch(url, {
|
||
method: method || 'POST',
|
||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||
body: body ? JSON.stringify(body) : undefined
|
||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||
if (data.ok) {
|
||
result.className = 'debug-result debug-result-ok';
|
||
result.textContent = data.message || 'OK';
|
||
if (data.data && data.data.latency_ms) result.textContent += ' (' + data.data.latency_ms + 'ms)';
|
||
} else {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = data.error || 'Hiba';
|
||
}
|
||
}).catch(function(e) {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Hálózati hiba: ' + e.message;
|
||
}).finally(function() {
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
});
|
||
}
|
||
|
||
// ── Section data loaders (stubs — filled in later phases) ──
|
||
function loadSectionData(id) {
|
||
switch (id) {
|
||
case 'diagnostic': loadDiagnostic(); break;
|
||
case 'events': loadEventHistory(); break;
|
||
case 'backup': loadBackupStatus(); break;
|
||
case 'storage': loadWatchdogStatus(); break;
|
||
case 'hub': loadHubStatus(); break;
|
||
case 'telemetry': break; // no auto-load, user triggers manually
|
||
case 'selfupdate': loadSelfUpdateStatus(); break;
|
||
case 'dr': loadDRStatus(); break;
|
||
case 'appexport': loadAppExportStatus(); break;
|
||
case 'logs': initLogViewer(); break;
|
||
}
|
||
}
|
||
|
||
// ── Section 1: Diagnostic ──
|
||
function loadDiagnostic() {
|
||
document.getElementById('diagnostic-content').innerHTML = '<span class="text-muted">Betöltés...</span>';
|
||
fetch('/api/debug/dump', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
renderDiagnostic(data);
|
||
}).catch(function(e) {
|
||
document.getElementById('diagnostic-content').innerHTML = '<span class="debug-result-error">Hiba: ' + e.message + '</span>';
|
||
});
|
||
}
|
||
function renderDiagnostic(d) {
|
||
var html = '';
|
||
// Controller info
|
||
if (d.controller) {
|
||
var c = d.controller;
|
||
var uptime = c.uptime_seconds;
|
||
var uptimeStr = uptime < 60 ? uptime + 'mp' : uptime < 3600 ? Math.floor(uptime/60) + 'p' : Math.floor(uptime/3600) + 'ó ' + Math.floor((uptime%3600)/60) + 'p';
|
||
html += '<div class="debug-kv-grid"><dt>Verzió</dt><dd class="mono">' + c.version + '</dd>';
|
||
html += '<dt>Uptime</dt><dd>' + uptimeStr + '</dd>';
|
||
html += '<dt>PID</dt><dd>' + c.pid + '</dd>';
|
||
html += '<dt>Log szint</dt><dd>' + c.logging_level + '</dd>';
|
||
if (c.config_hash) html += '<dt>Config hash</dt><dd class="mono" style="font-size:.75rem">' + c.config_hash.substring(0,12) + '…</dd>';
|
||
html += '</div>';
|
||
}
|
||
// Storage
|
||
if (d.storage && d.storage.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem">Tárhely</h4><table class="info-table debug-table"><tr><th>Útvonal</th><th>Cimke</th><th>Állapot</th><th>Használat</th></tr>';
|
||
d.storage.forEach(function(s) {
|
||
var status = s.disconnected ? '<span class="status-dot red"></span> Leválasztva' : s.decommissioned ? '<span class="status-dot gray"></span> Leszerelve' : '<span class="status-dot green"></span> OK';
|
||
var usage = s.used_percent !== undefined ? s.used_gb.toFixed(1) + ' / ' + s.total_gb.toFixed(1) + ' GB (' + s.used_percent.toFixed(0) + '%)' : '-';
|
||
html += '<tr><td class="mono">' + s.path + '</td><td>' + (s.label||'-') + '</td><td>' + status + '</td><td>' + usage + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
}
|
||
// Stacks
|
||
if (d.stacks) {
|
||
html += '<h4 style="margin-top:.75rem">Stack-ek</h4>';
|
||
html += '<div class="debug-kv-grid"><dt>Telepítve</dt><dd>' + d.stacks.deployed + '</dd><dt>Futó konténerek</dt><dd>' + d.stacks.running + '</dd><dt>Leállt</dt><dd>' + d.stacks.stopped + '</dd></div>';
|
||
if (d.stacks.list && d.stacks.list.length > 0) {
|
||
html += '<table class="info-table debug-table"><tr><th>Név</th><th>Állapot</th><th>Konténerek</th></tr>';
|
||
d.stacks.list.forEach(function(st) {
|
||
var stateDot = st.state === 'running' ? '<span class="status-dot green"></span>' : st.state === 'stopped' ? '<span class="status-dot red"></span>' : '<span class="status-dot yellow"></span>';
|
||
html += '<tr><td>' + (st.display_name || st.name) + '</td><td>' + stateDot + ' ' + st.state + '</td><td class="mono" style="font-size:.75rem">' + (st.containers||[]).join(', ') + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
}
|
||
}
|
||
// Scheduler
|
||
if (d.scheduler && d.scheduler.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem">Ütemező</h4><table class="info-table debug-table"><tr><th>Név</th><th>Típus</th><th>Utolsó futás</th><th>Fut</th></tr>';
|
||
d.scheduler.forEach(function(j) {
|
||
var type = j.type === 'daily' ? j.schedule : (j.interval || '-');
|
||
html += '<tr><td>' + j.name + '</td><td>' + type + '</td><td>' + (j.last_run ? fmtTime(j.last_run) : '-') + '</td><td>' + (j.running ? '🔄' : '-') + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
}
|
||
// Alerts
|
||
if (d.alerts && d.alerts.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem">Figyelmeztetések</h4>';
|
||
d.alerts.forEach(function(a) {
|
||
var cls = a.level === 'error' ? 'debug-result-error' : a.level === 'warning' ? 'debug-result debug-result-pending' : '';
|
||
html += '<div class="' + cls + '" style="padding:.3rem .5rem;margin-bottom:.25rem;border-radius:4px;font-size:.85rem">' + a.message + '</div>';
|
||
});
|
||
}
|
||
document.getElementById('diagnostic-content').innerHTML = html;
|
||
}
|
||
function downloadDump() {
|
||
fetch('/api/debug/dump', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
var blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
|
||
var a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = 'debug-dump-' + new Date().toISOString().replace(/[:.]/g,'-') + '.json';
|
||
a.click();
|
||
});
|
||
}
|
||
|
||
// ── Section 2: Events ──
|
||
function sendTestEvent() {
|
||
var eventType = document.getElementById('event-type').value;
|
||
var severity = document.getElementById('event-severity').value;
|
||
triggerAction('btn-test-event', '/api/debug/event/test', 'POST', {event_type: eventType, severity: severity});
|
||
setTimeout(loadEventHistory, 1500);
|
||
}
|
||
function loadEventHistory() {
|
||
fetch('/api/debug/event/history', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok || !data.data || data.data.length === 0) {
|
||
document.getElementById('event-history').innerHTML = '<span class="text-muted">Nincs esemény</span>';
|
||
return;
|
||
}
|
||
var html = '<table class="info-table debug-table"><tr><th>Idő</th><th>Típus</th><th>Súlyosság</th><th>Hub</th></tr>';
|
||
data.data.forEach(function(e) {
|
||
var statusCls = e.hub_status >= 200 && e.hub_status < 300 ? 'debug-result-ok' : 'debug-result-error';
|
||
html += '<tr><td>' + fmtTime(e.timestamp) + '</td><td>' + e.event_type + '</td><td>' + e.severity + '</td><td class="' + statusCls + '">' + (e.hub_status || e.hub_error || '-') + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
document.getElementById('event-history').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('event-history').innerHTML = '<span class="text-muted">Hiba</span>';
|
||
});
|
||
}
|
||
|
||
// ── Section 3: Backup ──
|
||
function loadBackupStatus() {
|
||
fetch('/api/backup/status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) { document.getElementById('backup-status').innerHTML = '<span class="text-muted">Nem elérhető</span>'; return; }
|
||
var d = data.data || {};
|
||
var html = '<div class="debug-kv-grid">';
|
||
html += '<span>Állapot:</span><span>' + (d.running ? '🔄 Fut' : '✅ Kész') + '</span>';
|
||
if (d.last_db_dump) html += '<span>Utolsó DB dump:</span><span>' + fmtTime(d.last_db_dump) + '</span>';
|
||
if (d.last_backup) html += '<span>Utolsó restic:</span><span>' + fmtTime(d.last_backup) + '</span>';
|
||
if (d.snapshot_count !== undefined) html += '<span>Snapshotok:</span><span>' + d.snapshot_count + '</span>';
|
||
html += '</div>';
|
||
document.getElementById('backup-status').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('backup-status').innerHTML = '<span class="text-muted">Nem elérhető</span>';
|
||
});
|
||
}
|
||
|
||
// ── Section 4: Storage ──
|
||
function loadWatchdogStatus() {
|
||
fetch('/api/debug/storage/watchdog-status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) { document.getElementById('watchdog-status').innerHTML = '<span class="text-muted">Nem elérhető</span>'; return; }
|
||
renderWatchdogStatus(data.data);
|
||
}).catch(function() {
|
||
document.getElementById('watchdog-status').innerHTML = '<span class="text-muted">Nem elérhető</span>';
|
||
});
|
||
startPolling('storage', 5000, function() {
|
||
fetch('/api/debug/storage/watchdog-status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) renderWatchdogStatus(data.data);
|
||
}).catch(function(){});
|
||
});
|
||
}
|
||
function renderWatchdogStatus(paths) {
|
||
if (!paths || paths.length === 0) {
|
||
document.getElementById('watchdog-status').innerHTML = '<span class="text-muted">Nincs tárhely</span>';
|
||
return;
|
||
}
|
||
var html = '<table class="info-table debug-table"><tr><th>Útvonal</th><th>Cimke</th><th>Állapot</th><th>Probe</th><th>Debounce</th><th>Latency</th><th>Művelet</th></tr>';
|
||
paths.forEach(function(p) {
|
||
var dot = p.status === 'connected' ? '<span class="status-dot green"></span>' : '<span class="status-dot red"></span>';
|
||
var simBadge = p.simulated ? ' <span class="badge-warn">SIM</span>' : '';
|
||
var action = '';
|
||
if (p.status === 'connected' && !p.simulated) {
|
||
action = '<button class="btn btn-xs btn-secondary" onclick="simulateDisconnect(\'' + p.path + '\')">Leválasztás</button>';
|
||
} else if (p.simulated) {
|
||
action = '<button class="btn btn-xs btn-primary" onclick="simulateReconnect(\'' + p.path + '\')">Visszacsatl.</button>';
|
||
}
|
||
html += '<tr><td class="mono">' + p.path + '</td><td>' + (p.label||'-') + '</td><td>' + dot + ' ' + p.status + simBadge + '</td>';
|
||
html += '<td>' + (p.probe_ok_count||0) + '/' + (p.probe_count||0) + '</td><td>' + (p.debounce_count||0) + '/' + (p.debounce_max||3) + '</td>';
|
||
html += '<td>' + (p.avg_latency_ms ? p.avg_latency_ms.toFixed(1) + 'ms' : '-') + '</td><td>' + action + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
document.getElementById('watchdog-status').innerHTML = html;
|
||
}
|
||
function simulateDisconnect(path) {
|
||
if (!confirm('Biztosan szimulálja a leválasztást?\n\nEz leállítja az érintett alkalmazásokat.')) return;
|
||
fetch('/api/debug/storage/simulate-disconnect', {
|
||
method: 'POST',
|
||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||
body: JSON.stringify({path: path})
|
||
}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) alert('Hiba: ' + (data.error || 'ismeretlen'));
|
||
loadWatchdogStatus();
|
||
}).catch(function(e) { alert('Hiba: ' + e.message); });
|
||
}
|
||
function simulateReconnect(path) {
|
||
fetch('/api/debug/storage/simulate-reconnect', {
|
||
method: 'POST',
|
||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||
body: JSON.stringify({path: path})
|
||
}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) alert('Hiba: ' + (data.error || 'ismeretlen'));
|
||
loadWatchdogStatus();
|
||
}).catch(function(e) { alert('Hiba: ' + e.message); });
|
||
}
|
||
|
||
// ── Section 5: Hub ──
|
||
function loadHubStatus() {
|
||
fetch('/api/debug/dump', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
var h = data.hub || {};
|
||
var html = '<div class="debug-kv-grid">';
|
||
html += '<span>URL:</span><span class="mono">' + (h.url||'-') + '</span>';
|
||
html += '<span>Utolsó push:</span><span>' + (h.last_success ? fmtTime(h.last_success) : '-') + '</span>';
|
||
html += '<span>Hibák egymás után:</span><span>' + (h.consecutive_failures||0) + '</span>';
|
||
if (h.last_error) html += '<span>Utolsó hiba:</span><span class="debug-result-error">' + h.last_error + '</span>';
|
||
html += '</div>';
|
||
document.getElementById('hub-status').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('hub-status').innerHTML = '<span class="text-muted">Nem elérhető</span>';
|
||
});
|
||
}
|
||
|
||
// ── Section 6: Self-update ──
|
||
function loadSelfUpdateStatus() {
|
||
fetch('/api/debug/dump', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
var u = data.self_update || {};
|
||
var html = '<div class="debug-kv-grid">';
|
||
html += '<span>Engedélyezve:</span><span>' + (u.enabled ? 'Igen' : 'Nem') + '</span>';
|
||
html += '<span>Automatikus:</span><span>' + (u.auto ? 'Igen' : 'Nem') + '</span>';
|
||
if (u.last_check) {
|
||
html += '<span>Jelenlegi:</span><span class="mono">' + u.last_check.current_version + '</span>';
|
||
html += '<span>Legújabb:</span><span class="mono">' + u.last_check.latest_version + '</span>';
|
||
html += '<span>Frissítés:</span><span>' + (u.last_check.update_available ? '<span class="state-text-green">Elérhető</span>' : 'Naprakész') + '</span>';
|
||
}
|
||
html += '</div>';
|
||
document.getElementById('selfupdate-status').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('selfupdate-status').innerHTML = '<span class="text-muted">Nem elérhető</span>';
|
||
});
|
||
}
|
||
|
||
// ── Section 7: DR ──
|
||
function loadDRStatus() {
|
||
fetch('/api/debug/dr/infra-status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) { document.getElementById('dr-status').innerHTML = '<span class="text-muted">Nem elérhető</span>'; return; }
|
||
var d = data.data || {};
|
||
var html = '<h4>Helyi infra backup</h4>';
|
||
if (d.drives && d.drives.length > 0) {
|
||
html += '<table class="info-table debug-table"><tr><th>Meghajtó</th><th>Cimke</th><th>Állapot</th><th>Utolsó módosítás</th><th>Fájlok</th></tr>';
|
||
d.drives.forEach(function(dr) {
|
||
var files = (dr.files || []).join(', ') || '-';
|
||
html += '<tr><td class="mono">' + dr.path + '</td><td>' + (dr.label||'-') + '</td><td>' + (dr.has_backup ? '✅ Van' : '❌ Nincs') + '</td><td>' + (dr.last_modified ? fmtTime(dr.last_modified) : '-') + '</td><td class="text-muted" style="font-size:.75rem">' + files + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
} else {
|
||
html += '<span class="text-muted">Nincs csatlakoztatott meghajtó</span>';
|
||
}
|
||
if (d.hub_infra_push) {
|
||
html += '<h4>Hub infra backup</h4><div class="debug-kv-grid">';
|
||
html += '<span>Utolsó push:</span><span>' + (d.hub_infra_push.last_success ? fmtTime(d.hub_infra_push.last_success) : '-') + '</span>';
|
||
if (d.hub_infra_push.last_error) html += '<span>Utolsó hiba:</span><span class="debug-result-error">' + d.hub_infra_push.last_error + '</span>';
|
||
html += '</div>';
|
||
}
|
||
document.getElementById('dr-status').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('dr-status').innerHTML = '<span class="text-muted">Nem elérhető</span>';
|
||
});
|
||
}
|
||
function triggerDR() {
|
||
var input = document.getElementById('dr-confirm-input');
|
||
if (input.value !== 'RESET') {
|
||
var result = document.getElementById('btn-dr-trigger-result');
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Írja be: RESET';
|
||
return;
|
||
}
|
||
if (!confirm('FIGYELEM! Ez újraindítja a controllert setup módba.\n\nBiztosan folytatja?')) return;
|
||
var btn = document.getElementById('btn-dr-trigger');
|
||
var result = document.getElementById('btn-dr-trigger-result');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Folyamatban...';
|
||
fetch('/api/debug/dr/trigger-setup', {
|
||
method: 'POST',
|
||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||
body: JSON.stringify({confirm: 'RESET'})
|
||
}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) {
|
||
result.className = 'debug-result debug-result-ok';
|
||
result.textContent = 'Controller újraindulása...';
|
||
} else {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = data.error || 'Hiba';
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
}
|
||
}).catch(function(e) {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Hiba: ' + e.message;
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
});
|
||
}
|
||
|
||
// ── Section 8: Log viewer ──
|
||
var currentLogLevel = 'DEBUG';
|
||
var lastLogTimestamp = '';
|
||
function setLogLevel(level, btn) {
|
||
currentLogLevel = level;
|
||
document.querySelectorAll('.debug-log-filter').forEach(function(b) { b.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
refreshLogs();
|
||
}
|
||
function initLogViewer() {
|
||
refreshLogs();
|
||
if (document.getElementById('log-auto-refresh').checked) {
|
||
startPolling('logs', 2000, function() { refreshLogs(true); });
|
||
}
|
||
}
|
||
function toggleLogAutoRefresh() {
|
||
if (document.getElementById('log-auto-refresh').checked) {
|
||
startPolling('logs', 2000, function() { refreshLogs(true); });
|
||
} else {
|
||
stopPolling('logs');
|
||
}
|
||
}
|
||
function refreshLogs(append) {
|
||
var url = '/api/debug/logs?level=' + currentLogLevel + '&limit=500';
|
||
if (append && lastLogTimestamp) url += '&after=' + encodeURIComponent(lastLogTimestamp);
|
||
fetch(url, {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) return;
|
||
var entries = (data.data && data.data.entries) || [];
|
||
var total = (data.data && data.data.total) || 0;
|
||
var viewer = document.getElementById('log-viewer');
|
||
if (!append) viewer.innerHTML = '';
|
||
entries.forEach(function(e) {
|
||
var cls = 'debug-log-entry debug-log-' + e.level.toLowerCase();
|
||
var ts = e.timestamp ? fmtTime(e.timestamp) : '';
|
||
var src = e.source ? ' <span class="text-muted">' + e.source + '</span>' : '';
|
||
var div = document.createElement('div');
|
||
div.className = cls;
|
||
div.innerHTML = '<span class="text-muted">' + ts + '</span> <span class="debug-log-level">[' + e.level + ']</span>' + src + ' ' + escapeHtml(e.message);
|
||
viewer.appendChild(div);
|
||
if (e.timestamp) lastLogTimestamp = e.timestamp;
|
||
});
|
||
if (!append || entries.length > 0) {
|
||
viewer.scrollTop = viewer.scrollHeight;
|
||
}
|
||
document.getElementById('log-count').textContent = viewer.children.length + ' / ' + total + ' bejegyzés';
|
||
}).catch(function(){});
|
||
}
|
||
function clearLogDisplay() {
|
||
document.getElementById('log-viewer').innerHTML = '';
|
||
document.getElementById('log-count').textContent = '';
|
||
lastLogTimestamp = '';
|
||
}
|
||
|
||
// ── Telemetry test ──
|
||
function runTelemetryTest() {
|
||
var btn = document.getElementById('btn-telemetry-run');
|
||
var result = document.getElementById('btn-telemetry-run-result');
|
||
var detail = document.getElementById('telemetry-detail');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Folyamatban...';
|
||
result.className = 'debug-result';
|
||
result.textContent = '';
|
||
detail.style.display = 'none';
|
||
|
||
fetch('/api/debug/telemetry', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) {
|
||
result.className = 'debug-result debug-result-ok';
|
||
result.textContent = data.message;
|
||
if (data.data && data.data.app_telemetry) {
|
||
renderTelemetryDetail(data.data);
|
||
}
|
||
} else {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = data.error || 'Hiba';
|
||
}
|
||
}).catch(function(e) {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Hálózati hiba: ' + e.message;
|
||
}).finally(function() {
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
});
|
||
}
|
||
|
||
function renderTelemetryDetail(data) {
|
||
var detail = document.getElementById('telemetry-detail');
|
||
var apps = data.app_telemetry || [];
|
||
if (apps.length === 0) {
|
||
detail.innerHTML = '<span class="text-muted">Nincs telepített alkalmazás vagy nincs mérési adat.</span>';
|
||
detail.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
var html = '<table class="debug-table" style="width:100%;font-size:.85rem">';
|
||
html += '<thead><tr><th>Alkalmazás</th><th>Konténerek</th><th>Memória (jelen.)</th><th>Memória (átlag)</th><th>Memória (csúcs)</th><th>CPU (átlag)</th><th>Katalógus limit</th><th>Hibák</th><th>Figyelmeztetések</th></tr></thead><tbody>';
|
||
|
||
for (var i = 0; i < apps.length; i++) {
|
||
var a = apps[i];
|
||
var errorClass = a.log_errors > 0 ? ' style="color:var(--red);font-weight:600"' : '';
|
||
var warnClass = a.log_warnings > 0 ? ' style="color:var(--yellow);font-weight:600"' : '';
|
||
html += '<tr>';
|
||
html += '<td><strong>' + escapeHtml(a.display_name || a.app_name) + '</strong></td>';
|
||
html += '<td class="mono" style="font-size:.8rem">' + (a.containers||[]).map(escapeHtml).join(', ') + '</td>';
|
||
html += '<td>' + (a.memory_current_mb||0).toFixed(1) + ' MB</td>';
|
||
html += '<td>' + (a.memory_avg_mb||0).toFixed(1) + ' MB</td>';
|
||
html += '<td>' + (a.memory_peak_mb||0).toFixed(1) + ' MB</td>';
|
||
html += '<td>' + (a.cpu_avg_percent||0).toFixed(1) + '%</td>';
|
||
html += '<td class="mono">' + escapeHtml(a.catalog_limit || '-') + '</td>';
|
||
html += '<td' + errorClass + '>' + (a.log_errors||0) + '</td>';
|
||
html += '<td' + warnClass + '>' + (a.log_warnings||0) + '</td>';
|
||
html += '</tr>';
|
||
|
||
if (a.issues && a.issues.length > 0) {
|
||
for (var j = 0; j < a.issues.length; j++) {
|
||
var issue = a.issues[j];
|
||
var sevColor = issue.severity === 'error' ? 'var(--red)' : 'var(--yellow)';
|
||
html += '<tr style="font-size:.8rem;opacity:.85"><td></td>';
|
||
html += '<td colspan="6" style="padding-left:1.5rem"><span style="color:' + sevColor + '">' + escapeHtml(issue.severity.toUpperCase()) + '</span> ' + escapeHtml(issue.message) + '</td>';
|
||
html += '<td colspan="2">×' + (issue.count||0) + '</td></tr>';
|
||
}
|
||
}
|
||
}
|
||
html += '</tbody></table>';
|
||
|
||
html += '<details style="margin-top:1rem"><summary class="text-muted" style="cursor:pointer;font-size:.85rem">Nyers JSON</summary>';
|
||
html += '<pre class="mono" style="font-size:.75rem;max-height:400px;overflow:auto;padding:.5rem;background:rgba(0,0,0,.3);border-radius:.25rem;margin-top:.5rem">' + escapeHtml(JSON.stringify(apps, null, 2)) + '</pre>';
|
||
html += '</details>';
|
||
|
||
detail.innerHTML = html;
|
||
detail.style.display = 'block';
|
||
}
|
||
|
||
// ── Section 9: App Export/Import ──
|
||
function loadAppExportStatus() {
|
||
document.getElementById('appexport-status').innerHTML = '<span class="text-muted">Betöltés...</span>';
|
||
fetch('/api/debug/appexport/status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) { document.getElementById('appexport-status').innerHTML = '<span class="text-muted">Nem elérhető</span>'; return; }
|
||
renderAppExportStatus(data.data);
|
||
}).catch(function(e) {
|
||
document.getElementById('appexport-status').innerHTML = '<span class="debug-result-error">Hiba: ' + e.message + '</span>';
|
||
});
|
||
}
|
||
function renderAppExportStatus(d) {
|
||
if (!d.available) {
|
||
document.getElementById('appexport-status').innerHTML = '<span class="text-muted">App export modul nem elérhető</span>';
|
||
return;
|
||
}
|
||
var html = '<div class="debug-kv-grid">';
|
||
html += '<dt>Debug mód</dt><dd>' + (d.debug_enabled ? '<span class="state-text-green">Aktív</span>' : 'Inaktív') + '</dd>';
|
||
html += '<dt>Verzió</dt><dd class="mono">' + (d.version||'-') + '</dd>';
|
||
html += '<dt>Csomagok</dt><dd>' + (d.bundle_count||0) + ' db</dd>';
|
||
html += '<dt>Temp fájlok</dt><dd>' + (d.stale_temp_count||0) + ' db';
|
||
if (d.stale_temp_count > 0) html += ' <span style="color:var(--warning)">⚠</span>';
|
||
html += '</dd>';
|
||
html += '</div>';
|
||
|
||
// Active job
|
||
if (d.has_active_job && d.active_job) {
|
||
var j = d.active_job;
|
||
html += '<h4 style="margin-top:.75rem">Aktív feladat</h4>';
|
||
html += '<div class="debug-kv-grid">';
|
||
html += '<dt>Típus</dt><dd>' + (j.job_type||'-') + '</dd>';
|
||
html += '<dt>Stack</dt><dd>' + (j.display_name || j.stack_name || '-') + '</dd>';
|
||
html += '<dt>Állapot</dt><dd>' + (j.running ? '🔄 Fut' : j.done ? '✅ Kész' : '⏸ Várakozik') + '</dd>';
|
||
if (j.error) html += '<dt>Hiba</dt><dd class="debug-result-error">' + escapeHtml(j.error) + '</dd>';
|
||
if (j.output_path) html += '<dt>Kimenet</dt><dd class="mono" style="font-size:.75rem">' + escapeHtml(j.output_path) + '</dd>';
|
||
if (j.output_size) html += '<dt>Méret</dt><dd>' + j.output_size + '</dd>';
|
||
html += '</div>';
|
||
if (j.steps && j.steps.length > 0) {
|
||
html += '<div style="margin-top:.5rem">';
|
||
j.steps.forEach(function(s) {
|
||
var icon = s.status === 'done' ? '✓' : s.status === 'running' ? '⟳' : s.status === 'failed' ? '✗' : '○';
|
||
var cls = s.status === 'failed' ? 'color:var(--danger)' : s.status === 'done' ? 'color:var(--success)' : s.status === 'running' ? 'color:var(--primary)' : 'color:var(--text-muted)';
|
||
html += '<div style="font-size:.85rem;padding:.1rem 0;' + cls + '">' + icon + ' ' + escapeHtml(s.label);
|
||
if (s.error) html += ' <span style="font-size:.8rem">(' + escapeHtml(s.error) + ')</span>';
|
||
html += '</div>';
|
||
});
|
||
html += '</div>';
|
||
}
|
||
}
|
||
|
||
// Export dirs
|
||
if (d.export_dirs && d.export_dirs.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem">Export könyvtárak</h4>';
|
||
html += '<table class="info-table debug-table"><tr><th>Útvonal</th><th>Cimke</th><th>Létezik</th></tr>';
|
||
d.export_dirs.forEach(function(dir) {
|
||
html += '<tr><td class="mono" style="font-size:.75rem">' + escapeHtml(dir.path) + '</td><td>' + (dir.label||'-') + '</td><td>' + (dir.exists ? '✅' : '❌') + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
}
|
||
|
||
// Stale temp files
|
||
if (d.stale_temp_files && d.stale_temp_files.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem;color:var(--warning)">Elavult temp fájlok</h4>';
|
||
html += '<ul style="font-size:.85rem;margin:0;padding-left:1.5rem">';
|
||
d.stale_temp_files.forEach(function(f) {
|
||
html += '<li class="mono" style="font-size:.75rem">' + escapeHtml(f) + '</li>';
|
||
});
|
||
html += '</ul>';
|
||
}
|
||
|
||
document.getElementById('appexport-status').innerHTML = html;
|
||
|
||
// Auto-refresh if a job is running
|
||
if (d.has_active_job && d.active_job && d.active_job.running) {
|
||
startPolling('appexport', 2000, function() {
|
||
fetch('/api/debug/appexport/status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) renderAppExportStatus(data.data);
|
||
}).catch(function(){});
|
||
});
|
||
}
|
||
}
|
||
function scanAppBundles() {
|
||
var btn = document.getElementById('btn-appexport-scan');
|
||
var result = document.getElementById('btn-appexport-scan-result');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Keresés...';
|
||
result.className = 'debug-result';
|
||
result.textContent = '';
|
||
fetch('/api/debug/appexport/bundles', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) {
|
||
result.className = 'debug-result debug-result-ok';
|
||
result.textContent = data.message;
|
||
if (data.data && data.data.bundles) {
|
||
renderAppBundles(data.data.bundles);
|
||
}
|
||
} else {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = data.error || 'Hiba';
|
||
}
|
||
}).catch(function(e) {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Hálózati hiba: ' + e.message;
|
||
}).finally(function() {
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
});
|
||
}
|
||
function renderAppBundles(bundles) {
|
||
var container = document.getElementById('appexport-bundles');
|
||
if (!bundles || bundles.length === 0) {
|
||
container.innerHTML = '<span class="text-muted">Nem található .fab csomag.</span>';
|
||
container.style.display = 'block';
|
||
return;
|
||
}
|
||
var html = '<table class="info-table debug-table"><tr><th>Alkalmazás</th><th>Dátum</th><th>Méret</th><th>Tároló</th><th>Titkos</th><th>DB</th><th>HDD</th><th>Elérés</th></tr>';
|
||
bundles.forEach(function(b) {
|
||
html += '<tr>';
|
||
html += '<td><strong>' + escapeHtml(b.display_name || b.app_name) + '</strong></td>';
|
||
html += '<td>' + (b.exported_at || '-') + '</td>';
|
||
html += '<td>' + (b.size_human || '-') + '</td>';
|
||
html += '<td>' + escapeHtml(b.drive_label || b.drive_path) + '</td>';
|
||
html += '<td>' + (b.encrypted ? '🔒' : '-') + '</td>';
|
||
html += '<td>' + (b.has_db ? '✅' : '-') + '</td>';
|
||
html += '<td>' + (b.needs_hdd ? '✅' : '-') + '</td>';
|
||
html += '<td class="mono" style="font-size:.7rem;max-width:200px;overflow:hidden;text-overflow:ellipsis">' + escapeHtml(b.path) + '</td>';
|
||
html += '</tr>';
|
||
});
|
||
html += '</table>';
|
||
container.innerHTML = html;
|
||
container.style.display = 'block';
|
||
}
|
||
|
||
// ── Helpers ──
|
||
function fmtTime(ts) {
|
||
if (!ts) return '-';
|
||
var d = new Date(ts);
|
||
if (isNaN(d.getTime())) return ts;
|
||
var pad = function(n) { return n < 10 ? '0' + n : n; };
|
||
var now = new Date();
|
||
var time = pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
||
if (d.toDateString() === now.toDateString()) return time;
|
||
return pad(d.getMonth()+1) + '-' + pad(d.getDate()) + ' ' + time;
|
||
}
|
||
function escapeHtml(s) {
|
||
var d = document.createElement('div');
|
||
d.textContent = s;
|
||
return d.innerHTML;
|
||
}
|
||
</script>
|
||
|
||
{{template "layout_end" .}}
|
||
{{end}}
|