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/status_test.go +12−12

@@ -20,10 +20,10 @@ func (f *fixture) adminGet(t *testing.T, path string) (int, string) {
20 20 return resp.StatusCode, body
21 21 }
22 22
23 func TestStatusPageSummarizesTheInstance(t *testing.T) {
23 func TestAdminOverviewSummarizesTheInstance(t *testing.T) {
24 24 t.Parallel()
25 25 f := newFixture(t)
26 code, body := f.adminGet(t, "/status")
26 code, body := f.adminGet(t, "/admin")
27 27 if code != 200 {
28 28 t.Fatalf("status page = %d:\n%s", code, body)
29 29 }
@@ -42,7 +42,7 @@ func TestStatusPageSummarizesTheInstance(t *testing.T) {
42 42 }
43 43 }
44 44
45 func TestStatusPageShowsConfigurationToAdmins(t *testing.T) {
45 func TestAdminConfigurationShowsToAdmins(t *testing.T) {
46 46 t.Parallel()
47 47 f := newFixtureWith(t, func(c *config.Config) {
48 48 c.InstanceName = "Acme Forge"
@@ -52,12 +52,12 @@ func TestStatusPageShowsConfigurationToAdmins(t *testing.T) {
52 52
53 53 // The page is instance-admin only: anonymous goes to login, and a signed-in
54 54 // non-admin gets the same 404 as a missing page.
55 if resp, _ := f.browserGet(t, "/status", ""); resp.StatusCode != http.StatusSeeOther || !strings.Contains(resp.Header.Get("Location"), "/login") {
55 if resp, _ := f.browserGet(t, "/admin", ""); resp.StatusCode != http.StatusSeeOther || !strings.Contains(resp.Header.Get("Location"), "/login") {
56 56 t.Fatalf("anonymous status page = %d %q", resp.StatusCode, resp.Header.Get("Location"))
57 57 }
58 58 f.addWriter(t)
59 59 _, writerCookie := f.browserSession(t, f.writerID)
60 if resp, _ := f.browserGet(t, "/status?tab=configuration", writerCookie); resp.StatusCode != http.StatusNotFound {
60 if resp, _ := f.browserGet(t, "/admin?tab=configuration", writerCookie); resp.StatusCode != http.StatusNotFound {
61 61 t.Fatalf("non-admin status page = %d, want 404", resp.StatusCode)
62 62 }
63 63
@@ -65,7 +65,7 @@ func TestStatusPageShowsConfigurationToAdmins(t *testing.T) {
65 65 t.Fatal(err)
66 66 }
67 67 _, cookie := f.browserSession(t, f.ownerID)
68 resp, body := f.browserGet(t, "/status?tab=configuration", cookie)
68 resp, body := f.browserGet(t, "/admin?tab=configuration", cookie)
69 69 if resp.StatusCode != 200 {
70 70 t.Fatalf("admin status page = %d:\n%s", resp.StatusCode, body)
71 71 }
@@ -79,7 +79,7 @@ func TestStatusPageShowsConfigurationToAdmins(t *testing.T) {
79 79 }
80 80 }
81 81
82 func TestStatusPageCountsPageViews(t *testing.T) {
82 func TestAdminPagesAreNotCountedAsPageViews(t *testing.T) {
83 83 t.Parallel()
84 84 f := newFixture(t)
85 85
@@ -103,7 +103,7 @@ func TestStatusPageCountsPageViews(t *testing.T) {
103 103
104 104 // The status page must not count itself, or a monitor turns the metric into
105 105 // a measure of polling.
106 if code, _ := f.adminGet(t, "/status"); code != 200 {
106 if code, _ := f.adminGet(t, "/admin"); code != 200 {
107 107 t.Fatalf("status = %d", code)
108 108 }
109 109 stats, err = f.srv.Web.PageViews.Stats(time.Now().UTC())
@@ -115,7 +115,7 @@ func TestStatusPageCountsPageViews(t *testing.T) {
115 115 }
116 116 }
117 117
118 func TestStatusPageTabsSplitTheSections(t *testing.T) {
118 func TestAdminTabsSplitTheSections(t *testing.T) {
119 119 t.Parallel()
120 120 f := newFixture(t)
121 121 if _, err := f.srv.Repos.Create("private", f.ownerID, "private example"); err != nil {
@@ -125,7 +125,7 @@ func TestStatusPageTabsSplitTheSections(t *testing.T) {
125 125 t.Fatal(err)
126 126 }
127 127
128 code, storage := f.adminGet(t, "/status?tab=storage")
128 code, storage := f.adminGet(t, "/admin?tab=storage")
129 129 if code != 200 || !strings.Contains(storage, "Repositories") || !strings.Contains(storage, "LFS and build objects") {
130 130 t.Fatalf("storage tab = %d:\n%s", code, storage)
131 131 }
@@ -135,7 +135,7 @@ func TestStatusPageTabsSplitTheSections(t *testing.T) {
135 135 }
136 136 }
137 137
138 code, runners := f.adminGet(t, "/status?tab=runners")
138 code, runners := f.adminGet(t, "/admin?tab=runners")
139 139 if code != 200 || !strings.Contains(runners, "Runners and queue") || !strings.Contains(runners, "Queue") {
140 140 t.Fatalf("runners tab = %d:\n%s", code, runners)
141 141 }
@@ -143,7 +143,7 @@ func TestStatusPageTabsSplitTheSections(t *testing.T) {
143 143 t.Fatalf("runners tab shows overview content:\n%s", runners)
144 144 }
145 145
146 code, config := f.adminGet(t, "/status?tab=configuration")
146 code, config := f.adminGet(t, "/admin?tab=configuration")
147 147 if code != 200 || !strings.Contains(config, "Configuration") || !strings.Contains(config, "Instance and access") {
148 148 t.Fatalf("configuration tab = %d:\n%s", code, config)
149 149 }