Files
llmgw/internal/web/templates/admin_users.html
T
Ilker ManapandClaude Opus 5 cd7eedf09e
CI / test (push) Successful in 1m49s
Give the mirror a page, so managing it is not a curl command
/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>
2026-09-27 10:17:12 +02:00

71 lines
2.9 KiB
HTML

{{define "admin_users"}}<!doctype html>
<html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Accounts · llmgw</title><link rel="stylesheet" href="/_static/base.css">
</head><body><div class="wrap">
<header>
<h1>Accounts</h1>
<p class="sub">{{len .Pending}} waiting for approval · {{len .Active}} active</p>
<nav><a href="/admin">cluster</a>
{{if .HasMirror}} <a href="/admin/mirror">mirror</a>
{{end}} <a href="/profile">your keys</a></nav>
</header>
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
{{if .Message}}<p class="ok">{{.Message}}</p>{{end}}
<h2>Waiting for approval</h2>
{{if .Pending}}
<table><tr><th>Email</th><th>Name</th><th>Signed up</th><th>Project</th><th></th></tr>
{{range .Pending}}<tr>
<td>{{.Email}}</td><td>{{.Name}}</td><td>{{ago .CreatedAt}}</td>
<td colspan="2">
<form method="post" action="/admin/users/{{.ID}}/approve" class="inline">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<select name="project">
{{range $.Projects}}<option value="{{.ID}}">{{.Path}}</option>{{end}}
</select>
<button type="submit">Approve</button>
</form>
<form method="post" action="/admin/users/{{.ID}}/suspend" class="inline">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<button type="submit" class="link danger">Reject</button>
</form>
</td>
</tr>{{end}}</table>
{{else}}<p class="empty">Nobody is waiting.</p>{{end}}
<h2>Accounts</h2>
{{if .Active}}
<table><tr><th>Email</th><th>State</th><th>Admin</th><th>Project</th><th>Last login</th><th></th></tr>
{{range .Active}}<tr>
<td>{{.Email}}</td>
<td class="state state-{{.State}}">{{.State}}</td>
<td>{{if .IsAdmin}}yes{{else}}—{{end}}</td>
<td>{{or .ProjectID "—"}}</td>
<td>{{ago .LastLoginAt}}</td>
<td>
{{if eq (printf "%s" .State) "active"}}
<form method="post" action="/admin/users/{{.ID}}/suspend" class="inline">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<button type="submit" class="link danger">Suspend</button>
</form>
{{else}}
<form method="post" action="/admin/users/{{.ID}}/reinstate" class="inline">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<button type="submit" class="link">Reinstate</button>
</form>
{{end}}
</td>
</tr>{{end}}</table>
{{else}}<p class="empty">No accounts yet.</p>{{end}}
<p class="note">Three things are deliberately not here. Making someone an admin,
resetting a password and issuing keys on another person's behalf all need a
shell on the control plane — <code>llmgw users promote</code>,
<code>reset-password</code>, <code>llmgw keys</code>. A stolen admin session can
approve and suspend; it cannot hand itself more access or take over an account.</p>
<footer><span>llmgw {{.Version}}</span><span>{{.Now}}</span></footer>
</div></body></html>{{end}}