c136308aad
- style.css: slate tokens -> the canonical navy block + @font-face (self-hosted, latin-ext for Hungarian customer names); single 2px radius; hairline table rows (--line-soft dividers, density kept); status classes re-expressed per the design-system addendum: .status-badge = outline tag + CSS ::before dot keyed by the raw status the templates emit (ok=blue, warn/blocked/stale=amber, down/fail=red, pending/disabled=quiet neutral); .status-dot = class-based 8px dot; severity badges stay filled amber/red (exceptions stay loud); config badges = filled informational chips in v2 colors; row tint only for warn/down (warn-dim/crit-dim). - Two-tone brand heading on all 8 'Felhom Hub' pages (<h1>Felhom <span>Hub</span></h1>, span = blue-bright). - app_detail chart retinted: avg #2EA8F5 (primary data), peak #8E7CE8 (secondary DATA series — not status red), catalog-limit line #E0A93E (threshold marker); legend/tick/grid -> v2 literals. - customer_unified: JS status-message colors (success -> blue-bright, error -> crit), all inline slate hexes -> tokens. - Login page inline HTML retinted (#0088cc -> #0083D8). - Grep gate: every slate hex (#0f172a #1e293b #334155 #60a5fa #4ade80 #facc15 #f87171 #94a3b8 #64748b #475569 #e2e8f0) at ZERO across hub/internal/web (non-test); statusIcon 0; inline statusColor style 0. go build/vet/test green; api/ + store/ untouched.
154 lines
9.1 KiB
HTML
154 lines
9.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Configuration — Felhom Hub</title>
|
|
<link rel="stylesheet" href="/style.css">
|
|
</head>
|
|
<body>
|
|
{{template "icon_sprite"}}
|
|
<div class="container">
|
|
<header>
|
|
<h1>Felhom <span>Hub</span></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">Apps</a>
|
|
<a href="/hosts" class="nav-link">Hosts</a>
|
|
<a href="/configuration" class="nav-link active">Configuration</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<h2 style="margin-bottom: 1rem;">Configuration</h2>
|
|
|
|
{{if eq .Flash "assets_refreshed"}}
|
|
<div class="flash flash-success">Assets refreshed successfully from image seed.</div>
|
|
{{end}}
|
|
{{if eq .Flash "assets_error"}}
|
|
<div class="flash flash-error">Asset refresh failed. Check server logs for details.</div>
|
|
{{end}}
|
|
{{if eq .Flash "assets_not_configured"}}
|
|
<div class="flash flash-error">Asset manager is not configured.</div>
|
|
{{end}}
|
|
{{if eq .Flash "floor_set"}}
|
|
<div class="flash flash-success">Controller-version floor saved.</div>
|
|
{{end}}
|
|
{{if eq .Flash "floor_invalid"}}
|
|
<div class="flash flash-error">Invalid version — use X.Y.Z (or blank to clear).</div>
|
|
{{end}}
|
|
{{if eq .Flash "artifacts_set"}}
|
|
<div class="flash flash-success">Artifact manifest saved.</div>
|
|
{{end}}
|
|
{{if eq .Flash "artifact_ver_invalid"}}
|
|
<div class="flash flash-error">Invalid artifact version — use X.Y.Z (or blank to clear).</div>
|
|
{{end}}
|
|
{{if eq .Flash "artifact_sha_invalid"}}
|
|
<div class="flash flash-error">Couldn't set the checksum — the Gitea sha lookup failed (version missing / Gitea unreachable) or the manually-entered sha is invalid. Manifest unchanged.</div>
|
|
{{end}}
|
|
|
|
<!-- Phase 2 managed updates: global controller-version floor (moved from the Customers page —
|
|
it is a global setting). -->
|
|
<section class="card">
|
|
<h3 style="margin-top: 0;">Managed updates — global floor</h3>
|
|
<p class="text-muted" style="margin: 0 0 0.75rem; font-size: 0.85em;">
|
|
The minimum controller version every box auto-updates to (unless a per-customer override is set).
|
|
Boxes below the floor update on their next report — no customer action. Blank = no global floor.
|
|
</p>
|
|
<form method="POST" action="/configuration/global-floor" style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
|
|
{{.CSRFField}}
|
|
<input type="text" name="min_controller_version" value="{{.GlobalFloor}}" placeholder="e.g. 0.86.0 (blank = none)" style="padding: 0.3em 0.5em; width: 14em;">
|
|
<button class="btn btn-sm" type="submit">Save global floor</button>
|
|
<span style="font-size: 0.85em; color: #cbd5e1;">Current: {{if .GlobalFloor}}<code>v{{.GlobalFloor}}</code>{{else}}<span class="text-muted">unset</span>{{end}}</span>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- BUNDLE slice: Day-0 artifact manifest (agent binary + golden archive). The hub is the
|
|
checksum TRUST ROOT — the host-bootstrap script verifies Gitea-fetched artifacts against
|
|
these sha256s before installing them. Record the version + sha256 printed by
|
|
publish-agent.sh / build-golden.sh. -->
|
|
<section class="card">
|
|
<h3 style="margin-top: 0;">Day-0 artifacts — agent & golden</h3>
|
|
<p class="text-muted" style="margin: 0 0 0.75rem; font-size: 0.85em;">
|
|
The current agent binary + golden archive the host-bootstrap script fetches from Gitea and
|
|
verifies (sha256) before installing. The hub vouches for these checksums (a different trust
|
|
root than Gitea). Pick a version — the sha256 is read from Gitea automatically (no manual
|
|
copy). Choose <em>— none —</em> to clear an artifact.
|
|
</p>
|
|
<form method="POST" action="/configuration/artifacts" style="display: grid; grid-template-columns: auto 12em 1fr; gap: 0.5rem; align-items: center; max-width: 56em;">
|
|
{{.CSRFField}}
|
|
<label style="font-size: 0.9em; color: #cbd5e1;">Agent</label>
|
|
{{if .AgentChoices}}
|
|
<select name="agent_version" id="agent_version" onchange="syncArtifactSha('agent')" style="padding: 0.3em 0.5em;">
|
|
<option value="" data-sha="">— none —</option>
|
|
{{range .AgentChoices}}
|
|
<option value="{{.Version}}" data-sha="{{.SHA256}}" {{if eq .Version $.Artifacts.AgentVersion}}selected{{end}}>{{.Version}}</option>
|
|
{{end}}
|
|
</select>
|
|
{{else}}
|
|
<input type="text" name="agent_version" value="{{.Artifacts.AgentVersion}}" placeholder="0.52.0" style="padding: 0.3em 0.5em;">
|
|
{{end}}
|
|
<input type="text" name="agent_sha256" id="agent_sha256" value="{{.Artifacts.AgentSHA256}}" {{if .AgentChoices}}readonly{{end}} placeholder="64-hex sha256 (blank = none)" style="padding: 0.3em 0.5em; font-family: monospace; {{if .AgentChoices}}opacity: 0.7;{{end}}">
|
|
<label style="font-size: 0.9em; color: #cbd5e1;">Golden</label>
|
|
{{if .GoldenChoices}}
|
|
<select name="golden_version" id="golden_version" onchange="syncArtifactSha('golden')" style="padding: 0.3em 0.5em;">
|
|
<option value="" data-sha="">— none —</option>
|
|
{{range .GoldenChoices}}
|
|
<option value="{{.Version}}" data-sha="{{.SHA256}}" {{if eq .Version $.Artifacts.GoldenVersion}}selected{{end}}>{{.Version}}</option>
|
|
{{end}}
|
|
</select>
|
|
{{else}}
|
|
<input type="text" name="golden_version" value="{{.Artifacts.GoldenVersion}}" placeholder="0.85.1" style="padding: 0.3em 0.5em;">
|
|
{{end}}
|
|
<input type="text" name="golden_sha256" id="golden_sha256" value="{{.Artifacts.GoldenSHA256}}" {{if .GoldenChoices}}readonly{{end}} placeholder="64-hex sha256 (blank = none)" style="padding: 0.3em 0.5em; font-family: monospace; {{if .GoldenChoices}}opacity: 0.7;{{end}}">
|
|
<span></span><span></span>
|
|
<button class="btn btn-sm" type="submit" style="justify-self: start;">Save artifact manifest</button>
|
|
</form>
|
|
<script>
|
|
// When a version is picked, mirror that option's Gitea-resolved sha256 into the read-only
|
|
// display field. The hub re-derives the sha authoritatively on save regardless of this value.
|
|
function syncArtifactSha(kind) {
|
|
var sel = document.getElementById(kind + '_version');
|
|
var sha = document.getElementById(kind + '_sha256');
|
|
if (!sel || !sha) return;
|
|
var opt = sel.options[sel.selectedIndex];
|
|
sha.value = (opt && opt.getAttribute('data-sha')) || '';
|
|
}
|
|
</script>
|
|
</section>
|
|
|
|
<!-- Assets section -->
|
|
<section class="card">
|
|
<h3 style="margin-top: 0;">Assets</h3>
|
|
<p class="text-muted" style="margin-bottom: 1rem;">
|
|
App logos and screenshots served to controllers. Assets are seeded from the Docker image
|
|
and synced to controllers daily via the asset manifest API.
|
|
</p>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<span class="label">Files in manifest</span>
|
|
<span class="value">{{.AssetCount}}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">Manifest generated</span>
|
|
<span class="value" style="font-family: var(--font-mono); font-size: 0.85em;">{{if .AssetLastSync}}{{.AssetLastSync}}{{else}}—{{end}}</span>
|
|
</div>
|
|
</div>
|
|
<form method="POST" action="/configuration" style="margin-top: 1rem;">
|
|
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
|
<input type="hidden" name="action" value="refresh_assets">
|
|
<button type="submit" class="btn" onclick="this.disabled=true;this.textContent='Refreshing…';this.form.submit();">Refresh Assets from Image</button>
|
|
</form>
|
|
<p class="text-muted" style="margin-top: 0.75rem; font-size: 0.8rem;">
|
|
Re-reads the baked-in asset seed directory and updates changed files.
|
|
Controllers will pick up changes on their next daily sync or manual trigger.
|
|
</p>
|
|
</section>
|
|
|
|
<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)">{{hubVersion}}</span>
|
|
</footer>
|
|
</div>
|
|
</body>
|
|
</html>
|