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/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Add one Admin page with status, accounts, and the audit log

43e681d8a5d94567aa9530d32b2c89223c1a17f9 · Keenan Nemetz · 5d ago · parent af018ce8

Add one Admin page with status, accounts, and the audit log

Instance administration was split between the Status and Accounts pages,
and the audit log had no browser view at all. The new /admin page gathers
the status tabs, the account roster, and a read-only audit viewer into one
place with a tab for each, and /status, /runners, and /users redirect to
the tab they named so old links keep working.

The audit reader walks the file backwards a chunk at a time, returns the
newest records first, and shows one extra record to know whether an older
page exists. It skips a line it cannot parse rather than failing the page
and refuses a symlink or a non-regular file, as startup already does.

Refs #198 (01M6MK0NNZ2XR1D5TQKNWYT4HN).

server/admin_users_test.go +6−6

@@ -17,14 +17,14 @@ func TestAdminUsersLifecycle(t *testing.T) {
17 17 }
18 18 sess, cookie := f.browserSession(t, f.ownerID)
19 19
20 resp, body := f.browserGet(t, "/users", cookie)
20 resp, body := f.browserGet(t, "/admin?tab=accounts", cookie)
21 21 if resp.StatusCode != 200 || !strings.Contains(body, "Invite a person") || !strings.Contains(body, "keenan") {
22 22 t.Fatalf("users page = %d:\n%s", resp.StatusCode, body)
23 23 }
24 if !strings.Contains(body, `href="/users" aria-current="page"`) {
24 if !strings.Contains(body, `href="/admin?tab=accounts" aria-current="page"`) {
25 25 t.Fatalf("users page is missing the active top-nav link:\n%s", body)
26 26 }
27 if _, home := f.browserGet(t, "/", cookie); !strings.Contains(home, `href="/users"`) {
27 if _, home := f.browserGet(t, "/", cookie); !strings.Contains(home, `href="/admin"`) {
28 28 t.Fatal("the top navigation does not link to Users for an instance admin")
29 29 }
30 30
@@ -145,7 +145,7 @@ func TestAdminUsersLifecycle(t *testing.T) {
145 145 resp, _ = f.browserPost(t, "/users/"+writer.Name+"/retire", cookie, url.Values{
146 146 "csrf": {sess.CSRF}, "confirm": {"wilbur"},
147 147 })
148 if resp.StatusCode != 303 || resp.Header.Get("Location") != "/users" {
148 if resp.StatusCode != 303 || resp.Header.Get("Location") != "/admin?tab=accounts" {
149 149 t.Fatalf("retire = %d %q", resp.StatusCode, resp.Header.Get("Location"))
150 150 }
151 151 writer, err = f.srv.Identity.Find(writer.Name)
@@ -173,7 +173,7 @@ func TestAdminCreatesBotAccount(t *testing.T) {
173 173 }
174 174 sess, cookie := f.browserSession(t, f.ownerID)
175 175
176 resp, body := f.browserGet(t, "/users", cookie)
176 resp, body := f.browserGet(t, "/admin?tab=accounts", cookie)
177 177 if resp.StatusCode != 200 || !strings.Contains(body, "Create a bot account") || !strings.Contains(body, `action="/users/bots"`) {
178 178 t.Fatalf("users page has no bot form (%d):\n%s", resp.StatusCode, body)
179 179 }
@@ -193,7 +193,7 @@ func TestAdminCreatesBotAccount(t *testing.T) {
193 193 if !acct.Bot() || acct.Role != access.Contribute || len(acct.Keys) != 1 || acct.Keys[0].Line != botKey {
194 194 t.Fatalf("created bot = %+v", acct)
195 195 }
196 if _, list := f.browserGet(t, "/users", cookie); !strings.Contains(list, `<a href="/users/ci-runner">ci-runner</a> <span class="state">bot</span>`) {
196 if _, list := f.browserGet(t, "/admin?tab=accounts", cookie); !strings.Contains(list, `<a href="/users/ci-runner">ci-runner</a> <span class="state">bot</span>`) {
197 197 t.Fatalf("the account list does not mark the bot:\n%s", list)
198 198 }
199 199