Files
felhom.eu/hub/internal/web/templates/customer.html
T
admin 6bcbaa1574 hub v0.3.2: Show Hub version in page footers
Add hubVersion template function via closure in web.New(). Version is
passed from main.go (set via ldflags at build time) and displayed in
the footer of all 6 page templates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 19:39:52 +01:00

345 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Customer.CustomerName}} — Felhom Hub</title>
<link rel="stylesheet" href="/style.css">
<meta http-equiv="refresh" content="60">
</head>
<body>
<div class="container">
<header>
<nav class="nav-links" style="margin-bottom: 0.5rem;">
<a href="/" class="nav-link">Dashboard</a>
<a href="/configs" class="nav-link">Customers</a>
</nav>
<a href="/" class="back-link">&larr; Back to Dashboard</a>
<h1>
<span class="status-dot" style="color: {{statusColor .OverallStatus}}">{{statusIcon .OverallStatus}}</span>
{{.Customer.CustomerName}}
</h1>
<p class="subtitle">Last report: {{timeAgo .Customer.ReceivedAt}} &middot; Controller v{{.Customer.ControllerVersion}}</p>
</header>
<!-- System Info -->
<section class="card">
<h2>System</h2>
<div class="info-grid">
{{with .Report.system}}
<div class="info-item">
<span class="label">Hostname</span>
<span class="value">{{index . "hostname"}}</span>
</div>
<div class="info-item">
<span class="label">OS</span>
<span class="value">{{index . "os"}}</span>
</div>
<div class="info-item">
<span class="label">Kernel</span>
<span class="value">{{index . "kernel"}}</span>
</div>
<div class="info-item">
<span class="label">CPU</span>
<span class="value">{{index . "cpu_model"}} ({{index . "cpu_cores"}} cores)</span>
</div>
{{end}}
</div>
<div class="metrics-grid">
<div class="metric">
<span class="metric-label">CPU</span>
<span class="metric-value">{{formatFloat .Customer.CPUPercent}}%</span>
<div class="bar"><div class="bar-fill" style="width: {{formatFloat .Customer.CPUPercent}}%"></div></div>
</div>
<div class="metric">
<span class="metric-label">Memory</span>
<span class="metric-value">{{formatFloat .Customer.MemoryPercent}}%</span>
<div class="bar"><div class="bar-fill" style="width: {{formatFloat .Customer.MemoryPercent}}%"></div></div>
</div>
</div>
</section>
<!-- Storage -->
<section class="card">
<h2>Storage</h2>
{{with .Report.storage}}
<div class="metrics-grid">
{{range .}}
<div class="metric">
<span class="metric-label">{{with index . "label"}}{{.}}{{else}}{{index . "mount"}}{{end}}</span>
<span class="metric-value">{{printf "%.0f" (index . "percent")}}%</span>
<div class="bar"><div class="bar-fill" style="width: {{printf "%.0f" (index . "percent")}}%"></div></div>
<span class="metric-detail">{{printf "%.1f" (index . "used_gb")}} / {{printf "%.1f" (index . "total_gb")}} GB</span>
</div>
{{end}}
</div>
{{end}}
</section>
<!-- Containers -->
<section class="card">
<h2>Containers ({{.Customer.ContainerRunning}}/{{.Customer.ContainerTotal}})</h2>
{{with .Report.containers}}
{{$list := index . "list"}}
{{if $list}}
<table class="container-table">
<thead>
<tr>
<th>Name</th>
<th>State</th>
<th>CPU</th>
<th>Memory</th>
</tr>
</thead>
<tbody>
{{range $list}}
<tr>
<td>{{index . "name"}}</td>
<td><span class="container-state container-state-{{index . "state"}}">{{index . "state"}}</span></td>
<td>{{printf "%.1f" (index . "cpu_percent")}}%</td>
<td>{{printf "%.0f" (index . "memory_mb")}} MB</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
{{end}}
</section>
<!-- Backup -->
<section class="card">
<h2>Backup</h2>
{{with .Report.backup}}
<div class="info-grid">
<div class="info-item">
<span class="label">Enabled</span>
<span class="value">{{if index . "enabled"}}Yes{{else}}No{{end}}</span>
</div>
<div class="info-item">
<span class="label">Snapshots</span>
<span class="value">{{index . "snapshot_count"}}</span>
</div>
<div class="info-item">
<span class="label">Repo Size</span>
<span class="value">{{index . "repo_size_mb"}} MB</span>
</div>
<div class="info-item">
<span class="label">Integrity</span>
<span class="value">{{if index . "integrity_ok"}}OK{{else}}Unknown{{end}}</span>
</div>
</div>
{{end}}
</section>
<!-- Infra Backup (Disaster Recovery) -->
<section class="card">
<h2>Infra Backup</h2>
{{if .InfraBackup}}
<div class="info-grid">
<div class="info-item">
<span class="label">Last Updated</span>
<span class="value">{{.InfraBackupAge}}</span>
</div>
<div class="info-item">
<span class="label">Deployed Stacks</span>
<span class="value">{{.InfraBackup.StackCount}}</span>
</div>
<div class="info-item">
<span class="label">Disks</span>
<span class="value">{{.InfraBackup.DiskCount}}</span>
</div>
</div>
{{else}}
<p style="color: #facc15">No infra backup received yet</p>
{{end}}
</section>
<!-- Health -->
<section class="card">
<h2>Health</h2>
{{if eq .OverallStatus "disabled"}}
<p class="health-status health-status-disabled">Reporting has been disabled on this node</p>
<p class="hint">Enable it in the controller's <code>controller.yaml</code>: <code>hub.enabled: true</code></p>
{{else}}
{{with .Report.health}}
<p class="health-status health-status-{{index . "status"}}">
Status: {{index . "status"}}
</p>
{{$issues := index . "issues"}}
{{if $issues}}
<h3>Issues</h3>
<ul class="issue-list">
{{range $issues}}
<li class="issue">{{.}}</li>
{{end}}
</ul>
{{end}}
{{$warnings := index . "warnings"}}
{{if $warnings}}
<h3>Warnings</h3>
<ul class="warning-list">
{{range $warnings}}
<li class="warning">{{.}}</li>
{{end}}
</ul>
{{end}}
{{end}}
{{end}}
</section>
<!-- Controller Update -->
<section class="card">
<h2>Controller Update</h2>
<div class="info-grid">
<div class="info-item">
<span class="label">Current version</span>
<span class="value">v{{.Customer.ControllerVersion}}</span>
</div>
{{if .LatestVersion}}
<div class="info-item">
<span class="label">Latest version</span>
<span class="value">
v{{.LatestVersion}}
{{if .UpdateAvailable}}
<span style="color: #4ade80; margin-left: 0.3em;">● update available</span>
{{else}}
<span style="color: #94a3b8; margin-left: 0.3em;">— up to date</span>
{{end}}
</span>
</div>
{{end}}
{{if .ControllerURL}}
<div class="info-item">
<span class="label">Controller URL</span>
<span class="value"><a href="{{.ControllerURL}}" target="_blank" style="color: #60a5fa;">{{.ControllerURL}}</a></span>
</div>
{{end}}
</div>
{{if and .ControllerURL .UpdateAvailable}}
<div style="margin-top: 0.75em;">
<button class="btn" id="btn-trigger-update" onclick="triggerControllerUpdate('{{.Customer.CustomerID}}')">
Trigger Update
</button>
<span id="update-msg" style="margin-left: 0.5em; display: none;"></span>
</div>
{{else if and .ControllerURL (not .LatestVersion)}}
<div style="margin-top: 0.75em;">
<button class="btn" id="btn-trigger-update" onclick="triggerControllerUpdate('{{.Customer.CustomerID}}')">
Trigger Update
</button>
<span id="update-msg" style="margin-left: 0.5em; display: none;"></span>
<p style="color: #94a3b8; font-size: 0.85em; margin-top: 0.3em;">Registry check not configured — cannot verify if update is available</p>
</div>
{{end}}
</section>
<script>
function triggerControllerUpdate(customerID) {
if (!confirm('Trigger self-update on this controller?\n\nThe controller will be briefly unavailable during restart.')) return;
var btn = document.getElementById('btn-trigger-update');
var msg = document.getElementById('update-msg');
btn.disabled = true;
btn.textContent = 'Triggering...';
msg.style.display = 'none';
fetch('/customers/' + customerID + '/trigger-update', {method: 'POST'})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.ok) {
msg.textContent = 'Update triggered — controller restarting';
msg.style.display = 'inline';
msg.style.color = '#4ade80';
} else {
msg.textContent = data.error || 'Failed';
msg.style.display = 'inline';
msg.style.color = '#f87171';
btn.disabled = false;
btn.textContent = 'Trigger Update';
}
})
.catch(function() {
msg.textContent = 'Connection error';
msg.style.display = 'inline';
msg.style.color = '#f87171';
btn.disabled = false;
btn.textContent = 'Trigger Update';
});
}
</script>
<!-- Notifications -->
<section class="card">
<h2>Notifications</h2>
<div class="info-grid">
<div class="info-item">
<span class="label">Email</span>
<span class="value">{{if .NotifPrefs}}{{if .NotifPrefs.Email}}{{.NotifPrefs.Email}}{{else}}Not set{{end}}{{else}}Not configured{{end}}</span>
</div>
{{if .NotifPrefs}}
<div class="info-item">
<span class="label">Events</span>
<span class="value">{{if .NotifPrefs.EnabledEvents}}{{joinStrings .NotifPrefs.EnabledEvents ", "}}{{else}}None{{end}}</span>
</div>
{{end}}
</div>
{{if .RecentNotifications}}
<h3>Recent (last 10)</h3>
<table class="history-table">
<thead>
<tr>
<th>Time</th>
<th>Event</th>
<th>Status</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{{range .RecentNotifications}}
<tr>
<td>{{.CreatedAt.Format "Jan 02 15:04"}}</td>
<td>{{.EventType}}</td>
<td><span class="status-badge status-badge-{{.Status}}">{{.Status}}</span></td>
<td>{{.Message}}</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
</section>
<!-- Report History (last 24h) -->
{{if .History}}
<section class="card">
<h2>Report History (last 24h)</h2>
<details>
<summary>{{len .History}} reports</summary>
<table class="history-table">
<thead>
<tr>
<th>Time</th>
<th>Status</th>
<th>CPU</th>
<th>Memory</th>
</tr>
</thead>
<tbody>
{{range .History}}
<tr>
<td>{{.ReceivedAt.Format "Jan 02 15:04"}}</td>
<td><span class="status-badge status-badge-{{.HealthStatus}}">{{.HealthStatus}}</span></td>
<td>{{formatFloat .CPUPercent}}%</td>
<td>{{formatFloat .MemoryPercent}}%</td>
</tr>
{{end}}
</tbody>
</table>
</details>
</section>
{{end}}
<footer>
<p>Auto-refreshes every 60 seconds &middot; <a href="/">Felhom Hub</a> {{hubVersion}}</p>
</footer>
</div>
</body>
</html>