feat(debug): add Telemetria teszt section to debug page (v0.28.1)
- New GET /api/debug/telemetry endpoint runs full telemetry pipeline on-demand - GetTelemetryPreview callback added to DebugCallbacks, wired in main.go - BuildAppTelemetryForDebug() exported wrapper in report/telemetry.go - Debug page: new collapsible section with per-app table (memory, CPU, log errors/warnings, issues) and raw JSON viewer - Available regardless of hub configuration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/monitor"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/report"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
|
||||
)
|
||||
@@ -27,6 +28,7 @@ type DebugCallbacks struct {
|
||||
TriggerSetupMode func() error
|
||||
HubConnectivityTest func() (statusCode int, latencyMs int64, err error)
|
||||
GiteaConnectivityTest func() (statusCode int, latencyMs int64, err error)
|
||||
GetTelemetryPreview func() ([]report.AppTelemetry, error)
|
||||
}
|
||||
|
||||
// debugPageHandler renders the debug dashboard page.
|
||||
@@ -80,6 +82,10 @@ func (s *Server) handleDebugAPI(w http.ResponseWriter, r *http.Request) {
|
||||
case subpath == "gitea/test-connectivity" && r.Method == http.MethodPost:
|
||||
s.debugGiteaConnectivity(w, r)
|
||||
|
||||
// Section: Telemetry testing
|
||||
case subpath == "telemetry" && r.Method == http.MethodGet:
|
||||
s.debugTelemetry(w, r)
|
||||
|
||||
// Section 6: Self-update
|
||||
case subpath == "selfupdate/dry-run" && r.Method == http.MethodPost:
|
||||
s.debugSelfUpdateDryRun(w, r)
|
||||
@@ -538,6 +544,40 @@ func (s *Server) debugGiteaConnectivity(w http.ResponseWriter, r *http.Request)
|
||||
fmt.Sprintf("Gitea elérhető (HTTP %d, %dms)", statusCode, latency), data)
|
||||
}
|
||||
|
||||
// ── Section: Telemetry testing ───────────────────────────────────────
|
||||
|
||||
func (s *Server) debugTelemetry(w http.ResponseWriter, r *http.Request) {
|
||||
if s.debugCallbacks == nil || s.debugCallbacks.GetTelemetryPreview == nil {
|
||||
writeDebugJSON(w, http.StatusNotImplemented, false, "Nem bekötött", nil)
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
telemetry, err := s.debugCallbacks.GetTelemetryPreview()
|
||||
latency := time.Since(start).Milliseconds()
|
||||
if err != nil {
|
||||
writeDebugJSON(w, http.StatusOK, false, err.Error(), map[string]interface{}{"latency_ms": latency})
|
||||
return
|
||||
}
|
||||
|
||||
totalErrors := 0
|
||||
totalWarnings := 0
|
||||
for _, app := range telemetry {
|
||||
totalErrors += app.LogErrors
|
||||
totalWarnings += app.LogWarnings
|
||||
}
|
||||
|
||||
writeDebugJSON(w, http.StatusOK, true,
|
||||
fmt.Sprintf("Telemetria összegyűjtve: %d app, %d hiba, %d figyelmeztetés (%dms)",
|
||||
len(telemetry), totalErrors, totalWarnings, latency),
|
||||
map[string]interface{}{
|
||||
"latency_ms": latency,
|
||||
"app_count": len(telemetry),
|
||||
"total_errors": totalErrors,
|
||||
"total_warnings": totalWarnings,
|
||||
"app_telemetry": telemetry,
|
||||
})
|
||||
}
|
||||
|
||||
// ── Section 6: Self-update ──────────────────────────────────────────
|
||||
|
||||
func (s *Server) debugSelfUpdateDryRun(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -127,6 +127,22 @@
|
||||
</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')">
|
||||
@@ -266,6 +282,7 @@ function loadSectionData(id) {
|
||||
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 'logs': initLogViewer(); break;
|
||||
@@ -597,6 +614,85 @@ function clearLogDisplay() {
|
||||
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';
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
function fmtTime(ts) {
|
||||
if (!ts) return '-';
|
||||
|
||||
Reference in New Issue
Block a user