Files

816 lines
45 KiB
HTML
Raw Permalink 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 "debug"}}
{{template "layout_start" .}}
<div class="page-header">
<h2>Debug</h2>
</div>
<div class="debug-banner">
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "debug.debug_mod_aktiv_ez_az"}}
</div>
<!-- Section 1: System Diagnostic -->
<div class="card debug-section" id="section-diagnostic">
<div class="card-header debug-section-header" onclick="toggleSection('diagnostic')">
<h3>{{T "debug.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">{{T "debug.betoltes"}}</span></div>
<div class="debug-actions">
<button class="btn btn-secondary btn-sm" id="btn-download-dump" data-label="{{T "debug.nyers_json_letoltese"}}" onclick="downloadDump()">{{T "debug.nyers_json_letoltese"}}</button>
<button class="btn btn-secondary btn-sm" id="btn-refresh-diag" data-label="{{T "debug.frissites"}}" onclick="loadSectionData('diagnostic')">{{T "debug.frissites"}}</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>{{T "debug.ertesites_teszt"}}</h3>
<span class="section-toggle">▶</span>
</div>
<div class="card-body debug-section-body" style="display:none">
<div class="debug-form-row">
<label>{{T "debug.esemeny_tipus"}}</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>{{T "debug.sulyossag"}}</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="{{T "debug.teszt_esemeny_kuldese"}}" onclick="sendTestEvent()">{{T "debug.teszt_esemeny_kuldese"}}</button>
<span class="debug-result" id="btn-test-event-result"></span>
</div>
<h4>{{T "debug.utolso_esemenyek"}}</h4>
<div id="event-history"><span class="text-muted">{{T "debug.betoltes"}}</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>{{T "debug.mentes_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">{{T "debug.betoltes"}}</span></div>
<div class="debug-actions">
<button class="btn btn-primary btn-sm" id="btn-full-backup" data-label="{{T "debug.teljes_mentes"}}" onclick="triggerAction('btn-full-backup','/api/backup/run','POST')">{{T "debug.teljes_mentes"}}</button>
<span class="debug-result" id="btn-full-backup-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-dbdump" data-label="{{T "debug.csak_db_dump"}}" onclick="triggerAction('btn-dbdump','/api/debug/backup/dbdump','POST')">{{T "debug.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="{{T "debug.csak_cross_drive"}}" onclick="triggerAction('btn-crossdrive','/api/debug/backup/crossdrive','POST')">{{T "debug.csak_cross_drive"}}</button>
<span class="debug-result" id="btn-crossdrive-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-nightchain" data-label="{{T "debug.night_chain"}}" onclick="triggerAction('btn-nightchain','/api/debug/backup/night-chain','POST')">{{T "debug.night_chain"}}</button>
<span class="debug-result" id="btn-nightchain-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-integrity" data-label="{{T "debug.restic_integritas"}}" onclick="triggerAction('btn-integrity','/api/debug/backup/integrity','POST')">{{T "debug.restic_integritas"}}</button>
<span class="debug-result" id="btn-integrity-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-offsite-proof" data-label="{{T "debug.tavoli_mentes_tartalma"}}" onclick="triggerAction('btn-offsite-proof','/api/debug/backup/offsite-proof','POST')">{{T "debug.tavoli_mentes_tartalma"}}</button>
<span class="debug-result" id="btn-offsite-proof-result"></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>{{T "debug.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">{{T "debug.betoltes"}}</span></div>
<div class="debug-actions">
<button class="btn btn-primary btn-sm" id="btn-hub-push" data-label="{{T "debug.hub_jelentes_kuldese"}}" onclick="triggerAction('btn-hub-push','/api/debug/hub/push','POST')">{{T "debug.hub_jelentes_kuldese"}}</button>
<span class="debug-result" id="btn-hub-push-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-hub-conn" data-label="{{T "debug.hub_elerhetoseg"}}" onclick="triggerAction('btn-hub-conn','/api/debug/hub/test-connectivity','POST')">{{T "debug.hub_elerhetoseg"}}</button>
<span class="debug-result" id="btn-hub-conn-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-pref-sync" data-label="{{T "debug.preferencia_szinkron"}}" onclick="triggerAction('btn-pref-sync','/api/debug/hub/preferences-sync','POST')">{{T "debug.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="{{T "debug.gitea_elerhetoseg"}}" onclick="triggerAction('btn-gitea-conn','/api/debug/gitea/test-connectivity','POST')">{{T "debug.gitea_elerhetoseg"}}</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>{{T "debug.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">{{T "debug.kattintson_a_gombra_a_telemetria"}}</span></div>
<div class="debug-actions">
<button class="btn btn-primary btn-sm" id="btn-telemetry-run" data-label="{{T "debug.telemetria_futtatasa"}}" onclick="runTelemetryTest()">{{T "debug.telemetria_futtatasa"}}</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>{{T "debug.onfrissites_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">{{T "debug.betoltes"}}</span></div>
<div class="debug-actions">
<button class="btn btn-primary btn-sm" id="btn-check-update" data-label="{{T "debug.frissites_keresese"}}" onclick="triggerAction('btn-check-update','/api/selfupdate/check','POST')">{{T "debug.frissites_keresese"}}</button>
<span class="debug-result" id="btn-check-update-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-dryrun" data-label="{{T "debug.dry_run_frissites"}}" onclick="triggerAction('btn-dryrun','/api/debug/selfupdate/dry-run','POST')">{{T "debug.dry_run_frissites"}}</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>{{T "debug.dr_telepito_varazslo"}}</h3>
<span class="section-toggle">▶</span>
</div>
<div class="card-body debug-section-body" style="display:none">
<div class="debug-actions" style="margin-top:1rem">
<div class="debug-dr-danger">
<p style="color:var(--crit);font-weight:600">{{T "debug.veszhelyzet_szimulacio"}}</p>
<p class="text-muted" style="font-size:.85rem;margin-bottom:.5rem">{{T "debug.ez_torli_az_ugyfel_azonositot"}}</p>
<div class="debug-form-row">
<label>{{T "debug.irja_be_reset_a_megerositeshez"}}</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="{{T "debug.ujrainditas_setup_modban"}}" onclick="triggerDR()">{{T "debug.ujrainditas_setup_modban"}}</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>{{T "debug.alkalmazas_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">{{T "debug.betoltes"}}</span></div>
<div class="debug-actions" style="margin-top:.75rem">
<button class="btn btn-secondary btn-sm" id="btn-appexport-scan" data-label="{{T "debug.csomagok_keresese"}}" onclick="scanAppBundles()">{{T "debug.csomagok_keresese"}}</button>
<span class="debug-result" id="btn-appexport-scan-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-appexport-cleanup" data-label="{{T "debug.temp_fajlok_torlese"}}" onclick="triggerAction('btn-appexport-cleanup','/api/debug/appexport/cleanup','POST')">{{T "debug.temp_fajlok_torlese"}}</button>
<span class="debug-result" id="btn-appexport-cleanup-result"></span>
<button class="btn btn-secondary btn-sm" id="btn-appexport-refresh" data-label="{{T "debug.frissites"}}" onclick="loadSectionData('appexport')">{{T "debug.frissites"}}</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>{{T "debug.naploviewer"}}</h3>
<span class="section-toggle">▶</span>
</div>
<div class="card-body debug-section-body" style="display:none">
<div class="debug-log-filters" style="margin-bottom:.5rem">
<button class="btn btn-xs debug-log-source active" id="log-tab-controller" onclick="setLogSource('controller',this)">{{T "debug.vezerlo"}}</button>
<button class="btn btn-xs debug-log-source" id="log-tab-agent" onclick="setLogSource('agent',this)">{{T "debug.ugynok"}}</button>
</div>
<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()"> {{T "debug.automatikus_frissites"}}
</label>
<button class="btn btn-xs btn-secondary" onclick="clearLogDisplay()">{{T "debug.torles"}}</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 = '{{T "debug.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 || '{{T "debug.hiba"}}';
}
}).catch(function(e) {
result.className = 'debug-result debug-result-error';
result.textContent = '{{T "debug.halozati_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 'hub': loadHubStatus(); break;
case 'telemetry': break; // no auto-load, user triggers manually
case 'selfupdate': loadSelfUpdateStatus(); break;
case 'appexport': loadAppExportStatus(); break;
case 'logs': initLogViewer(); break;
}
}
// ── Section 1: Diagnostic ──
function loadDiagnostic() {
document.getElementById('diagnostic-content').innerHTML = '<span class="text-muted">{{T "debug.betoltes"}}</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">{{T "debug.hiba_2"}} ' + 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 + '{{T "debug.mp"}}' : uptime < 3600 ? Math.floor(uptime/60) + '{{T "debug.p"}}' : Math.floor(uptime/3600) + '{{T "debug.o"}} ' + Math.floor((uptime%3600)/60) + '{{T "debug.p"}}';
html += '<div class="debug-kv-grid"><dt>{{T "debug.verzio"}}</dt><dd class="mono">' + c.version + '</dd>';
html += '<dt>Uptime</dt><dd>' + uptimeStr + '</dd>';
html += '<dt>PID</dt><dd>' + c.pid + '</dd>';
html += '<dt>{{T "debug.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 "debug.tarhely"}}</h4><table class="info-table debug-table"><tr><th>{{T "debug.utvonal"}}</th><th>{{T "debug.cimke"}}</th><th>{{T "debug.allapot"}}</th><th>{{T "debug.hasznalat"}}</th></tr>';
d.storage.forEach(function(s) {
var status = s.disconnected ? '<span class="status-dot red"></span> {{T "debug.levalasztva"}}' : s.decommissioned ? '<span class="status-dot gray"></span> {{T "debug.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>{{T "debug.telepitve"}}</dt><dd>' + d.stacks.deployed + '</dd><dt>{{T "debug.futo_kontenerek"}}</dt><dd>' + d.stacks.running + '</dd><dt>{{T "debug.leallt"}}</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>{{T "debug.nev"}}</th><th>{{T "debug.allapot"}}</th><th>{{T "debug.kontenerek"}}</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">{{T "debug.utemezo"}}</h4><table class="info-table debug-table"><tr><th>{{T "debug.nev"}}</th><th>{{T "debug.tipus"}}</th><th>{{T "debug.utolso_futas"}}</th><th>{{T "debug.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 ? '{{T "debug.fut_2"}}' : '-') + '</td></tr>';
});
html += '</table>';
}
// Alerts
if (d.alerts && d.alerts.length > 0) {
html += '<h4 style="margin-top:.75rem">{{T "debug.figyelmeztetesek"}}</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">{{T "debug.nincs_esemeny"}}</span>';
return;
}
var html = '<table class="info-table debug-table"><tr><th>{{T "debug.ido"}}</th><th>{{T "debug.tipus"}}</th><th>{{T "debug.sulyossag_2"}}</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">{{T "debug.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">{{T "debug.nem_elerheto"}}</span>'; return; }
var d = data.data || {};
var html = '<div class="debug-kv-grid">';
html += '<span>{{T "debug.allapot_2"}}</span><span>' + (d.running ? '{{T "debug.fut_3"}}' : '{{T "debug.kesz"}}') + '</span>';
if (d.last_db_dump) html += '<span>{{T "debug.utolso_db_dump"}}</span><span>' + fmtTime(d.last_db_dump) + '</span>';
if (d.last_backup) html += '<span>{{T "debug.utolso_restic"}}</span><span>' + fmtTime(d.last_backup) + '</span>';
if (d.snapshot_count !== undefined) html += '<span>{{T "debug.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">{{T "debug.nem_elerheto"}}</span>';
});
}
// ── 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>{{T "debug.utolso_push"}}</span><span>' + (h.last_success ? fmtTime(h.last_success) : '-') + '</span>';
html += '<span>{{T "debug.hibak_egymas_utan"}}</span><span>' + (h.consecutive_failures||0) + '</span>';
if (h.last_error) html += '<span>{{T "debug.utolso_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">{{T "debug.nem_elerheto"}}</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>{{T "debug.engedelyezve"}}</span><span>' + (u.enabled ? '{{T "debug.igen"}}' : '{{T "debug.nem"}}') + '</span>';
html += '<span>{{T "debug.automatikus"}}</span><span>' + (u.auto ? '{{T "debug.igen"}}' : '{{T "debug.nem"}}') + '</span>';
if (u.last_check) {
html += '<span>{{T "debug.jelenlegi"}}</span><span class="mono">' + u.last_check.current_version + '</span>';
html += '<span>{{T "debug.legujabb"}}</span><span class="mono">' + u.last_check.latest_version + '</span>';
html += '<span>{{T "debug.frissites_2"}}</span><span>' + (u.last_check.update_available ? '<span class="state-text-run">{{T "debug.elerheto"}}</span>' : '{{T "debug.naprakesz"}}') + '</span>';
}
html += '</div>';
document.getElementById('selfupdate-status').innerHTML = html;
}).catch(function() {
document.getElementById('selfupdate-status').innerHTML = '<span class="text-muted">{{T "debug.nem_elerheto"}}</span>';
});
}
// ── Section 7: DR ──
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 = '{{T "debug.irja_be_reset"}}';
return;
}
var btn = document.getElementById('btn-dr-trigger');
felhomConfirm(btn, '{{T "debug.figyelem_ez_ujrainditja_a_controllert"}}', function () {
var result = document.getElementById('btn-dr-trigger-result');
btn.disabled = true;
btn.textContent = '{{T "debug.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 = '{{T "debug.controller_ujraindulasa"}}';
} else {
result.className = 'debug-result debug-result-error';
result.textContent = data.error || '{{T "debug.hiba"}}';
btn.disabled = false;
btn.textContent = btn.dataset.label;
}
}).catch(function(e) {
result.className = 'debug-result debug-result-error';
result.textContent = '{{T "debug.hiba_2"}} ' + e.message;
btn.disabled = false;
btn.textContent = btn.dataset.label;
});
});
}
// ── Section 8: Log viewer (Vezérlő | Ügynök tabs — v0.116.0) ──
var currentLogLevel = 'DEBUG';
var currentLogSource = 'controller';
var lastLogTimestamp = '';
var LOG_LEVEL_PRIORITY = {DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3};
function setLogLevel(level, btn) {
currentLogLevel = level;
document.querySelectorAll('.debug-log-filter').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
refreshLogs();
}
function setLogSource(source, btn) {
currentLogSource = source;
document.querySelectorAll('.debug-log-source').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
clearLogDisplay();
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) {
if (currentLogSource === 'agent') { refreshAgentLogs(); return; }
var url = '/api/debug/logs?level=' + currentLogLevel + '&limit=1000';
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 + ' {{T "debug.bejegyzes"}}';
}).catch(function(){});
}
// The agent tab: full refresh each poll (the agent ring is small), client-side level
// filter. A pre-0.83 agent (unsupported) renders the notice instead of an error.
function refreshAgentLogs() {
fetch('/api/debug/agent-logs', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
var viewer = document.getElementById('log-viewer');
viewer.innerHTML = '';
if (!data.ok) {
viewer.innerHTML = '<div class="debug-log-entry debug-log-warn">' + escapeHtml(data.error || '{{T "debug.hiba"}}') + '</div>';
document.getElementById('log-count').textContent = '';
return;
}
if (data.data && data.data.unsupported) {
viewer.innerHTML = '<div class="debug-log-entry">' + escapeHtml(data.data.notice) + '</div>';
document.getElementById('log-count').textContent = '';
return;
}
var entries = (data.data && data.data.entries) || [];
var total = (data.data && data.data.total) || 0;
var min = LOG_LEVEL_PRIORITY[currentLogLevel] || 0;
entries.forEach(function(e) {
if ((LOG_LEVEL_PRIORITY[e.level] || 0) < min) return;
var div = document.createElement('div');
div.className = 'debug-log-entry debug-log-' + e.level.toLowerCase();
var ts = e.timestamp ? fmtTime(e.timestamp) : '';
div.innerHTML = '<span class="text-muted">' + ts + '</span> <span class="debug-log-level">[' + e.level + ']</span> ' + escapeHtml(e.message);
viewer.appendChild(div);
});
viewer.scrollTop = viewer.scrollHeight;
document.getElementById('log-count').textContent = viewer.children.length + ' / ' + total + ' {{T "debug.bejegyzes"}}';
}).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 = '{{T "debug.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 || '{{T "debug.hiba"}}';
}
}).catch(function(e) {
result.className = 'debug-result debug-result-error';
result.textContent = '{{T "debug.halozati_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">{{T "debug.nincs_telepitett_alkalmazas_vagy_nincs"}}</span>';
detail.style.display = 'block';
return;
}
var html = '<table class="debug-table" style="width:100%;font-size:.85rem">';
html += '<thead><tr><th>{{T "debug.alkalmazas"}}</th><th>{{T "debug.kontenerek"}}</th><th>{{T "debug.memoria_jelen"}}</th><th>{{T "debug.memoria_atlag"}}</th><th>{{T "debug.memoria_csucs"}}</th><th>{{T "debug.cpu_atlag"}}</th><th>{{T "debug.katalogus_limit"}}</th><th>{{T "debug.hibak"}}</th><th>{{T "debug.figyelmeztetesek"}}</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(--crit);font-weight:600"' : '';
var warnClass = a.log_warnings > 0 ? ' style="color:var(--warn);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(--crit)' : 'var(--warn)';
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">{{T "debug.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">{{T "debug.betoltes"}}</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">{{T "debug.nem_elerheto"}}</span>'; return; }
renderAppExportStatus(data.data);
}).catch(function(e) {
document.getElementById('appexport-status').innerHTML = '<span class="debug-result-error">{{T "debug.hiba_2"}} ' + e.message + '</span>';
});
}
function renderAppExportStatus(d) {
if (!d.available) {
document.getElementById('appexport-status').innerHTML = '<span class="text-muted">{{T "debug.app_export_modul_nem_elerheto"}}</span>';
return;
}
var html = '<div class="debug-kv-grid">';
html += '<dt>{{T "debug.debug_mod"}}</dt><dd>' + (d.debug_enabled ? '<span class="state-text-run">{{T "debug.aktiv"}}</span>' : '{{T "debug.inaktiv"}}') + '</dd>';
html += '<dt>{{T "debug.verzio"}}</dt><dd class="mono">' + (d.version||'-') + '</dd>';
html += '<dt>{{T "debug.csomagok"}}</dt><dd>' + (d.bundle_count||0) + ' {{T "debug.db"}}</dd>';
html += '<dt>{{T "debug.temp_fajlok"}}</dt><dd>' + (d.stale_temp_count||0) + ' {{T "debug.db"}}';
if (d.stale_temp_count > 0) html += ' <span style="color:var(--warn)"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg></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">{{T "debug.aktiv_feladat"}}</h4>';
html += '<div class="debug-kv-grid">';
html += '<dt>{{T "debug.tipus"}}</dt><dd>' + (j.job_type||'-') + '</dd>';
html += '<dt>Stack</dt><dd>' + (j.display_name || j.stack_name || '-') + '</dd>';
html += '<dt>{{T "debug.allapot"}}</dt><dd>' + (j.running ? '{{T "debug.fut_3"}}' : j.done ? '{{T "debug.kesz"}}' : '{{T "debug.varakozik"}}') + '</dd>';
if (j.error) html += '<dt>{{T "debug.hiba"}}</dt><dd class="debug-result-error">' + escapeHtml(j.error) + '</dd>';
if (j.output_path) html += '<dt>{{T "debug.kimenet"}}</dt><dd class="mono" style="font-size:.75rem">' + escapeHtml(j.output_path) + '</dd>';
if (j.output_size) html += '<dt>{{T "debug.meret"}}</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' ? '&#10003;' : s.status === 'running' ? '&#8635;' : s.status === 'failed' ? '&#215;' : '&#9675;';
var cls = s.status === 'failed' ? 'color:var(--crit)' : s.status === 'done' ? 'color:var(--blue)' : s.status === 'running' ? 'color:var(--blue)' : 'color:var(--text-3)';
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">{{T "debug.export_konyvtarak"}}</h4>';
html += '<table class="info-table debug-table"><tr><th>{{T "debug.utvonal"}}</th><th>{{T "debug.cimke"}}</th><th>{{T "debug.letezik"}}</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 ? '{{T "debug.igen_2"}}' : '<span class="state-text-warn">{{T "debug.nincs"}}</span>') + '</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(--warn)">{{T "debug.elavult_temp_fajlok"}}</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 = '{{T "debug.kereses"}}';
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 || '{{T "debug.hiba"}}';
}
}).catch(function(e) {
result.className = 'debug-result debug-result-error';
result.textContent = '{{T "debug.halozati_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">{{T "debug.nem_talalhato_fab_csomag"}}</span>';
container.style.display = 'block';
return;
}
var html = '<table class="info-table debug-table"><tr><th>{{T "debug.alkalmazas"}}</th><th>{{T "debug.datum"}}</th><th>{{T "debug.meret"}}</th><th>{{T "debug.tarolo"}}</th><th>{{T "debug.titkos"}}</th><th>DB</th><th>HDD</th><th>{{T "debug.eleres"}}</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 ? '{{T "debug.titkositott"}}' : '-') + '</td>';
html += '<td>' + (b.has_db ? '{{T "debug.igen_2"}}' : '-') + '</td>';
html += '<td>' + (b.needs_hdd ? '{{T "debug.igen_2"}}' : '-') + '</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}}