Files
felhom.eu/hub/internal/web/templates/app_detail.html
T
admin a757bee07a feat(hub): app telemetry analytics dashboard (v0.4.0)
- store/telemetry.go: new app_telemetry + app_log_issues tables with
  SaveAppTelemetry, GetFleetAppSummary (with P95), GetAppTelemetryHistory,
  GetAppCustomerBreakdown, GetCustomerAppSummary, GetAppIssues, prune methods
- api/handler.go: parse and save optional app_telemetry from report body,
  backward-compatible with old controllers
- cmd/hub/main.go: prune app_telemetry (90d) and stale issues (30d)
- web/apps.go: handleApps + handleAppDetail + chart data aggregation helpers
- web/server.go: routes for /apps, /apps/{name}, /static/chart.min.js;
  added memoryColor/accuracyClass/gt template functions
- web/embed.go: embed static/chart.min.js
- web/configs.go: add app telemetry section to handleCustomerUnified
- templates/apps.html: fleet-wide app list with summary cards and sortable table
- templates/app_detail.html: per-app page with Chart.js memory trend,
  customer breakdown, and known issues table
- templates/customer_unified.html: new Alkalmazás telemetria card
- templates/style.css: badge, summary-card, chart, period-selector,
  accuracy-dot, mem-color, data-table styles
- All templates: added Alkalmazások nav link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 10:46:50 +01:00

