vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Build out the forge web UI
f09ef39efe44de693748053a7b596ef328fcf06b · Keenan Nemetz · 15d ago · parent 70b70264
Build out the forge web UI Turn the web surface into a compact, server-rendered forge with HTMX as the progressive dynamic layer. - Split templates and assets into embedded files; add a tokenised dark theme, Lucide icons, and vendored HTMX. - Add the repository Overview page and a separate chronological Activity feed with type filters and HTMX fragment updates. - Finish the issue and change workflows: create pages, comments, close/reopen, labels and assignees, approval, discussion, line-anchored review comments, and HTMX list filters. - Add syntax-highlighted blobs, contributor/statistics derivation, branch-aware code browsing, and a repository Runs list. - Add a loopback-only development login for local write testing, with explicit configuration and listener checks. - Extend tests for the new pages, fragments, labels, assignees, anchors, and development login.
server/web/templates/auth.html +81
| @@ -0,0 +1,81 @@ | |||
| 1 | {{define "login"}} | ||
| 2 | {{template "header" .}} | ||
| 3 | {{if not .Enabled}} | ||
| 4 | <div class="auth"> | ||
| 5 | <div class="card"> | ||
| 6 | <h1>Log in</h1> | ||
| 7 | <p class="muted">{{.Reason}}</p> | ||
| 8 | </div> | ||
| 9 | </div> | ||
| 10 | {{else}} | ||
| 11 | <form class="auth" data-auth="login" data-next="{{.Next}}"> | ||
| 12 | <div class="card form-card"> | ||
| 13 | <h1>Log in</h1> | ||
| 14 | <label>Account name | ||
| 15 | <input type="text" name="username" autocomplete="username webauthn" required autofocus> | ||
| 16 | </label> | ||
| 17 | <p class="error" data-auth-error></p> | ||
| 18 | {{if .Registration}} | ||
| 19 | <p class="muted">No account yet? <a href="/register">Create one</a>.</p> | ||
| 20 | {{else}} | ||
| 21 | <p class="muted">No account yet? Ask an administrator for a one-time enrollment link.</p> | ||
| 22 | {{end}} | ||
| 23 | </div> | ||
| 24 | <div class="form-actions"><button type="submit">Use a passkey</button></div> | ||
| 25 | </form> | ||
| 26 | {{end}} | ||
| 27 | {{template "footer" .}} | ||
| 28 | {{end}} | ||
| 29 | |||
| 30 | {{define "register"}} | ||
| 31 | {{template "header" .}} | ||
| 32 | <form class="auth" data-auth="register"> | ||
| 33 | <div class="card form-card"> | ||
| 34 | <h1>Create an account</h1> | ||
| 35 | <p class="muted">Pick a name and register a passkey. You can add an SSH key after signing in.</p> | ||
| 36 | <label>Account name | ||
| 37 | <input type="text" name="username" pattern="[a-z0-9][a-z0-9_-]{1,31}" maxlength="32" autocomplete="username webauthn" required autofocus> | ||
| 38 | </label> | ||
| 39 | <label>Display name <span class="muted">(optional, shown to other people)</span> | ||
| 40 | <input type="text" name="display" maxlength="64" autocomplete="name"> | ||
| 41 | </label> | ||
| 42 | <p class="error" data-auth-error></p> | ||
| 43 | <p class="muted">Names are 2–32 characters: lowercase letters, digits, <code>-</code> and <code>_</code>. | ||
| 44 | The account name is permanent — it is the folder your branches live under and cannot be | ||
| 45 | changed later. The display name is a label and can be changed any time.</p> | ||
| 46 | </div> | ||
| 47 | <div class="form-actions"><button type="submit">Create account and passkey</button></div> | ||
| 48 | </form> | ||
| 49 | {{template "footer" .}} | ||
| 50 | {{end}} | ||
| 51 | |||
| 52 | {{define "invite"}} | ||
| 53 | {{template "header" .}} | ||
| 54 | {{if and .Account .Enabled .Token}} | ||
| 55 | <form class="auth" data-auth="invite" data-token="{{.Token}}"> | ||
| 56 | <div class="card form-card"> | ||
| 57 | <h1>Join {{.Account.Name}}</h1> | ||
| 58 | <p class="muted">Register this device as a passkey for <strong>{{.Account.Name}}</strong>. The link works once.</p> | ||
| 59 | <p class="error" data-auth-error></p> | ||
| 60 | </div> | ||
| 61 | <div class="form-actions"><button type="submit">Create a passkey</button></div> | ||
| 62 | </form> | ||
| 63 | {{else}} | ||
| 64 | <div class="auth"> | ||
| 65 | <div class="card"> | ||
| 66 | {{if .Account}} | ||
| 67 | <h1>Join {{.Account.Name}}</h1> | ||
| 68 | {{if not .Enabled}} | ||
| 69 | <p class="muted">{{.Reason}}</p> | ||
| 70 | {{else}} | ||
| 71 | <p class="error">This enrollment link is no longer valid.</p> | ||
| 72 | {{end}} | ||
| 73 | {{else}} | ||
| 74 | <h1>Enrollment link</h1> | ||
| 75 | <p class="error">This enrollment link is invalid or has expired. Ask an administrator for a new one.</p> | ||
| 76 | {{end}} | ||
| 77 | </div> | ||
| 78 | </div> | ||
| 79 | {{end}} | ||
| 80 | {{template "footer" .}} | ||
| 81 | {{end}} | ||