Files
nilsandClaude Sonnet 5.5 b1fa7b1396 Add token expiry, edit notice, log allowlist, local-zone test; merge PLAN.md into SPECS.md
get_logs needs a unit from an allowlist (router-*, kea, unbound, networkd,
nftables, ddclient, acme, sshd, kernel). API tokens are valid for 1, 7, 30
(default) or 90 days, or without limit. The overview and the Changes page
say which token made the unapplied changes.

options.json (router://options) no longer holds the options' source paths:
they differ between builds of a release, so a router rebuilding itself
offline had to build the file and failed (found by the update test).

Web UI: panels flow into columns on wide screens and the width is no longer
capped; on phones the navigation is a menu button; the Advanced page uses
panels; VLAN dropdowns show their chosen names; the duplicate WireGuard
heading is gone.

basic: a subtest for wan -> local rules.

SPECS.md is now the only document (decisions, how each part works, and what
is not done yet); PLAN.md is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
2026-11-01 19:06:27 +01:00

66 lines
3.0 KiB
HTML

{{define "content"}}
<div class="cols">
{{$d := .Data}}
<section>
<h2>Installed</h2>
<dl>
<dt>Version</dt><dd>{{or $d.Installed.Version "unknown"}}{{if $d.Installed.Rev}} <span class="muted">{{$d.Installed.Rev}}</span>{{end}}</dd>
<dt>Settings schema</dt><dd>{{$d.Installed.Schema}}</dd>
</dl>
</section>
{{if $d.Status.Running}}
<section class="busy"><h2>Updating…</h2><p>Verifying, importing and building the new version. This page refreshes on its own;
the web UI restarts when the new version is switched on.</p></section>
{{else if $d.Pending}}
<section class="pending"><h2>Waiting for confirmation</h2>
<p>A new configuration is active and not confirmed yet. Confirm it on the <a href="/">Overview</a>, or the router
goes back to the previous version.</p></section>
{{end}}
<section>
<h2>Install a bundle</h2>
<p>A <code>.bundle</code> file made with <code>nix run .#release</code>. It is installed only if it is signed with
this router's update key. The settings are kept (and migrated if the new version needs it); the new version
must be confirmed afterwards, or the router goes back.</p>
<form method="post" action="/update/upload?csrf={{.CSRF}}" enctype="multipart/form-data">
<p><label><input type="checkbox" name="force"> also install a version that is not newer</label></p>
<p><input type="file" name="bundle" required> <button>Upload and install</button></p>
</form>
</section>
<section>
<h2>Update server</h2>
<form method="post" action="/update/url" class="actions">
<input type="hidden" name="csrf" value="{{.CSRF}}">
<label>Address <input name="url" value="{{$d.URL}}" size="40" placeholder="https://updates.example.com/router"></label>
<button class="secondary">Save</button>
</form>
{{if $d.URL}}
<form method="post" action="/update/check" class="actions">
<input type="hidden" name="csrf" value="{{.CSRF}}">
<button class="secondary">Check for updates</button>
</form>
{{end}}
{{if $d.OfferErr}}<p class="error">Could not check: {{$d.OfferErr}}</p>{{end}}
{{with $d.Offer}}
<p>{{if .Newer}}Available: <strong>{{.Version}}</strong>{{if .Rev}} <span class="muted">{{.Rev}}</span>{{end}}.{{else}}Nothing newer: the server has {{.Version}}.{{end}}</p>
<form method="post" action="/update/install">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
{{if not .Newer}}<p><label><input type="checkbox" name="force"> install it anyway</label></p>{{end}}
<button>{{if .Newer}}Install {{.Version}}{{else}}Install{{end}}</button>
</form>
{{end}}
<p class="hint">The server only has to serve static files: the <code>latest</code> file and <code>cache/</code> that
<code>nix run .#release</code> writes. The data is signed, so plain HTTP works too.</p>
</section>
{{if $d.Status.Log}}
<section>
<h2>Last update{{if $d.Status.Result}} — {{if eq $d.Status.Result "success"}}succeeded{{else}}failed ({{$d.Status.Result}}){{end}}{{end}}</h2>
<pre class="log">{{$d.Status.Log}}</pre>
</section>
{{end}}
</div>
{{end}}