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>
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
<!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">← 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>
|
||||
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Alkalmazások — Felhom Hub</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</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>
|
||||
|
||||
<h2 style="margin-bottom: 1rem;">Alkalmazás telemetria</h2>
|
||||
|
||||
<!-- Period selector -->
|
||||
<div class="period-selector">
|
||||
<a href="?period=24h{{if .Sort}}&sort={{.Sort}}&order={{.Order}}{{end}}" class="period-btn{{if eq .Period "24h"}} active{{end}}">24 óra</a>
|
||||
<a href="?period=7d{{if .Sort}}&sort={{.Sort}}&order={{.Order}}{{end}}" class="period-btn{{if or (eq .Period "7d") (eq .Period "")}} active{{end}}">7 nap</a>
|
||||
<a href="?period=30d{{if .Sort}}&sort={{.Sort}}&order={{.Order}}{{end}}" class="period-btn{{if eq .Period "30d"}} active{{end}}">30 nap</a>
|
||||
</div>
|
||||
|
||||
<!-- Summary cards -->
|
||||
<div class="summary-cards">
|
||||
<div class="summary-card">
|
||||
<div class="card-number">{{.TotalApps}}</div>
|
||||
<div class="card-label">Alkalmazás összesen</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="card-number">{{.TotalDeployments}}</div>
|
||||
<div class="card-label">Telepítések száma</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="card-number" {{if gt .AppsWithErrors 0}}style="color: var(--red)"{{end}}>{{.AppsWithErrors}}</div>
|
||||
<div class="card-label">Hibás alkalmazások</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App table -->
|
||||
{{if .Apps}}
|
||||
<section class="card" style="padding: 0; overflow: hidden;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a href="?period={{.Period}}&sort=name&order={{if eq .Sort "name"}}{{if eq .Order "asc"}}desc{{else}}asc{{end}}{{else}}asc{{end}}">Alkalmazás</a></th>
|
||||
<th><a href="?period={{.Period}}&sort=deployments&order={{if eq .Sort "deployments"}}{{if eq .Order "asc"}}desc{{else}}asc{{end}}{{else}}desc{{end}}">Telepítések</a></th>
|
||||
<th><a href="?period={{.Period}}&sort=memory&order={{if eq .Sort "memory"}}{{if eq .Order "asc"}}desc{{else}}asc{{end}}{{else}}desc{{end}}">Átl. memória</a></th>
|
||||
<th>P95 memória</th>
|
||||
<th>Katalógus becslés</th>
|
||||
<th>Katalógus limit</th>
|
||||
<th>Pontosság</th>
|
||||
<th><a href="?period={{.Period}}&sort=errors&order={{if eq .Sort "errors"}}{{if eq .Order "asc"}}desc{{else}}asc{{end}}{{else}}desc{{end}}">Hibák</a></th>
|
||||
<th>Figyelmeztetések</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Apps}}
|
||||
<tr>
|
||||
<td><a href="/apps/{{.AppName}}">{{if .DisplayName}}{{.DisplayName}}{{else}}{{.AppName}}{{end}}</a></td>
|
||||
<td>{{.DeploymentCount}}</td>
|
||||
<td>{{formatFloat .AvgMemoryMB}} MB</td>
|
||||
<td>{{formatFloat .P95MemoryMB}} MB</td>
|
||||
<td>{{if .CatalogEstimate}}{{.CatalogEstimate}}{{else}}—{{end}}</td>
|
||||
<td>{{if .CatalogLimit}}{{.CatalogLimit}}{{else}}—{{end}}</td>
|
||||
<td>
|
||||
{{$ac := accuracyClass .P95MemoryMB .CatalogLimit}}
|
||||
{{if eq $ac "ok"}}<span class="accuracy-dot accuracy-ok" title="P95 rendben"></span>
|
||||
{{else if eq $ac "warn"}}<span class="accuracy-dot accuracy-warn" title="P95 > limit 50%"></span>
|
||||
{{else if eq $ac "danger"}}<span class="accuracy-dot accuracy-danger" title="P95 meghaladja a limitet"></span>
|
||||
{{else}}—{{end}}
|
||||
</td>
|
||||
<td>{{if gt .TotalErrors 0}}<span class="badge badge-error">{{.TotalErrors}}</span>{{else}}0{{end}}</td>
|
||||
<td>{{if gt .TotalWarnings 0}}<span class="badge badge-warn">{{.TotalWarnings}}</span>{{else}}0{{end}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<p>Nincs telemetria adat a kiválasztott időszakra.</p>
|
||||
<p class="hint">Az alkalmazás telemetria a következő riport beérkezése után jelenik meg (v0.28.0+ vezérlő szükséges).</p>
|
||||
</div>
|
||||
{{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>
|
||||
@@ -13,6 +13,7 @@
|
||||
<nav class="nav-links">
|
||||
<a href="/" class="nav-link">Dashboard</a>
|
||||
<a href="/configs" class="nav-link active">Customers</a>
|
||||
<a href="/apps" class="nav-link">Alkalmazások</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<nav class="nav-links">
|
||||
<a href="/" class="nav-link">Dashboard</a>
|
||||
<a href="/configs" class="nav-link active">Customers</a>
|
||||
<a href="/apps" class="nav-link">Alkalmazások</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<nav class="nav-links">
|
||||
<a href="/" class="nav-link">Dashboard</a>
|
||||
<a href="/configs" class="nav-link active">Customers</a>
|
||||
<a href="/apps" class="nav-link">Alkalmazások</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<nav class="nav-links" style="margin-bottom: 0.5rem;">
|
||||
<a href="/" class="nav-link">Dashboard</a>
|
||||
<a href="/configs" class="nav-link">Customers</a>
|
||||
<a href="/apps" class="nav-link">Alkalmazások</a>
|
||||
</nav>
|
||||
<a href="/" class="back-link">← Back to Dashboard</a>
|
||||
<h1>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<nav class="nav-links" style="margin-bottom: 0.5rem;">
|
||||
<a href="/" class="nav-link">Dashboard</a>
|
||||
<a href="/configs" class="nav-link active">Customers</a>
|
||||
<a href="/apps" class="nav-link">Alkalmazások</a>
|
||||
</nav>
|
||||
<a href="/configs" class="back-link">← All Customers</a>
|
||||
<h1>
|
||||
@@ -470,6 +471,39 @@
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- Alkalmazás telemetria -->
|
||||
{{if .HasAppTelemetry}}
|
||||
<section class="card">
|
||||
<h2>Alkalmazás telemetria <span class="text-muted" style="font-size: 0.85em; font-weight: normal;">(utolsó 7 nap)</span></h2>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alkalmazás</th>
|
||||
<th>Memória (jelenlegi)</th>
|
||||
<th>Memória (átlag 7d)</th>
|
||||
<th>Memória (csúcs 7d)</th>
|
||||
<th>Katalógus limit</th>
|
||||
<th>Hibák</th>
|
||||
<th>Figyelmeztetések</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .AppTelemetry}}
|
||||
<tr>
|
||||
<td><a href="/apps/{{.AppName}}">{{if .DisplayName}}{{.DisplayName}}{{else}}{{.AppName}}{{end}}</a></td>
|
||||
<td class="{{memoryColor .MemoryCurrentMB .CatalogLimit}}">{{formatFloat .MemoryCurrentMB}} MB</td>
|
||||
<td>{{formatFloat .MemoryAvgMB}} MB</td>
|
||||
<td>{{formatFloat .MemoryPeakMB}} MB</td>
|
||||
<td>{{if .CatalogLimit}}{{.CatalogLimit}}{{else}}—{{end}}</td>
|
||||
<td>{{if gt .LogErrors 0}}<span class="badge badge-error">{{.LogErrors}}</span>{{else}}0{{end}}</td>
|
||||
<td>{{if gt .LogWarnings 0}}<span class="badge badge-warn">{{.LogWarnings}}</span>{{else}}0{{end}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<!-- Notifications -->
|
||||
<section class="card">
|
||||
<h2>Notifications</h2>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<nav class="nav-links">
|
||||
<a href="/" class="nav-link active">Dashboard</a>
|
||||
<a href="/configs" class="nav-link">Customers</a>
|
||||
<a href="/apps" class="nav-link">Alkalmazások</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -618,6 +618,137 @@ code {
|
||||
.diff-hub_only td { color: #3b82f6; }
|
||||
.diff-controller_only td { color: #fb923c; }
|
||||
|
||||
/* Badge styles */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.badge-error {
|
||||
background: rgba(248, 113, 113, 0.2);
|
||||
color: var(--red);
|
||||
}
|
||||
.badge-warn {
|
||||
background: rgba(250, 204, 21, 0.2);
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
/* Summary cards row */
|
||||
.summary-cards {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.summary-card {
|
||||
flex: 1;
|
||||
min-width: 160px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
.summary-card .card-number {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-mono);
|
||||
line-height: 1.1;
|
||||
}
|
||||
.summary-card .card-label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Chart container */
|
||||
.chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* Period selector button group */
|
||||
.period-selector {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.period-btn {
|
||||
padding: 0.3rem 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.25rem;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.period-btn:hover, .period-btn.active {
|
||||
background: var(--accent);
|
||||
color: var(--bg-primary);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Accuracy dot */
|
||||
.accuracy-dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
.accuracy-ok { background: var(--green); }
|
||||
.accuracy-warn { background: var(--yellow); }
|
||||
.accuracy-danger { background: var(--red); }
|
||||
|
||||
/* Memory color classes */
|
||||
.mem-ok { color: var(--green); }
|
||||
.mem-warn { color: var(--yellow); }
|
||||
.mem-danger { color: var(--red); }
|
||||
|
||||
/* Data table (apps page) */
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.data-table th {
|
||||
text-align: left;
|
||||
padding: 0.6rem 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
border-bottom: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.data-table td {
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-bottom: 1px solid rgba(100,116,139,0.15);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.data-table td a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-family: var(--font-sans, sans-serif);
|
||||
}
|
||||
.data-table td a:hover { text-decoration: underline; }
|
||||
.data-table th a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
.data-table th a:hover { color: var(--text-primary); }
|
||||
.data-table tr:hover td { background: rgba(96,165,250,0.04); }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.container { padding: 1rem; }
|
||||
|
||||
Reference in New Issue
Block a user