feat(ui): /settings page + nav link on every page

Slice 5 of the on-box UI uplevel. Adds a third page at /settings/
served by Caddy from /srv/furtka/www/settings/index.html. Three
groups of content:

  - About this box (read-only): hostname, IP, Furtka version,
    kernel, RAM, Docker, uptime — all consumed from status.json
    via the same 30s refresh loop the landing uses.
  - Appearance: theme follows prefers-color-scheme, language is
    English for v1. Shown read-only.
  - Coming next: linked roadmap chips (Reboot / Shut down / Change
    hostname / Backup / User accounts / Remote access), each
    jumping to the planned section on furtka.org. Implementing any
    of these graduates it in-place.

Nav link to Settings also added to the landing page and /apps so
the three pages share one persistent navigation (Jakob's Law).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Daniel Maksymilian Syrnicki 2026-04-16 12:29:43 +02:00
parent c7ca6bfbb1
commit 4e4dc1001f
2 changed files with 93 additions and 0 deletions

View file

@ -76,6 +76,7 @@ _HTML = """<!DOCTYPE html>
<div class="nav-links">
<a href="/">Home</a>
<a href="/apps" aria-current="page">Apps</a>
<a href="/settings/">Settings</a>
</div>
</nav>

View file

@ -188,6 +188,7 @@ _INDEX_HTML = """\
<div class="nav-links">
<a href="/" aria-current="page">Home</a>
<a href="/apps">Apps</a>
<a href="/settings/">Settings</a>
</div>
</nav>
<header>
@ -719,6 +720,96 @@ details.log-details[open] > summary { color: var(--fg); }
.app-icon svg { width: 36px; height: 36px; }
"""
_SETTINGS_HTML = """\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Settings · Furtka</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<main class="wrap">
<nav class="nav">
<a class="brand" href="/">Furtka</a>
<div class="nav-links">
<a href="/">Home</a>
<a href="/apps">Apps</a>
<a href="/settings/" aria-current="page">Settings</a>
</div>
</nav>
<h1>Settings</h1>
<p class="lede">What this box knows about itself.</p>
<section>
<h2>About this box</h2>
<div class="card">
<dl class="kv">
<dt>Hostname</dt><dd id="set-hostname"></dd>
<dt>IP address</dt><dd id="set-ip"></dd>
<dt>Furtka version</dt><dd id="set-version"></dd>
<dt>Kernel</dt><dd id="set-kernel"></dd>
<dt>RAM</dt><dd id="set-ram"></dd>
<dt>Docker</dt><dd id="set-docker"></dd>
<dt>Uptime</dt><dd id="set-uptime"></dd>
</dl>
</div>
</section>
<section>
<h2>Appearance</h2>
<div class="card">
<dl class="kv">
<dt>Theme</dt><dd>Follows your system setting</dd>
<dt>Language</dt><dd>English</dd>
</dl>
</div>
</section>
<section>
<h2>Coming next</h2>
<div class="coming">
<p class="hint">Controls we're building — follow progress on <a href="https://furtka.org">furtka.org</a>.</p>
<a href="https://furtka.org/#planned">Reboot</a>
<a href="https://furtka.org/#planned">Shut down</a>
<a href="https://furtka.org/#planned">Change hostname</a>
<a href="https://furtka.org/#planned">Backup</a>
<a href="https://furtka.org/#planned">User accounts</a>
<a href="https://furtka.org/#planned">Remote access</a>
</div>
</section>
<footer>
<p>Furtka · <a href="https://furtka.org">furtka.org</a></p>
</footer>
</main>
<script>
async function refresh() {
try {
const r = await fetch('/status.json', { cache: 'no-store' });
if (!r.ok) return;
const s = await r.json();
document.getElementById('set-hostname').textContent = s.hostname || '';
document.getElementById('set-ip').textContent = s.ip_primary || '';
document.getElementById('set-version').textContent = s.furtka_version || '';
document.getElementById('set-kernel').textContent = s.kernel || '';
document.getElementById('set-ram').textContent = s.ram_total || '';
document.getElementById('set-docker').textContent = s.docker_version || '';
document.getElementById('set-uptime').textContent = s.uptime || '';
} catch (e) {
/* next tick will retry */
}
}
refresh();
setInterval(refresh, 15000);
</script>
</body>
</html>
"""
_STATUS_JSON_PLACEHOLDER = """\
{
"hostname": "",
@ -945,6 +1036,7 @@ def _post_install_commands(hostname):
return [
_write_file_cmd("/etc/caddy/Caddyfile", _CADDYFILE),
_write_file_cmd("/srv/furtka/www/index.html", _INDEX_HTML),
_write_file_cmd("/srv/furtka/www/settings/index.html", _SETTINGS_HTML),
_write_file_cmd("/srv/furtka/www/style.css", _STYLE_CSS),
_write_file_cmd("/srv/furtka/www/status.json", _STATUS_JSON_PLACEHOLDER),
_write_file_cmd("/usr/local/bin/furtka-status", _FURTKA_STATUS_SH, mode="755"),