Commit c5a2a4cc37
Verified · cmc ci/build: success ci/test: success
e2e/adminusersweb_test.go +4
| @@ -57,6 +57,10 @@ func TestAdminUsersWeb(t *testing.T) { | ||
| 57 | 57 | } |
| 58 | 58 | post(url.Values{"field": {"enable"}, "user": {"alice"}}) |
| 59 | 59 | post(url.Values{"field": {"promote"}, "user": {"alice"}}) |
| 60 | if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "show", "alice", "--json"); strings.Contains(out, `"admin":true`) { | |
| 61 | t.Fatalf("promote without a confirm took: %s", out) | |
| 62 | } | |
| 63 | post(url.Values{"field": {"promote"}, "user": {"alice"}, "confirm": {"alice"}}) | |
| 60 | 64 | if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "show", "alice", "--json"); !strings.Contains(out, `"admin":true`) { |
| 61 | 65 | t.Fatalf("promote did not take: %s", out) |
| 62 | 66 | } |
e2e/mrweb_test.go +2 −1
| @@ -483,7 +483,8 @@ func TestMRSupersedes(t *testing.T) { | ||
| 483 | 483 | } |
| 484 | 484 | |
| 485 | 485 | alice := inst.login(t, aliceKey) |
| 486 | if status, body := browserPost(t, alice, inst.base()+"/alice/app/mrs/1/close", url.Values{"by": {"2"}}); status != 200 { | |
| 486 | // The field's placeholder is "!N", so the "!" is accepted. | |
| 487 | if status, body := browserPost(t, alice, inst.base()+"/alice/app/mrs/1/close", url.Values{"by": {"!2"}}); status != 200 { | |
| 487 | 488 | t.Fatalf("close post: %d\n%s", status, body) |
| 488 | 489 | } |
| 489 | 490 | |
e2e/orgweb_test.go +33 −2
| @@ -85,10 +85,32 @@ func TestOrgManagementWeb(t *testing.T) { | ||
| 85 | 85 | } |
| 86 | 86 | } |
| 87 | 87 | |
| 88 | // Revoking and removing work the same way round. | |
| 88 | // Revoking and removing need the team's name typed; a bare post | |
| 89 | // changes nothing. | |
| 89 | 90 | browserPost(t, alice, inst.base()+"/acme", url.Values{ |
| 90 | 91 | "field": {"team-revoke"}, "team": {"builders"}, "repo": {"acme/widget"}, |
| 91 | 92 | }) |
| 93 | if out, _, _ := inst.ssh(t, aliceKey, "", "org", "team", "show", "acme", "builders", "--json"); !strings.Contains(out, `"acme/widget"`) { | |
| 94 | t.Fatalf("unconfirmed revoke dropped the grant: %s", out) | |
| 95 | } | |
| 96 | browserPost(t, alice, inst.base()+"/acme", url.Values{ | |
| 97 | "field": {"team-revoke"}, "team": {"builders"}, "repo": {"acme/widget"}, "confirm": {"builders"}, | |
| 98 | }) | |
| 99 | if out, _, _ := inst.ssh(t, aliceKey, "", "org", "team", "show", "acme", "builders", "--json"); strings.Contains(out, `"acme/widget"`) { | |
| 100 | t.Fatalf("confirmed revoke left the grant: %s", out) | |
| 101 | } | |
| 102 | browserPost(t, alice, inst.base()+"/acme", url.Values{ | |
| 103 | "field": {"team-remove"}, "team": {"builders"}, "user": {"bob"}, | |
| 104 | }) | |
| 105 | if out, _, _ := inst.ssh(t, aliceKey, "", "org", "team", "show", "acme", "builders", "--json"); !strings.Contains(out, `"bob"`) { | |
| 106 | t.Fatalf("unconfirmed team remove took bob out: %s", out) | |
| 107 | } | |
| 108 | browserPost(t, alice, inst.base()+"/acme", url.Values{ | |
| 109 | "field": {"team-remove"}, "team": {"builders"}, "user": {"bob"}, "confirm": {"builders"}, | |
| 110 | }) | |
| 111 | if out, _, _ := inst.ssh(t, aliceKey, "", "org", "team", "show", "acme", "builders", "--json"); strings.Contains(out, `"bob"`) { | |
| 112 | t.Fatalf("confirmed team remove left bob in: %s", out) | |
| 113 | } | |
| 92 | 114 | |
| 93 | 115 | // Deleting the team needs its name typed; a bare post is refused and |
| 94 | 116 | // the team stays. |
| @@ -108,9 +130,18 @@ func TestOrgManagementWeb(t *testing.T) { | ||
| 108 | 130 | t.Fatalf("team not deleted: exit %d", code) |
| 109 | 131 | } |
| 110 | 132 | |
| 111 | browserPost(t, alice, inst.base()+"/acme", url.Values{ | |
| 133 | _, body = browserPost(t, alice, inst.base()+"/acme", url.Values{ | |
| 112 | 134 | "field": {"member-remove"}, "user": {"bob"}, |
| 113 | 135 | }) |
| 136 | if !strings.Contains(body, "type bob to confirm") { | |
| 137 | t.Fatalf("unconfirmed member remove was not refused:\n%s", body) | |
| 138 | } | |
| 139 | if members := orgMembers(t, inst, aliceKey); len(members) != 2 { | |
| 140 | t.Fatalf("member removed without confirmation: %v", members) | |
| 141 | } | |
| 142 | browserPost(t, alice, inst.base()+"/acme", url.Values{ | |
| 143 | "field": {"member-remove"}, "user": {"bob"}, "confirm": {"bob"}, | |
| 144 | }) | |
| 114 | 145 | if members := orgMembers(t, inst, aliceKey); len(members) != 1 { |
| 115 | 146 | t.Fatalf("member not removed: %v", members) |
| 116 | 147 | } |
internal/httpd/adminusers.go +1 −1
| @@ -105,7 +105,7 @@ func (s *Server) adminUsersSubmit(w http.ResponseWriter, r *http.Request, viewer | ||
| 105 | 105 | back("unknown action") |
| 106 | 106 | return |
| 107 | 107 | } |
| 108 | if verb == "demote" || verb == "disable" { | |
| 108 | if verb == "promote" || verb == "demote" || verb == "disable" { | |
| 109 | 109 | if ok, msg := confirmed(r, name); !ok { |
| 110 | 110 | back(msg) |
| 111 | 111 | return |
internal/httpd/mractions.go +1 −1
| @@ -87,7 +87,7 @@ func (s *Server) mrMergeSubmit(w http.ResponseWriter, r *http.Request, u store.U | ||
| 87 | 87 | |
| 88 | 88 | func (s *Server) mrCloseSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
| 89 | 89 | args := []string{} |
| 90 | if by := strings.TrimSpace(r.FormValue("by")); by != "" { | |
| 90 | if by := strings.TrimPrefix(strings.TrimSpace(r.FormValue("by")), "!"); by != "" { | |
| 91 | 91 | if _, err := strconv.ParseInt(by, 10, 64); err != nil { |
| 92 | 92 | s.mrRedirect(w, r, "the superseding request is a number") |
| 93 | 93 | return |
internal/httpd/orgweb.go +12
| @@ -78,6 +78,10 @@ func (s *Server) orgSubmit(w http.ResponseWriter, r *http.Request, u store.User) | ||
| 78 | 78 | argv = append(argv, "--role", role) |
| 79 | 79 | } |
| 80 | 80 | case "member-remove": |
| 81 | if ok, msg := confirmed(r, user); !ok { | |
| 82 | back(msg) | |
| 83 | return | |
| 84 | } | |
| 81 | 85 | argv = []string{"org", "members", "remove", owner, user} |
| 82 | 86 | case "team-create": |
| 83 | 87 | argv = []string{"org", "team", "create", owner, team} |
| @@ -90,11 +94,19 @@ func (s *Server) orgSubmit(w http.ResponseWriter, r *http.Request, u store.User) | ||
| 90 | 94 | case "team-add": |
| 91 | 95 | argv = append([]string{"org", "team", "add", owner, team}, strings.Fields(user)...) |
| 92 | 96 | case "team-remove": |
| 97 | if ok, msg := confirmed(r, team); !ok { | |
| 98 | back(msg) | |
| 99 | return | |
| 100 | } | |
| 93 | 101 | argv = append([]string{"org", "team", "remove", owner, team}, strings.Fields(user)...) |
| 94 | 102 | case "team-grant": |
| 95 | 103 | argv = []string{"org", "team", "grant", owner, team, |
| 96 | 104 | strings.TrimSpace(r.FormValue("repo")), r.FormValue("role")} |
| 97 | 105 | case "team-revoke": |
| 106 | if ok, msg := confirmed(r, team); !ok { | |
| 107 | back(msg) | |
| 108 | return | |
| 109 | } | |
| 98 | 110 | argv = []string{"org", "team", "revoke", owner, team, strings.TrimSpace(r.FormValue("repo"))} |
| 99 | 111 | default: |
| 100 | 112 | back("unknown form") |
internal/web/static/style.css +6 −6
| @@ -523,7 +523,7 @@ nav.tabs a i { font-style: normal; color: var(--muted); margin-left: 4px; } | ||
| 523 | 523 | .pagehead h1, .listhead h1, .headrow h1, .listhead h2, .headrow h2 { margin: 0; } |
| 524 | 524 | .pagehead > p, .listhead > p, .headrow > p { margin: 0; } |
| 525 | 525 | .pagehead .grow, .listhead .spacer, .headrow .spacer { flex: 1; } |
| 526 | .issuetitle { margin: 0 0 var(--sp-2); font-size: var(--fs-5); } | |
| 526 | .issuetitle { margin: 0 0 var(--sp-2); font-size: var(--fs-5); text-wrap: balance; } | |
| 527 | 527 | .issuenumber { color: var(--muted); font-weight: 400; } |
| 528 | 528 | .issuemeta { color: var(--muted); font-size: var(--fs-2); margin: 0 0 var(--sp-4); } |
| 529 | 529 | .commithead p { margin: var(--sp-1) 0; } |
| @@ -905,7 +905,7 @@ table.tree td.lastcommit a { | ||
| 905 | 905 | table.tree td.lastcommit a:hover { color: var(--link); } |
| 906 | 906 | table.tree th.age, table.tree td.age { width: 20%; } |
| 907 | 907 | td.age, th.age { text-align: right; white-space: nowrap; color: var(--muted); font-variant-numeric: tabular-nums; } |
| 908 | td.size, td.mode { color: var(--muted); white-space: nowrap; } | |
| 908 | td.size, td.mode { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; } | |
| 909 | 909 | table.tree th.size, table.tree td.size { text-align: right; } |
| 910 | 910 | table.refs td.name { width: 100%; } |
| 911 | 911 | table.refs td.sha, td.sha { color: var(--muted); } |
| @@ -928,8 +928,8 @@ table.keys.nowrap th, table.keys.nowrap td { white-space: nowrap; } | ||
| 928 | 928 | The cell is two fixed slots, each right-aligned. A form cannot span |
| 929 | 929 | table cells, so one column per control — which would let the table |
| 930 | 930 | align them — is out without form="" plumbing, and the forms hold |
| 931 | different numbers of controls: only an admin's role form has a | |
| 932 | confirm field, and the enable form has none. Sizing the slot instead | |
| 931 | different numbers of controls: the enable form and the disabled | |
| 932 | row's Promote have no confirm field. Sizing the slot instead | |
| 933 | 933 | of the form is what puts every button in one column and every field |
| 934 | 934 | in another; right-aligning the cell alone left the disabled row's |
| 935 | 935 | Promote where other rows keep their field. A slot is a field, a gap |
| @@ -1486,7 +1486,7 @@ a.memberchip { | ||
| 1486 | 1486 | a.memberchip:hover { text-decoration: none; border-color: var(--link); color: var(--link); } |
| 1487 | 1487 | a.memberchip .role { color: var(--muted); } |
| 1488 | 1488 | |
| 1489 | .pathbar { display: flex; align-items: center; gap: var(--sp-2) var(--sp-3); margin: 0 0 var(--sp-3); flex-wrap: wrap; font-size: var(--fs-2); } | |
| 1489 | .pathbar { display: flex; align-items: center; gap: var(--sp-2) var(--sp-3); margin: 0 0 var(--sp-3); flex-wrap: wrap; font-size: var(--fs-2); overflow-wrap: anywhere; } | |
| 1490 | 1490 | .pathbar .spacer { flex: 1; } |
| 1491 | 1491 | .pathbar .actions { display: flex; align-items: center; gap: var(--sp-2); } |
| 1492 | 1492 | /* history · blame · raw were 21px tall and "raw" 22px wide (#232) */ |
| @@ -1608,6 +1608,7 @@ details.refmenu .refdrop { | ||
| 1608 | 1608 | min-width: 12rem; |
| 1609 | 1609 | max-height: 20rem; |
| 1610 | 1610 | overflow-y: auto; |
| 1611 | overscroll-behavior: contain; | |
| 1611 | 1612 | background: var(--canvas); |
| 1612 | 1613 | border: 1px solid var(--line); |
| 1613 | 1614 | border-radius: var(--r-card); |
| @@ -1849,7 +1850,6 @@ svg.icon { vertical-align: -0.125em; } | ||
| 1849 | 1850 | cell padding and the card clipped the age; a long name wraps (#232) */ |
| 1850 | 1851 | table.tree td.name { white-space: normal; overflow-wrap: anywhere; } |
| 1851 | 1852 | .clone pre { white-space: pre-wrap; word-break: break-all; } |
| 1852 | .pathbar { overflow-wrap: anywhere; } | |
| 1853 | 1853 | .readme .cardbody, .code { padding: var(--sp-3); } |
| 1854 | 1854 | .thread { margin-left: var(--sp-3); } |
| 1855 | 1855 | .blamehunk { flex-direction: column; gap: 0; } |
internal/web/templates/account.html +4 −4
| @@ -32,7 +32,7 @@ | ||
| 32 | 32 | <label for="p-website">Website</label> |
| 33 | 33 | <input type="text" id="p-website" name="website" value="{{.Profile.Website}}" placeholder="https://example.org"> |
| 34 | 34 | <label for="p-links">Links</label> |
| 35 | <textarea id="p-links" name="links" rows="3" placeholder="one per line: label|https://... or a bare https://... (at most 5)">{{.LinksText}}</textarea> | |
| 35 | <textarea id="p-links" name="links" rows="3" placeholder="one per line: label|https://… or a bare https://… (at most 5)">{{.LinksText}}</textarea> | |
| 36 | 36 | <span class="btngroup"><button type="submit" class="btn">Save profile</button></span> |
| 37 | 37 | </form> |
| 38 | 38 | <h3>About</h3> |
| @@ -65,7 +65,7 @@ commands and push; a <code>git</code> key can only move git data (e.g., CI).</p> | ||
| 65 | 65 | <form method="post" action="/settings" class="setform stack"> |
| 66 | 66 | <input type="hidden" name="field" value="key-add"> |
| 67 | 67 | <label for="key">Public key</label> |
| 68 | <textarea id="key" name="key" rows="3" required placeholder="ssh-ed25519 AAAA... you@machine"></textarea> | |
| 68 | <textarea id="key" name="key" rows="3" required spellcheck="false" placeholder="ssh-ed25519 AAAA… you@machine"></textarea> | |
| 69 | 69 | <label for="key-label">Label</label> |
| 70 | 70 | <input id="key-label" name="label" maxlength="64" placeholder="defaults to the key's comment"> |
| 71 | 71 | <label for="scope">Scope</label> |
| @@ -94,13 +94,13 @@ account and where notifications go.</p> | ||
| 94 | 94 | <form method="post" action="/settings" class="setform"> |
| 95 | 95 | <input type="hidden" name="field" value="email-add"> |
| 96 | 96 | <label for="address">Address</label> |
| 97 | <input type="email" id="address" name="address" required placeholder="you@example.org"> | |
| 97 | <input type="email" id="address" name="address" required autocomplete="email" placeholder="you@example.org"> | |
| 98 | 98 | <button type="submit" class="btn">Send code</button> |
| 99 | 99 | </form> |
| 100 | 100 | <form method="post" action="/settings" class="setform"> |
| 101 | 101 | <input type="hidden" name="field" value="email-verify"> |
| 102 | 102 | <label for="code">Verification code</label> |
| 103 | <input type="text" id="code" name="code" required placeholder="from the mail"> | |
| 103 | <input type="text" id="code" name="code" required autocomplete="one-time-code" autocapitalize="none" spellcheck="false" placeholder="from the mail"> | |
| 104 | 104 | <button type="submit" class="btn">Verify</button> |
| 105 | 105 | </form> |
| 106 | 106 | </details> |
internal/web/templates/adminusers.html +1 −1
| @@ -38,7 +38,7 @@ | ||
| 38 | 38 | <input type="hidden" name="field" value="promote"> |
| 39 | 39 | <input type="hidden" name="user" value="{{.Username}}"> |
| 40 | 40 | <input type="hidden" name="state" value="{{$.State}}"> |
| 41 | <button type="submit" class="btn">Promote</button> | |
| 41 | {{template "confirmfield" .Username}} <button type="submit" class="btn">Promote</button> | |
| 42 | 42 | </form> |
| 43 | 43 | {{else}} |
| 44 | 44 | {{/* An account that is not active cannot be promoted, but the row |
internal/web/templates/builds.html +1 −1
| @@ -11,7 +11,7 @@ | ||
| 11 | 11 | </div>{{end}}{{end}} |
| 12 | 12 | <form method="get" class="searchform compact"> |
| 13 | 13 | <label for="ref" class="colhead">Branch</label> |
| 14 | <input type="text" id="ref" name="ref" value="{{.Filter.Ref}}" list="buildrefs"> | |
| 14 | <input type="text" id="ref" name="ref" value="{{.Filter.Ref}}" list="buildrefs" spellcheck="false"> | |
| 15 | 15 | <datalist id="buildrefs">{{range .Refs}}<option value="{{.}}">{{end}}</datalist> |
| 16 | 16 | <input type="hidden" name="status" value="{{.Filter.Status}}"> |
| 17 | 17 | <input type="hidden" name="job" value="{{.Filter.Job}}"> |
internal/web/templates/fork.html +1 −1
| @@ -11,7 +11,7 @@ own.</p> | ||
| 11 | 11 | <option value="{{.Viewer}}">{{.Viewer}}</option> |
| 12 | 12 | {{range .Orgs}}<option value="{{.}}"{{if eq . $.Owner}} selected{{end}}>{{.}}</option>{{end}} |
| 13 | 13 | </select></label> |
| 14 | <label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" value="{{.Name}}" aria-describedby="forkhint"></label> | |
| 14 | <label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" value="{{.Name}}" aria-describedby="forkhint" autocomplete="off" spellcheck="false"></label> | |
| 15 | 15 | <span class="hint" id="forkhint">Defaults to the source name; change it to fork twice into the same owner.</span></p> |
| 16 | 16 | <p><button type="submit">Create fork</button></p> |
| 17 | 17 | </form> |
internal/web/templates/issue.html +4 −4
| @@ -55,8 +55,8 @@ | ||
| 55 | 55 | {{else}}<p class="none">none yet</p>{{end}} |
| 56 | 56 | {{if .CanWrite}} |
| 57 | 57 | <form method="post" action="{{$base}}/label" class="actions"> |
| 58 | <input type="text" name="add" aria-label="Add labels" placeholder="add, space-separated"> | |
| 59 | <input type="text" name="remove" aria-label="Remove labels" placeholder="remove"> | |
| 58 | <input type="text" name="add" aria-label="Add labels" autocomplete="off" spellcheck="false" placeholder="add, space-separated"> | |
| 59 | <input type="text" name="remove" aria-label="Remove labels" autocomplete="off" spellcheck="false" placeholder="remove"> | |
| 60 | 60 | <button type="submit" class="btn">Apply</button> |
| 61 | 61 | </form> |
| 62 | 62 | {{end}} |
| @@ -67,8 +67,8 @@ | ||
| 67 | 67 | {{else}}<p class="none">nobody yet</p>{{end}} |
| 68 | 68 | {{if .CanWrite}} |
| 69 | 69 | <form method="post" action="{{$base}}/assign" class="actions"> |
| 70 | <input type="text" name="add" aria-label="Add assignees" placeholder="add, space-separated"> | |
| 71 | <input type="text" name="remove" aria-label="Remove assignees" placeholder="remove"> | |
| 70 | <input type="text" name="add" aria-label="Add assignees" autocomplete="off" spellcheck="false" placeholder="add, space-separated"> | |
| 71 | <input type="text" name="remove" aria-label="Remove assignees" autocomplete="off" spellcheck="false" placeholder="remove"> | |
| 72 | 72 | <button type="submit" class="btn">Apply</button> |
| 73 | 73 | </form> |
| 74 | 74 | {{end}} |
internal/web/templates/layout.html +2 −1
| @@ -6,6 +6,7 @@ | ||
| 6 | 6 | <title>{{template "title" .}}</title> |
| 7 | 7 | <link rel="stylesheet" href="/static/style.css?v={{styleVersion}}"> |
| 8 | 8 | <link rel="icon" href="/favicon.svg" type="image/svg+xml"> |
| 9 | <meta name="theme-color" content="#000000"> | |
| 9 | 10 | {{with field . "Feed"}}<link rel="alternate" type="application/atom+xml" href="{{.}}"> |
| 10 | 11 | {{end}}</head> |
| 11 | 12 | <body> |
| @@ -223,7 +224,7 @@ | ||
| 223 | 224 | |
| 224 | 225 | {{/* cmtln turns a line number into the link that opens the comment form |
| 225 | 226 | on that line. No JavaScript: the anchor travels in the query. */}} |
| 226 | {{define "cmtln"}}{{if and .Viewer .Base}}<a class="cmt" title="Comment on this line" href="{{.Base}}?view=diff&cpath={{.Path}}&cline={{.N}}&cside={{.Side}}#compose">{{.N}}</a>{{else}}{{.N}}{{end}}{{end}} | |
| 227 | {{define "cmtln"}}{{if and .Viewer .Base}}<a class="cmt" aria-label="Comment on line {{.N}}" title="Comment on this line" href="{{.Base}}?view=diff&cpath={{.Path}}&cline={{.N}}&cside={{.Side}}#compose">{{.N}}</a>{{else}}{{.N}}{{end}}{{end}} | |
| 227 | 228 | |
| 228 | 229 | {{/* thread renders one review thread with its reply and resolve controls. |
| 229 | 230 | Class carries "stale" for threads whose anchor is gone. */}} |
internal/web/templates/login.html +1 −1
| @@ -11,7 +11,7 @@ expires in fifteen minutes.</p> | ||
| 11 | 11 | {{if .EmailLogin}} |
| 12 | 12 | <form method="post" action="/login"> |
| 13 | 13 | <div class="field"><label for="identifier">Username or email address</label> |
| 14 | <input type="text" id="identifier" name="identifier" autocomplete="username" required></div> | |
| 14 | <input type="text" id="identifier" name="identifier" autocomplete="username" autocapitalize="none" spellcheck="false" required></div> | |
| 15 | 15 | <button type="submit">Email me a link</button> |
| 16 | 16 | </form> |
| 17 | 17 | <p>Or, from a machine with your registered key:</p> |
internal/web/templates/mr.html +5 −5
| @@ -107,7 +107,7 @@ | ||
| 107 | 107 | </form> |
| 108 | 108 | <form method="post" action="{{$base}}/close" class="actions"> |
| 109 | 109 | <label class="vh" for="by">Closed in favour of</label> |
| 110 | <input type="text" id="by" name="by" inputmode="numeric" size="4" placeholder="!N"> | |
| 110 | <input type="text" id="by" name="by" size="4" autocomplete="off" placeholder="!N"> | |
| 111 | 111 | <button type="submit" class="danger">Close without merging</button> |
| 112 | 112 | </form> |
| 113 | 113 | <form method="post" action="{{$base}}/draft" class="actions"> |
| @@ -143,8 +143,8 @@ | ||
| 143 | 143 | {{else}}<p class="none">nobody yet</p>{{end}} |
| 144 | 144 | {{if and .CanWrite (or (eq .MR.State "open") (eq .MR.State "source_gone"))}} |
| 145 | 145 | <form method="post" action="{{$base}}/review-request" class="actions"> |
| 146 | <input type="text" name="add" aria-label="Add reviewers" placeholder="add, space-separated"> | |
| 147 | <input type="text" name="remove" aria-label="Remove reviewers" placeholder="remove"> | |
| 146 | <input type="text" name="add" aria-label="Add reviewers" autocomplete="off" spellcheck="false" placeholder="add, space-separated"> | |
| 147 | <input type="text" name="remove" aria-label="Remove reviewers" autocomplete="off" spellcheck="false" placeholder="remove"> | |
| 148 | 148 | <button type="submit" class="btn">Apply</button> |
| 149 | 149 | </form> |
| 150 | 150 | {{end}} |
| @@ -174,8 +174,8 @@ | ||
| 174 | 174 | {{else}}<p class="none">none yet</p>{{end}} |
| 175 | 175 | {{if .CanWrite}} |
| 176 | 176 | <form method="post" action="{{$base}}/label" class="actions"> |
| 177 | <input type="text" name="add" aria-label="Add labels" placeholder="add, space-separated"> | |
| 178 | <input type="text" name="remove" aria-label="Remove labels" placeholder="remove"> | |
| 177 | <input type="text" name="add" aria-label="Add labels" autocomplete="off" spellcheck="false" placeholder="add, space-separated"> | |
| 178 | <input type="text" name="remove" aria-label="Remove labels" autocomplete="off" spellcheck="false" placeholder="remove"> | |
| 179 | 179 | <button type="submit" class="btn">Apply</button> |
| 180 | 180 | </form> |
| 181 | 181 | {{end}} |
internal/web/templates/new.html +2 −2
| @@ -9,7 +9,7 @@ | ||
| 9 | 9 | <option value="{{.Viewer}}">{{.Viewer}}</option> |
| 10 | 10 | {{range .Orgs}}<option value="{{.}}">{{.}}</option>{{end}} |
| 11 | 11 | </select></label> |
| 12 | <label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="namehint"></label> | |
| 12 | <label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="namehint" autocomplete="off" spellcheck="false"></label> | |
| 13 | 13 | <span class="hint" id="namehint">Lowercase letters, digits, dot, dash and underscore; must start with a letter or digit; up to 63 characters.</span></p> |
| 14 | 14 | <fieldset class="segmented"> |
| 15 | 15 | <legend>Visibility</legend> |
| @@ -26,7 +26,7 @@ | ||
| 26 | 26 | grants access through teams. You are its first admin.</p> |
| 27 | 27 | <form method="post" action="/new"> |
| 28 | 28 | <input type="hidden" name="field" value="org-create"> |
| 29 | <p><label>Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="orghint"></label> | |
| 29 | <p><label>Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="orghint" autocomplete="off" spellcheck="false"></label> | |
| 30 | 30 | <span class="hint" id="orghint">The same rules as a repository name, and it becomes a top-level path.</span></p> |
| 31 | 31 | <p><button type="submit">Create organization</button></p> |
| 32 | 32 | </form> |
internal/web/templates/owner.html +27 −11
| @@ -89,7 +89,7 @@ | ||
| 89 | 89 | {{range .Members}}<tr> |
| 90 | 90 | <td><a href="/{{.Name}}">{{.Name}}</a></td> |
| 91 | 91 | <td>{{.Role}}</td> |
| 92 | <td class="act"><form method="post" action="/{{$org}}"><input type="hidden" name="field" value="member-remove"><input type="hidden" name="user" value="{{.Name}}"><button type="submit" class="linklike">Remove</button></form></td> | |
| 92 | <td class="act"><form method="post" action="/{{$org}}"><input type="hidden" name="field" value="member-remove"><input type="hidden" name="user" value="{{.Name}}">{{template "confirmfield" .Name}} <button type="submit" class="linklike">Remove</button></form></td> | |
| 93 | 93 | </tr> |
| 94 | 94 | {{end}}</table></div> |
| 95 | 95 | <details class="editbox"> |
| @@ -97,7 +97,7 @@ | ||
| 97 | 97 | <form method="post" action="/{{$org}}" class="setform stack"> |
| 98 | 98 | <input type="hidden" name="field" value="member-add"> |
| 99 | 99 | <label for="member">Username</label> |
| 100 | <input type="text" id="member" name="user" required> | |
| 100 | <input type="text" id="member" name="user" required autocomplete="off" spellcheck="false"> | |
| 101 | 101 | <label class="none" for="memberrole">Role</label> |
| 102 | 102 | <select id="memberrole" name="role"> |
| 103 | 103 | <option value="member">member</option> |
| @@ -118,25 +118,41 @@ of a team get its role on every repository it is granted.</p> | ||
| 118 | 118 | <p class="meta">members: {{range .Members}}<a class="memberchip" href="/{{.}}">{{.}}</a> {{else}}<span class="none">none yet</span>{{end}}</p> |
| 119 | 119 | <p class="meta">repositories: {{range .Grants}}<span class="memberchip"><a href="/{{.RepoPath}}">{{.RepoPath}}</a> <span class="role">{{.Role}}</span></span> {{else}}<span class="none">none yet</span>{{end}}</p> |
| 120 | 120 | <form method="post" action="/{{$org}}" class="setform stack"> |
| 121 | <input type="hidden" name="field" value="team-add"> | |
| 121 | 122 | <input type="hidden" name="team" value="{{.Name}}"> |
| 122 | <label class="none" for="tm-{{.Name}}">Members</label> | |
| 123 | <input type="text" id="tm-{{.Name}}" name="user" placeholder="usernames, space-separated"> | |
| 124 | <button type="submit" name="field" value="team-add" class="btn">Add</button> | |
| 125 | <button type="submit" name="field" value="team-remove" class="btn">Remove</button> | |
| 123 | <label class="none" for="tm-{{.Name}}">Add members</label> | |
| 124 | <input type="text" id="tm-{{.Name}}" name="user" placeholder="usernames, space-separated" autocomplete="off" spellcheck="false"> | |
| 125 | <button type="submit" class="btn">Add</button> | |
| 126 | 126 | </form> |
| 127 | {{if .Members}}<form method="post" action="/{{$org}}" class="setform stack"> | |
| 128 | <input type="hidden" name="field" value="team-remove"> | |
| 129 | <input type="hidden" name="team" value="{{.Name}}"> | |
| 130 | <label class="none" for="tmr-{{.Name}}">Remove members</label> | |
| 131 | <input type="text" id="tmr-{{.Name}}" name="user" placeholder="usernames, space-separated" autocomplete="off" spellcheck="false"> | |
| 132 | {{template "confirmfield" .Name}} | |
| 133 | <button type="submit" class="btn">Remove</button> | |
| 134 | </form>{{end}} | |
| 127 | 135 | <form method="post" action="/{{$org}}" class="setform stack"> |
| 136 | <input type="hidden" name="field" value="team-grant"> | |
| 128 | 137 | <input type="hidden" name="team" value="{{.Name}}"> |
| 129 | <label class="none" for="tr-{{.Name}}">Repository</label> | |
| 130 | <input type="text" id="tr-{{.Name}}" name="repo" placeholder="owner/name"> | |
| 138 | <label class="none" for="tr-{{.Name}}">Grant a repository</label> | |
| 139 | <input type="text" id="tr-{{.Name}}" name="repo" placeholder="owner/name" autocomplete="off" spellcheck="false"> | |
| 131 | 140 | <label class="none" for="trr-{{.Name}}">Role</label> |
| 132 | 141 | <select id="trr-{{.Name}}" name="role"> |
| 133 | 142 | <option value="read">read</option> |
| 134 | 143 | <option value="write">write</option> |
| 135 | 144 | <option value="admin">admin</option> |
| 136 | 145 | </select> |
| 137 | <button type="submit" name="field" value="team-grant" class="btn">Grant</button> | |
| 138 | <button type="submit" name="field" value="team-revoke" class="btn">Revoke</button> | |
| 146 | <button type="submit" class="btn">Grant</button> | |
| 139 | 147 | </form> |
| 148 | {{if .Grants}}<form method="post" action="/{{$org}}" class="setform stack"> | |
| 149 | <input type="hidden" name="field" value="team-revoke"> | |
| 150 | <input type="hidden" name="team" value="{{.Name}}"> | |
| 151 | <label class="none" for="trv-{{.Name}}">Revoke a repository</label> | |
| 152 | <input type="text" id="trv-{{.Name}}" name="repo" placeholder="owner/name" autocomplete="off" spellcheck="false"> | |
| 153 | {{template "confirmfield" .Name}} | |
| 154 | <button type="submit" class="btn">Revoke</button> | |
| 155 | </form>{{end}} | |
| 140 | 156 | <form method="post" action="/{{$org}}" class="setform"> |
| 141 | 157 | <input type="hidden" name="field" value="team-delete"> |
| 142 | 158 | <input type="hidden" name="team" value="{{.Name}}"> |
| @@ -162,7 +178,7 @@ of a team get its role on every repository it is granted.</p> | ||
| 162 | 178 | <form method="post" action="/{{$org}}" class="setform"> |
| 163 | 179 | <input type="hidden" name="field" value="org-rename"> |
| 164 | 180 | <label for="orgrename">New name</label> |
| 165 | <input type="text" id="orgrename" name="name" value="{{$org}}" required> | |
| 181 | <input type="text" id="orgrename" name="name" value="{{$org}}" required autocomplete="off" spellcheck="false"> | |
| 166 | 182 | <button type="submit" class="btn">Rename</button> |
| 167 | 183 | </form> |
| 168 | 184 | <p class="meta">Every clone URL under this organization changes with the name.</p> |
internal/web/templates/refs.html +2 −2
| @@ -2,8 +2,8 @@ | ||
| 2 | 2 | {{define "content"}} |
| 3 | 3 | <h1>Refs</h1> |
| 4 | 4 | <form method="get" action="/{{.Repo.OwnerName}}/{{.Repo.Name}}/compare" class="compareform"> |
| 5 | <label>Compare <input name="base" value="{{.Repo.DefaultBranch}}" size="14" aria-label="base ref"></label> | |
| 6 | <label>with <input name="head" size="14" placeholder="branch, tag or sha" aria-label="head ref"></label> | |
| 5 | <label>Compare <input name="base" value="{{.Repo.DefaultBranch}}" size="14" aria-label="Compare base ref" autocomplete="off" spellcheck="false"></label> | |
| 6 | <label>with <input name="head" size="14" placeholder="branch, tag or sha" aria-label="with head ref" autocomplete="off" spellcheck="false"></label> | |
| 7 | 7 | <button type="submit" class="btn">Compare</button> |
| 8 | 8 | </form> |
| 9 | 9 | <h2>Branches</h2> |
internal/web/templates/register.html +4 −4
| @@ -6,9 +6,9 @@ | ||
| 6 | 6 | {{else}}<p class="lede">Open registration. Your account activates once you verify your email.</p>{{end}} |
| 7 | 7 | {{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}} |
| 8 | 8 | <form method="post" action="/register" class="signupform"> |
| 9 | <div class="field"><label for="username">Username</label><input type="text" id="username" name="username" value="{{.Username}}" required></div> | |
| 10 | {{if eq .Mode "invite"}}<div class="field"><label for="invite">Invite code</label><input type="text" id="invite" name="invite" required></div> | |
| 11 | {{else}}<div class="field"><label for="email">Email</label><input type="text" id="email" name="email" required></div>{{end}} | |
| 9 | <div class="field"><label for="username">Username</label><input type="text" id="username" name="username" value="{{.Username}}" required autocomplete="username" autocapitalize="none" spellcheck="false"></div> | |
| 10 | {{if eq .Mode "invite"}}<div class="field"><label for="invite">Invite code</label><input type="text" id="invite" name="invite" required autocomplete="off" spellcheck="false"></div> | |
| 11 | {{else}}<div class="field"><label for="email">Email</label><input type="email" id="email" name="email" required autocomplete="email"></div>{{end}} | |
| 12 | 12 | <div class="field"><label for="key">SSH public key</label> |
| 13 | 13 | <p class="hint">Paste the contents of your public key file, usually <code>~/.ssh/id_ed25519.pub</code>. It starts with <code>ssh-ed25519</code> or <code>ssh-rsa</code>.</p> |
| 14 | 14 | {{/* The help is a disclosure rather than a link out: a wiki page is on |
| @@ -19,7 +19,7 @@ | ||
| 19 | 19 | <pre class="code" tabindex="0">ssh-keygen -t ed25519 |
| 20 | 20 | cat ~/.ssh/id_ed25519.pub</pre> |
| 21 | 21 | </details> |
| 22 | <textarea id="key" name="key" rows="3" required placeholder="ssh-ed25519 AAAA... you@host"></textarea></div> | |
| 22 | <textarea id="key" name="key" rows="3" required spellcheck="false" placeholder="ssh-ed25519 AAAA… you@host"></textarea></div> | |
| 23 | 23 | <p><button type="submit">Create account</button></p> |
| 24 | 24 | </form> |
| 25 | 25 | <p class="meta">Prefer the terminal? <code>ssh git@{{.Host}} register --username you {{if eq .Mode "invite"}}--invite <code>{{else}}--email you@example.org{{end}}</code></p> |
internal/web/templates/settings.html +1 −1
| @@ -186,7 +186,7 @@ | ||
| 186 | 186 | <input type="hidden" name="field" value="runner-add"> |
| 187 | 187 | <label for="runner-key">Attach a runner</label> |
| 188 | 188 | <p class="hint">Install <code>gitbay-runner</code>, run <code>gitbay-runner init</code>, and paste the key it prints. The runner builds your commits with the repository's secrets; merge requests from forks wait unless it runs with <code>-untrusted</code>.</p> |
| 189 | <textarea id="runner-key" name="key" rows="3" placeholder="ssh-ed25519 AAAA… (from gitbay-runner init)">{{index .Submitted "key"}}</textarea> | |
| 189 | <textarea id="runner-key" name="key" rows="3" spellcheck="false" placeholder="ssh-ed25519 AAAA… (from gitbay-runner init)">{{index .Submitted "key"}}</textarea> | |
| 190 | 190 | <button type="submit" class="btn">Attach</button> |
| 191 | 191 | </form> |
| 192 | 192 | </section> |
internal/web/templates/snippet.html +1 −1
| @@ -29,7 +29,7 @@ | ||
| 29 | 29 | {{if .CanWrite}} |
| 30 | 30 | <details class="editbox"><summary>add a file</summary> |
| 31 | 31 | <form method="post" action="/{{.Owner}}/-/snippets/{{.Snippet.PublicID}}/file" class="commentform"> |
| 32 | <p><input type="text" name="name" aria-label="File name" placeholder="filename" required></p> | |
| 32 | <p><input type="text" name="name" aria-label="File name" placeholder="filename" required autocomplete="off" spellcheck="false"></p> | |
| 33 | 33 | <p><textarea name="content" aria-label="Content" rows="12" required></textarea></p> |
| 34 | 34 | <p><button type="submit" class="btn">Add file</button></p> |
| 35 | 35 | </form></details> |
internal/web/templates/snippetnew.html +1 −1
| @@ -4,7 +4,7 @@ | ||
| 4 | 4 | <h1>New snippet</h1> |
| 5 | 5 | {{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}} |
| 6 | 6 | <form method="post" action="/{{.Owner}}/-/snippets/new" class="commentform"> |
| 7 | <p><input type="text" name="name" aria-label="File name" placeholder="filename" value="{{.Name}}" required></p> | |
| 7 | <p><input type="text" name="name" aria-label="File name" placeholder="filename" value="{{.Name}}" required autocomplete="off" spellcheck="false"></p> | |
| 8 | 8 | <p><input type="text" name="description" aria-label="Description" placeholder="description" value="{{.Description}}"></p> |
| 9 | 9 | <p><select name="visibility" aria-label="Visibility"> |
| 10 | 10 | <option value="unlisted"{{if or (eq .Visibility "unlisted") (eq .Visibility "")}} selected{{end}}>unlisted</option> |