Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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/account.html +97

@@ -0,0 +1,97 @@
1 {{define "account"}}
2 {{template "header" .}}
3 <h1>Account</h1>
4 <p><strong>{{.Account.Label}}</strong> <span class="muted">{{.Account.Name}} · {{.Account.ID}}</span></p>
5
6 <h2>Display name</h2>
7 <p class="muted">Shown next to your account on requests, issues and approvals. Your account
8 name <strong>{{.Account.Name}}</strong> is permanent: your branches live under
9 <code>refs/heads/contrib/{{.Account.Name}}/</code>.</p>
10 <form method="post" action="/account/display" class="form-block">
11 <input type="hidden" name="csrf" value="{{.CSRF}}">
12 <div class="card form-card">
13 <label>Display name
14 <input type="text" name="display" maxlength="64" value="{{.Account.Display}}" placeholder="{{.Account.Name}}">
15 </label>
16 </div>
17 <div class="form-actions"><button type="submit">{{icon "check"}}Save display name</button></div>
18 </form>
19
20 <h2>SSH keys</h2>
21 {{if .Account.Keys}}
22 <table>
23 <tr><th>Fingerprint</th><th>Type</th><th>Comment</th><th></th></tr>
24 {{range .Account.Keys}}
25 <tr>
26 <td class="sha">{{.Fingerprint}}</td>
27 <td class="muted">{{.Type}}</td>
28 <td class="muted">{{.Comment}}</td>
29 <td>
30 <form method="post" action="/account/keys/remove">
31 <input type="hidden" name="csrf" value="{{$.CSRF}}">
32 <input type="hidden" name="fingerprint" value="{{.Fingerprint}}">
33 <button class="link" type="submit">remove</button>
34 </form>
35 </td>
36 </tr>
37 {{end}}
38 </table>
39 {{else}}
40 <p class="muted">No SSH keys yet.</p>
41 {{end}}
42 <form method="post" action="/account/keys" class="form-block">
43 <input type="hidden" name="csrf" value="{{.CSRF}}">
44 <div class="card form-card">
45 <label>Public key <textarea name="key" rows="3" placeholder="ssh-ed25519 AAAA... you@example.com" required></textarea></label>
46 </div>
47 <div class="form-actions"><button type="submit">{{icon "plus"}}Add SSH key</button></div>
48 </form>
49
50 <h2>Passkeys</h2>
51 {{if .Credentials}}
52 <table>
53 <tr><th>Credential</th><th>Label</th><th>Created</th><th>Last used</th></tr>
54 {{range .Credentials}}
55 <tr>
56 <td class="sha">{{credid .ID}}</td>
57 <td>{{if .Label}}{{.Label}}{{else}}passkey{{end}}</td>
58 <td class="muted">{{since .Created.Unix}}</td>
59 <td class="muted">{{if .LastUsed.IsZero}}never{{else}}{{since .LastUsed.Unix}}{{end}}</td>
60 </tr>
61 {{end}}
62 </table>
63 {{else}}
64 <p class="muted">No passkeys yet.</p>
65 {{end}}
66
67 {{if .Enabled}}
68 <h2>Add a passkey</h2>
69 <form data-auth="passkey" class="form-block">
70 <div class="card form-card">
71 <label>Label <input type="text" name="label" placeholder="laptop, phone, security key"></label>
72 <p class="error" data-auth-error></p>
73 </div>
74 <div class="form-actions"><button type="submit">{{icon "plus"}}Register this device</button></div>
75 </form>
76 {{else}}
77 <p class="muted">{{.Reason}}</p>
78 {{end}}
79
80 <h2>Sessions</h2>
81 {{if .Sessions}}
82 <table>
83 <tr><th>Created</th><th>Last seen</th><th>Expires</th><th>Client</th></tr>
84 {{range .Sessions}}
85 <tr>
86 <td class="muted">{{since .Created.Unix}}</td>
87 <td class="muted">{{since .LastSeen.Unix}}</td>
88 <td class="muted">{{.Expires.Format "2006-01-02 15:04"}}</td>
89 <td class="muted">{{.UserAgent}}</td>
90 </tr>
91 {{end}}
92 </table>
93 {{else}}
94 <p class="muted">No sessions.</p>
95 {{end}}
96 {{template "footer" .}}
97 {{end}}