rastrillo / idear Public

Clone
git clone https://amadan.net/rastrillo/idear

Plain git — no account needed to clone.

Download

Download this file

1{{define "content"}}
2{{$viewer := .Content.Viewer}}
3{{$grantable := .Content.Grantable}}
4<h1>Members</h1>
5
6<table>
7<thead><tr><th>Member</th><th>Role</th><th>Status</th><th>Actions</th></tr></thead>
8<tbody>
9{{range .Content.Members}}
10<tr>
11<td>{{.Email}}{{if .Name}} ({{.Name}}){{end}}</td>
12<td>{{.Role.Title}}</td>
13<td>{{if .Active}}Active{{else}}Removed{{end}}</td>
14<td>
15{{if mayActOn $viewer .}}
16{{/* The role selector is built from Grantable, never from the three
17 role constants. An Admin may grant only Member — idear refuses a
18 grant that is not strictly below the granter's own rank, unless
19 built with PeerGrants — so a selector offering Admin to an Admin
20 would 403 on every submit, and one offering Owner would 403 for
21 everybody including the Owner. Hiding a control is never the
22 enforcement; the store refuses either way. */}}
23{{if $grantable}}
24<form method="post" action="/members/{{.ID}}/role">
25<select name="role" aria-label="Role">
26{{range $grantable}}<option value="{{.}}">{{.Title}}</option>{{end}}
27</select>
28<button type="submit">Set role</button>
29</form>
30{{end}}
31{{if .Active}}
32<form method="post" action="/members/{{.ID}}/remove"><button type="submit">Remove</button></form>
33{{else}}
34<form method="post" action="/members/{{.ID}}/restore"><button type="submit">Restore</button></form>
35{{end}}
36{{end}}
37</td>
38</tr>
39{{end}}
40</tbody>
41</table>
42
43{{if $grantable}}
44<h2>Invite someone</h2>
45<form method="post" action="/members/invitations">
46<label>Email <input type="email" name="email" required></label>
47<select name="role" aria-label="Role">
48{{range $grantable}}<option value="{{.}}">{{.Title}}</option>{{end}}
49</select>
50<button type="submit">Invite</button>
51</form>
52{{end}}
53
54{{with .Content.Invitations}}
55<h2>Pending invitations</h2>
56<ul>
57{{range .}}
58<li>{{.Email}} — {{.Role.Title}}
59<form method="post" action="/members/invitations/{{.ID}}/revoke"><button type="submit">Revoke</button></form>
60</li>
61{{end}}
62</ul>
63{{end}}
64
65{{if .Content.IsOwner}}
66<h2>Transfer ownership</h2>
67{{/* The only path to Owner. idear demotes the outgoing Owner and
68 promotes the incoming one in one transaction, so the instance is
69 never left with two Owners or none — and role=owner is refused on
70 every other route, for every actor. */}}
71<form method="post" action="/members/transfer">
72<select name="member" aria-label="New owner">
73{{range .Content.Members}}{{if and .Active (ne .ID $viewer.ID)}}<option value="{{.ID}}">{{.Email}}</option>{{end}}{{end}}
74</select>
75<button type="submit">Transfer ownership</button>
76</form>
77{{end}}
78{{end}}
79