CI / test (push) Successful in 1m49s
/admin/mirror, behind the same admin session as the other admin pages. What is held, what is arriving with its byte count, what failed and why; start a fetch, retry a failed one, remove a version. Not mounted at all without a mirror configured. The right page for a deployment with no mirror is no page, rather than an empty one pointing at nothing. It reloads itself only while something is downloading. A reload at any other moment takes with it whatever is half-typed into the fetch form, and there is nothing to watch when nothing is moving. A failed row offers Retry, not Remove. A fetch that failed left nothing on disk, so the remove could only answer "the mirror holds nothing for this" — a button whose single outcome is a confusing error. Starting the same download again is what somebody looking at that row wants, and the mirror allows it: only a download still running is refused. An unreachable mirror is said outright instead of rendering as an empty table. The two look nearly identical and mean opposite things: one is a mirror waiting to be filled, the other is a machine to go and look at. Dashboard takes NavExtras rather than a second bool. Two adjacent booleans at a call site get swapped silently and are discovered by somebody clicking the wrong link. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
112 lines
5.3 KiB
HTML
112 lines
5.3 KiB
HTML
{{define "admin_mirror"}}<!doctype html>
|
|
<html lang="en"><head>
|
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
{{/* Only while something is downloading. A reload at any other time would
|
|
take with it whatever the operator was typing into the form below. */}}
|
|
{{if .RefreshSec}}<meta http-equiv="refresh" content="{{.RefreshSec}}">{{end}}
|
|
<title>Mirror · llmgw</title><link rel="stylesheet" href="/_static/base.css">
|
|
</head><body><div class="wrap">
|
|
<header>
|
|
<h1>Model mirror</h1>
|
|
<p class="sub">
|
|
{{if .Unreachable}}{{.Addr}} did not answer{{else}}{{.Present}} held ·
|
|
{{.Fetching}} downloading · {{.Failed}} failed · {{.Addr}}{{end}}
|
|
</p>
|
|
<nav><a href="/admin">cluster</a> <a href="/admin/users">accounts</a>
|
|
<a href="/profile">your keys</a></nav>
|
|
</header>
|
|
|
|
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
|
{{if .Message}}<p class="ok">{{.Message}}</p>{{end}}
|
|
|
|
{{if .Unreachable}}
|
|
{{/* Said outright rather than shown as an empty table. A mirror holding
|
|
nothing and a mirror that is not there look the same in a list of zero
|
|
rows, and they call for opposite responses. */}}
|
|
<p class="note">Nothing was read from the mirror, so this page is not a list of
|
|
what it holds — it is not currently reachable at <code>{{.Addr}}</code>. Check
|
|
that <code>llmgw-mirror</code> is running there and that its management port is
|
|
reachable from this machine.</p>
|
|
{{else}}
|
|
|
|
<h2>What the mirror holds</h2>
|
|
{{if .Rows}}
|
|
<table>
|
|
<tr><th>Model</th><th>Version</th><th>File</th><th>State</th><th>Size</th>
|
|
<th>Checksum</th><th></th></tr>
|
|
{{range .Rows}}<tr>
|
|
<td>{{.Model}}</td>
|
|
<td>{{.Version}}</td>
|
|
<td><code>{{.File}}</code></td>
|
|
<td class="state state-{{.State}}">{{.State}}</td>
|
|
<td class="num">
|
|
{{if eq (printf "%s" .State) "fetching"}}
|
|
{{if .HasPercent}}{{bytes .BytesDone}} / {{bytes .BytesTotal}} ({{.Percent}}%)
|
|
{{else}}{{/* No total came from upstream, so no percentage is invented —
|
|
a bar that guesses its own end and then stops moving is worse
|
|
than a number that only goes up. */}}
|
|
{{bytes .BytesDone}} so far{{end}}
|
|
{{else if eq (printf "%s" .State) "failed"}}—
|
|
{{else}}{{bytes .BytesDone}}{{end}}
|
|
</td>
|
|
<td>{{if .SHA256}}<code title="{{.SHA256}}">{{slice .SHA256 0 12}}</code>{{else}}—{{end}}</td>
|
|
<td>
|
|
{{if eq (printf "%s" .State) "fetching"}}
|
|
<span class="note">downloading</span>
|
|
{{else if eq (printf "%s" .State) "failed"}}
|
|
{{/* Retry, not remove. A fetch that failed left nothing on disk, so the
|
|
remove would come back "the mirror holds nothing for this" — a button
|
|
whose only outcome is a confusing error. Starting the same download
|
|
again is what somebody looking at a failed row wants, and the mirror
|
|
allows it: only a download still running is refused. */}}
|
|
<form method="post" action="/admin/mirror/fetch" class="inline">
|
|
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
|
<input type="hidden" name="source" value="{{.Source}}">
|
|
<input type="hidden" name="model" value="{{.Model}}">
|
|
<input type="hidden" name="version" value="{{.Version}}">
|
|
<input type="hidden" name="file" value="{{.File}}">
|
|
<button type="submit" class="link">Retry</button>
|
|
</form>
|
|
{{else}}
|
|
<form method="post" action="/admin/mirror/{{.Model}}/{{.Version}}/remove"
|
|
class="inline" onsubmit="return confirm('Remove {{.Model}}/{{.Version}} and every file in it?')">
|
|
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
|
<button type="submit" class="link danger">Remove</button>
|
|
</form>
|
|
{{end}}
|
|
</td>
|
|
</tr>
|
|
{{if .Err}}<tr><td colspan="7" class="error-row">{{.Err}}</td></tr>{{end}}
|
|
{{if not .Err}}<tr><td colspan="7" class="url-row"><code>{{.URL}}</code></td></tr>{{end}}
|
|
{{end}}
|
|
</table>
|
|
<p class="note">The address under each row is what a catalog entry should say.
|
|
It is rendered here rather than assembled by hand because the mirror and the
|
|
catalog have to spell the same file the same way — two spellings is what makes a
|
|
file that is in use look unreferenced.</p>
|
|
{{else}}<p class="empty">The mirror is reachable and holds nothing yet.</p>{{end}}
|
|
|
|
<h2>Fetch a model</h2>
|
|
<form method="post" action="/admin/mirror/fetch">
|
|
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
|
<label>Source URL
|
|
<input type="text" name="source" required
|
|
placeholder="https://huggingface.co/…/resolve/main/model.gguf"></label>
|
|
<div class="field-row">
|
|
<label>Model <input type="text" name="model" required placeholder="qwen2.5-coder-7b"></label>
|
|
<label>Version <input type="text" name="version" required placeholder="2026-09"></label>
|
|
<label>File <span class="opt">optional</span>
|
|
<input type="text" name="file" placeholder="taken from the URL"></label>
|
|
</div>
|
|
<button type="submit">Start download</button>
|
|
</form>
|
|
<p class="note">The mirror answers as soon as the download is under way, not when
|
|
it finishes — a model is tens of gigabytes and nothing here holds a connection
|
|
open for that. The row above will show it arriving. A version is never
|
|
overwritten: fetching into one that exists is refused, so a new build is a new
|
|
version and the old weights stay where they are.</p>
|
|
{{end}}
|
|
|
|
<footer><span>llmgw {{.Version}}</span><span>{{.Now}}</span></footer>
|
|
</div></body></html>{{end}}
|