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.

Split repository settings into tabs

e189a23e457b6aa01a35a4586586713cdc2d426d · Keenan Nemetz · 23h ago · parent 87ebcc5a

Split repository settings into tabs

One page held four audiences and ten forms. Each part is now its own tab:
General for the repository's own fields, Access for the instance-admin
rules, Secrets for the values jobs read, and Danger for rename and delete.
A tab is offered only to the accounts that may use it, and a viewer with one
tab gets no row at all. The Access and Secrets tabs, and the failures on
each, keep their own URLs so a redirect and an error land where the form was;
the Access and Secrets fragments survive as deep links.

server/admin_web_test.go +21−11

@@ -19,9 +19,9 @@ func TestRepositoryAccessSettings(t *testing.T) {
19 19 f.addWriter(t)
20 20 sess, cookie := f.browserSession(t, f.ownerID)
21 21
22 // Access rules are instance-admin settings, so they live on the repository's
23 // own Settings page rather than in a separate admin surface.
24 resp, body := f.browserGet(t, "/r/"+f.repo+"/settings", cookie)
22 // Access rules are instance-admin settings, so they are a tab on the
23 // repository's own Settings page rather than a separate admin surface.
24 resp, body := f.browserGet(t, "/r/"+f.repo+"/settings?tab=access", cookie)
25 25 if resp.StatusCode != 200 {
26 26 t.Fatalf("settings page = %d:\n%s", resp.StatusCode, body)
27 27 }
@@ -50,10 +50,10 @@ func TestRepositoryAccessSettings(t *testing.T) {
50 50 "runner:" + f.writerID: {"on"},
51 51 }
52 52 resp, _ = f.browserPost(t, "/r/"+f.repo+"/settings/access", cookie, form)
53 if resp.StatusCode != 303 || resp.Header.Get("Location") != "/r/"+f.repo+"/settings?saved=access#access" {
53 if resp.StatusCode != 303 || resp.Header.Get("Location") != "/r/"+f.repo+"/settings?tab=access&saved=access#access" {
54 54 t.Fatalf("save = %d %q", resp.StatusCode, resp.Header.Get("Location"))
55 55 }
56 if resp, body := f.browserGet(t, "/r/"+f.repo+"/settings?saved=access", cookie); resp.StatusCode != 200 || !strings.Contains(body, "Access rules saved") {
56 if resp, body := f.browserGet(t, "/r/"+f.repo+"/settings?tab=access&saved=access", cookie); resp.StatusCode != 200 || !strings.Contains(body, "Access rules saved") {
57 57 t.Fatalf("saved access page = %d:\n%s", resp.StatusCode, body)
58 58 }
59 59
@@ -198,13 +198,23 @@ func TestRepositoryAccessRequiresInstanceAdmin(t *testing.T) {
198 198 if _, err := f.srv.Identity.SetAdmin(f.writerID, true, "test"); err != nil {
199 199 t.Fatal(err)
200 200 }
201 resp, body := f.browserGet(t, "/r/"+f.repo+"/settings", writerCookie)
201 resp, body := f.browserGet(t, "/r/"+f.repo+"/settings?tab=access", writerCookie)
202 202 if resp.StatusCode != 200 || !strings.Contains(body, `id="access"`) {
203 203 t.Fatalf("instance-admin settings = %d:\n%s", resp.StatusCode, body)
204 204 }
205 for _, want := range []string{"Danger zone", "Default branch"} {
206 if !strings.Contains(body, want) {
207 t.Fatalf("instance admin does not see %q:\n%s", want, body)
205 // The repository's own fields and the destructive operations are separate
206 // tabs, so they are read from those URLs too.
207 resp, general := f.browserGet(t, "/r/"+f.repo+"/settings", writerCookie)
208 if resp.StatusCode != 200 || !strings.Contains(general, "Default branch") {
209 t.Fatalf("instance-admin general tab = %d:\n%s", resp.StatusCode, general)
210 }
211 resp, danger := f.browserGet(t, "/r/"+f.repo+"/settings?tab=danger", writerCookie)
212 if resp.StatusCode != 200 {
213 t.Fatalf("instance-admin danger tab = %d:\n%s", resp.StatusCode, danger)
214 }
215 for _, want := range []string{"Rename repository", "Delete repository"} {
216 if !strings.Contains(danger, want) {
217 t.Fatalf("instance admin does not see %q:\n%s", want, danger)
208 218 }
209 219 }
210 220 if _, home := f.browserGet(t, "/", writerCookie); !strings.Contains(home, `href="/admin"`) {
@@ -305,7 +315,7 @@ func TestRepositoryAccessListsOnlyExplicitAccountsAndCanAddOne(t *testing.T) {
305 315 }
306 316 sess, cookie := f.browserSession(t, f.ownerID)
307 317
308 resp, body := f.browserGet(t, "/r/"+f.repo+"/settings", cookie)
318 resp, body := f.browserGet(t, "/r/"+f.repo+"/settings?tab=access", cookie)
309 319 if resp.StatusCode != 200 {
310 320 t.Fatalf("settings = %d:\n%s", resp.StatusCode, body)
311 321 }
@@ -338,7 +348,7 @@ func TestRepositoryAccessListsOnlyExplicitAccountsAndCanAddOne(t *testing.T) {
338 348 if acl.Entries["carol"] != access.Contribute.String() {
339 349 t.Fatalf("carol entry = %q, want contribute", acl.Entries["carol"])
340 350 }
341 _, body = f.browserGet(t, "/r/"+f.repo+"/settings", cookie)
351 _, body = f.browserGet(t, "/r/"+f.repo+"/settings?tab=access", cookie)
342 352 if !strings.Contains(body, `name="role:carol"`) {
343 353 t.Fatalf("carol is not in the access table after being added:\n%s", body)
344 354 }