210 lines
9.1 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.AppName}} — Felhom Hub</title>
<link rel="stylesheet" href="/style.css">
<script src="/static/chart.min.js"></script>
</head>
<body>
<div class="container">
<header>
<h1>Felhom Hub</h1>
<nav class="nav-links">
<a href="/" class="nav-link">Dashboard</a>
<a href="/configs" class="nav-link">Customers</a>
<a href="/apps" class="nav-link active">Alkalmazások</a>
</nav>
</header>
<a href="/apps{{if .Period}}?period={{.Period}}{{end}}" class="back-link">&larr; Alkalmazások</a>
<!-- Period selector -->
<div class="period-selector" style="margin-top: 1rem;">
<a href="?period=24h" class="period-btn{{if eq .Period "24h"}} active{{end}}">24 óra</a>
<a href="?period=7d" class="period-btn{{if or (eq .Period "7d") (eq .Period "")}} active{{end}}">7 nap</a>
<a href="?period=30d" class="period-btn{{if eq .Period "30d"}} active{{end}}">30 nap</a>
</div>
<!-- Overview card -->
<section class="card">
<h2>{{if .Summary}}{{if .Summary.DisplayName}}{{.Summary.DisplayName}}{{else}}{{.AppName}}{{end}}{{else}}{{.AppName}}{{end}}</h2>
<div class="info-grid">
<div class="info-item">
<span class="label">App neve</span>
<span class="value" style="font-family: var(--font-mono)">{{.AppName}}</span>
</div>
{{if .Summary}}
<div class="info-item">
<span class="label">Telepítések</span>
<span class="value">{{.Summary.DeploymentCount}}</span>
</div>
<div class="info-item">
<span class="label">Katalógus becslés</span>
<span class="value">{{if .Summary.CatalogEstimate}}{{.Summary.CatalogEstimate}}{{else}}—{{end}}</span>
</div>
<div class="info-item">
<span class="label">Katalógus limit</span>
<span class="value">{{if .Summary.CatalogLimit}}{{.Summary.CatalogLimit}}{{else}}—{{end}}</span>
</div>
{{if .SuggestedLimit}}
<div class="info-item">
<span class="label">Javasolt limit (P95×1.2)</span>
<span class="value" style="color: var(--yellow)">{{.SuggestedLimit}} MB</span>
</div>
{{end}}
<div class="info-item">
<span class="label">Átl. memória</span>
<span class="value">{{formatFloat .Summary.AvgMemoryMB}} MB</span>
</div>
<div class="info-item">
<span class="label">P95 memória</span>
<span class="value {{accuracyClass .Summary.P95MemoryMB .Summary.CatalogLimit}}">{{formatFloat .Summary.P95MemoryMB}} MB</span>
</div>
<div class="info-item">
<span class="label">Átl. CPU</span>
<span class="value">{{formatFloat .Summary.AvgCPU}}%</span>
</div>
{{end}}
</div>
</section>
<!-- Memory trend chart -->
<section class="card">
<h2>Memória trend</h2>
<div class="chart-container">
<canvas id="memoryChart"></canvas>
</div>
<script>
(function() {
var chartData = {{json .ChartData}};
if (!chartData || !chartData.labels || chartData.labels.length === 0) {
document.getElementById('memoryChart').parentElement.innerHTML = '<p class="text-muted">Nincs elegendő adat a grafikonhoz.</p>';
return;
}
var ctx = document.getElementById('memoryChart').getContext('2d');
var datasets = [
{
label: 'Átl. memória (MB)',
data: chartData.avg_memory,
borderColor: '#60a5fa',
backgroundColor: 'rgba(96,165,250,0.1)',
fill: true,
tension: 0.3,
pointRadius: 2
},
{
label: 'Csúcs memória (MB)',
data: chartData.peak_memory,
borderColor: '#f87171',
backgroundColor: 'transparent',
fill: false,
tension: 0.3,
pointRadius: 2,
borderDash: [4, 2]
}
];
if (chartData.catalog_limit > 0) {
datasets.push({
label: 'Katalógus limit',
data: chartData.labels.map(function() { return chartData.catalog_limit; }),
borderColor: '#4ade80',
backgroundColor: 'transparent',
fill: false,
pointRadius: 0,
borderWidth: 1,
borderDash: [6, 4]
});
}
new Chart(ctx, {
type: 'line',
data: { labels: chartData.labels, datasets: datasets },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { labels: { color: '#94a3b8', font: { size: 11 } } }
},
scales: {
x: { ticks: { color: '#64748b', font: { size: 10 }, maxTicksLimit: 10 }, grid: { color: 'rgba(100,116,139,0.15)' } },
y: { ticks: { color: '#64748b', font: { size: 10 } }, grid: { color: 'rgba(100,116,139,0.15)' }, title: { display: true, text: 'MB', color: '#64748b' } }
}
}
});
})();
</script>
</section>
<!-- Customer breakdown -->
{{if .Customers}}
<section class="card">
<h2>Ügyfél bontás</h2>
<table class="data-table">
<thead>
<tr>
<th>Ügyfél</th>
<th>Átl. memória</th>
<th>Csúcs memória</th>
<th>Átl. CPU</th>
<th>Hibák összesen</th>
<th>Utolsó riport</th>
</tr>
</thead>
<tbody>
{{range .Customers}}
<tr>
<td><a href="/customers/{{.CustomerID}}">{{.CustomerID}}</a></td>
<td>{{formatFloat .AvgMemoryMB}} MB</td>
<td>{{formatFloat .PeakMemoryMB}} MB</td>
<td>{{formatFloat .AvgCPU}}%</td>
<td>{{if gt .TotalErrors 0}}<span class="badge badge-error">{{.TotalErrors}}</span>{{else}}0{{end}}</td>
<td>{{timeAgo .LastReport}}</td>
</tr>
{{end}}
</tbody>
</table>
</section>
{{end}}
<!-- Known issues -->
{{if .Issues}}
<section class="card">
<h2>Ismert hibák</h2>
<table class="data-table">
<thead>
<tr>
<th>Súlyosság</th>
<th>Üzenet</th>
<th>Előfordulások</th>
<th>Érintett ügyfelek</th>
<th>Első észlelés</th>
<th>Utolsó észlelés</th>
</tr>
</thead>
<tbody>
{{range .Issues}}
<tr>
<td>
{{if eq .Severity "error"}}<span class="badge badge-error">error</span>
{{else}}<span class="badge badge-warn">warn</span>{{end}}
</td>
<td style="font-family: var(--font-mono); font-size: 0.8rem; max-width: 40ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="{{.Message}}">{{.Message}}</td>
<td>{{.OccurrenceCount}}</td>
<td>{{len .AffectedCustomers}}</td>
<td>{{timeAgo .FirstSeen}}</td>
<td>{{timeAgo .LastSeen}}</td>
</tr>
{{end}}
</tbody>
</table>
</section>
{{end}}
<footer style="margin-top: 2rem; color: var(--text-muted); font-size: 0.8rem; text-align: center;">
Felhom Hub <span style="font-family: var(--font-mono)">v{{hubVersion}}</span>
</footer>
</div>
</body>
</html>