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.
Fold admin work into repository settings and a Users page
9579c1f976254fdeb2ec73d4016db5b25c7921c1 · Keenan Nemetz · 14d ago · parent 381d6aac
Fold admin work into repository settings and a Users page Remove the combined /admin tabs. Per-repository access rules now live on the repository's Settings page, visible to instance admins while repository details and the danger zone stay with repository admins. User management moves to /users, linked from the top navigation only for instance admins, and repository creation moves to /repos/new. Align the invite, danger-zone, and settings forms with the shared card-and-actions layout, and make settings section headings consistent.
server/web/templates/admin_users.html +43−42
| @@ -1,20 +1,14 @@ | |||
| 1 | 1 | {{define "admin_users"}} | |
| 2 | 2 | {{template "header" .}} | |
| 3 | {{template "adminhead" .}} | ||
| 3 | <div class="page-head"> | ||
| 4 | <div class="page-head-main"> | ||
| 5 | <h1>Users</h1> | ||
| 6 | <p class="page-sub">Accounts, enrollment links, keys, and sessions. New accounts start with an enrollment link, never a pasted SSH key.</p> | ||
| 7 | </div> | ||
| 8 | </div> | ||
| 4 | 9 | ||
| 5 | 10 | {{if .Error}}<div class="flash bad">{{.Error}}</div>{{end}} | |
| 6 | 11 | ||
| 7 | <div class="card form-card"> | ||
| 8 | <h2>Create an account</h2> | ||
| 9 | <form method="post" action="/admin/users" class="form-block"> | ||
| 10 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | ||
| 11 | <label>Account name | ||
| 12 | <input type="text" name="name" required maxlength="63" autocomplete="off" placeholder="lowercase-name"> | ||
| 13 | </label> | ||
| 14 | <div class="form-actions"><button type="submit">{{icon "user"}}Create invite</button></div> | ||
| 15 | </form> | ||
| 16 | </div> | ||
| 17 | |||
| 18 | 12 | <div class="panel"> | |
| 19 | 13 | <table> | |
| 20 | 14 | <thead><tr><th>Account</th><th>Status</th><th>Role</th><th>Keys</th><th>Sessions</th><th></th></tr></thead> | |
| @@ -22,25 +16,35 @@ | |||
| 22 | 16 | {{range .Users}} | |
| 23 | 17 | <tr> | |
| 24 | 18 | <td> | |
| 25 | <a href="/admin/users/{{.Account.Name}}">{{.Account.Name}}</a> | ||
| 19 | <a href="/users/{{.Account.Name}}">{{.Account.Name}}</a> | ||
| 26 | 20 | </td> | |
| 27 | 21 | <td><span class="state {{.Account.Status}}">{{.Account.Status}}</span></td> | |
| 28 | 22 | <td>{{if .Account.Admin}}<span class="label">instance admin</span>{{else}}<span class="muted">account</span>{{end}}</td> | |
| 29 | 23 | <td>{{len .Account.Keys}}</td> | |
| 30 | 24 | <td>{{.Sessions}}</td> | |
| 31 | <td><a href="/admin/users/{{.Account.Name}}">Manage</a></td> | ||
| 25 | <td><a href="/users/{{.Account.Name}}">Manage</a></td> | ||
| 32 | 26 | </tr> | |
| 33 | 27 | {{end}} | |
| 34 | 28 | </tbody> | |
| 35 | 29 | </table> | |
| 36 | 30 | </div> | |
| 31 | |||
| 32 | <h2>Create an account</h2> | ||
| 33 | <form method="post" action="/users" class="form-block"> | ||
| 34 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | ||
| 35 | <div class="card form-card"> | ||
| 36 | <label>Account name | ||
| 37 | <input type="text" name="name" required maxlength="63" autocomplete="off" placeholder="lowercase-name"> | ||
| 38 | </label> | ||
| 39 | </div> | ||
| 40 | <div class="form-actions"><button type="submit">{{icon "user"}}Create invite</button></div> | ||
| 41 | </form> | ||
| 37 | 42 | {{template "footer" .}} | |
| 38 | 43 | {{end}} | |
| 39 | 44 | ||
| 40 | 45 | {{define "admin_user"}} | |
| 41 | 46 | {{template "header" .}} | |
| 42 | {{template "adminhead" .}} | ||
| 43 | <div class="page-head subhead"> | ||
| 47 | <div class="page-head"> | ||
| 44 | 48 | <div class="page-head-main"> | |
| 45 | 49 | <h1>{{.Account.Name}}</h1> | |
| 46 | 50 | <p class="page-sub"> | |
| @@ -49,14 +53,8 @@ | |||
| 49 | 53 | · created {{since .Account.Created.Unix}} | |
| 50 | 54 | </p> | |
| 51 | 55 | </div> | |
| 52 | </div> | ||
| 53 | |||
| 54 | {{if .Error}}<div class="flash bad">{{.Error}}</div>{{end}} | ||
| 55 | |||
| 56 | <div class="card form-card"> | ||
| 57 | <h2>Access and enrollment</h2> | ||
| 58 | <div class="form-actions"> | ||
| 59 | <form class="inline" method="post" action="/admin/users/{{.Account.Name}}/admin"> | ||
| 56 | <div class="page-head-actions"> | ||
| 57 | <form class="inline" method="post" action="/users/{{.Account.Name}}/admin"> | ||
| 60 | 58 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | |
| 61 | 59 | {{if .Account.Admin}} | |
| 62 | 60 | <input type="hidden" name="admin" value="false"> | |
| @@ -66,19 +64,15 @@ | |||
| 66 | 64 | <button type="submit" class="secondary">{{icon "user"}}Grant instance admin</button> | |
| 67 | 65 | {{end}} | |
| 68 | 66 | </form> | |
| 69 | <form class="inline" method="post" action="/admin/users/{{.Account.Name}}/invite"> | ||
| 67 | <form class="inline" method="post" action="/users/{{.Account.Name}}/invite"> | ||
| 70 | 68 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | |
| 71 | 69 | <button type="submit" class="secondary">{{icon "external-link"}}New enrollment link</button> | |
| 72 | 70 | </form> | |
| 73 | {{if .Sessions}} | ||
| 74 | <form class="inline" method="post" action="/admin/users/{{.Account.Name}}/sessions/revoke"> | ||
| 75 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | ||
| 76 | <button type="submit" class="warn">{{icon "log-out"}}Revoke all sessions</button> | ||
| 77 | </form> | ||
| 78 | {{end}} | ||
| 79 | 71 | </div> | |
| 80 | 72 | </div> | |
| 81 | 73 | ||
| 74 | {{if .Error}}<div class="flash bad">{{.Error}}</div>{{end}} | ||
| 75 | |||
| 82 | 76 | <h2>SSH keys</h2> | |
| 83 | 77 | {{if .Account.Keys}} | |
| 84 | 78 | <div class="panel"> | |
| @@ -91,7 +85,7 @@ | |||
| 91 | 85 | <td>{{.Type}}</td> | |
| 92 | 86 | <td class="muted">{{.Comment}}</td> | |
| 93 | 87 | <td> | |
| 94 | <form class="inline" method="post" action="/admin/users/{{$.Account.Name}}/keys/remove"> | ||
| 88 | <form class="inline" method="post" action="/users/{{$.Account.Name}}/keys/remove"> | ||
| 95 | 89 | <input type="hidden" name="csrf" value="{{$.CSRF}}"> | |
| 96 | 90 | <input type="hidden" name="fingerprint" value="{{.Fingerprint}}"> | |
| 97 | 91 | <button type="submit" class="danger">Remove</button> | |
| @@ -106,7 +100,15 @@ | |||
| 106 | 100 | <p class="muted">No SSH keys. This account can sign in with a passkey only.</p> | |
| 107 | 101 | {{end}} | |
| 108 | 102 | ||
| 109 | <h2>Sessions</h2> | ||
| 103 | <div class="with-more"> | ||
| 104 | <h2>Sessions</h2> | ||
| 105 | {{if .Sessions}} | ||
| 106 | <form class="inline" method="post" action="/users/{{.Account.Name}}/sessions/revoke"> | ||
| 107 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | ||
| 108 | <button type="submit" class="link">Revoke all sessions</button> | ||
| 109 | </form> | ||
| 110 | {{end}} | ||
| 111 | </div> | ||
| 110 | 112 | {{if .Sessions}} | |
| 111 | 113 | <div class="panel"> | |
| 112 | 114 | <table> | |
| @@ -128,24 +130,23 @@ | |||
| 128 | 130 | {{end}} | |
| 129 | 131 | ||
| 130 | 132 | <h2>Danger zone</h2> | |
| 131 | <div class="card danger-zone"> | ||
| 132 | <form method="post" action="/admin/users/{{.Account.Name}}/retire" class="form-block"> | ||
| 133 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | ||
| 133 | <form method="post" action="/users/{{.Account.Name}}/retire" class="form-block"> | ||
| 134 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | ||
| 135 | <div class="card form-card danger-zone"> | ||
| 134 | 136 | <h3>Retire account</h3> | |
| 135 | 137 | <p class="muted">Disables the account, ends its sessions, and closes any enrollment links. The name is never reissued.</p> | |
| 136 | 138 | <label>Type <code>{{.Account.Name}}</code> to confirm | |
| 137 | 139 | <input type="text" name="confirm" required autocomplete="off"> | |
| 138 | 140 | </label> | |
| 139 | <div class="form-actions"><button type="submit" class="danger">{{icon "circle-x"}}Retire account</button></div> | ||
| 140 | </form> | ||
| 141 | </div> | ||
| 141 | </div> | ||
| 142 | <div class="form-actions"><button type="submit" class="danger">{{icon "circle-x"}}Retire account</button></div> | ||
| 143 | </form> | ||
| 142 | 144 | {{template "footer" .}} | |
| 143 | 145 | {{end}} | |
| 144 | 146 | ||
| 145 | 147 | {{define "admin_invite"}} | |
| 146 | 148 | {{template "header" .}} | |
| 147 | {{template "adminhead" .}} | ||
| 148 | <div class="page-head subhead"> | ||
| 149 | <div class="page-head"> | ||
| 149 | 150 | <div class="page-head-main"> | |
| 150 | 151 | <h1>Enrollment link</h1> | |
| 151 | 152 | <p class="page-sub">For <strong>{{.Account.Name}}</strong> · expires {{.Expires.UTC.Format "2006-01-02 15:04 UTC"}}</p> | |
| @@ -163,7 +164,7 @@ | |||
| 163 | 164 | </div> | |
| 164 | 165 | ||
| 165 | 166 | <div class="form-actions"> | |
| 166 | <a class="button secondary" href="/admin/users/{{.Account.Name}}">{{icon "user"}}Back to account</a> | ||
| 167 | <a class="button secondary" href="/users/{{.Account.Name}}">{{icon "user"}}Back to account</a> | ||
| 167 | 168 | </div> | |
| 168 | 169 | {{template "footer" .}} | |
| 169 | 170 | {{end}} | |