package server import ( "html/template" "net/http" "cc.tiredsysadmin.keeporg/internal/store" ) // The administration pages. Each control is its own small form carrying the // session's CSRF token and one action, which is why there is no JavaScript // here and no way to act on a page you were not served. var groupsTmpl = template.Must(template.Must(pages.Clone()).Parse(` {{define "body"}}
Nested groups decide who reaches which vault.
{{if .Error}}{{.Error}}
{{end}} {{if .Notice}}{{.Notice}}
{{end}} {{if .Groups}}| Group | People | Vaults | |
|---|---|---|---|
| {{.Name}} | {{.Members}} | {{.Owns}} | {{if .Admin}}you administer{{end}} |
There are no groups you can see.
{{end}} {{if or .Parents .ServerAdmin}} {{end}} {{end}}`)) var groupTmpl = template.Must(template.Must(pages.Clone()).Parse(` {{define "body"}} {{with .G}}{{if .Parent}}Inside {{.Parent}}. {{end}}All groups
{{end}} {{if .Error}}{{.Error}}
{{end}} {{if .Notice}}{{.Notice}}
{{end}} {{with .G}}| Who | Role | |
|---|---|---|
| {{.Username}}{{if .Disabled}} disabled{{end}} | {{.Role}} | {{if $.G.Admin}} {{end}} |
Nobody is in this group yet.
{{end}} {{if .Admin}} {{end}}| Vault | Access | Where from | |
|---|---|---|---|
| {{.Name}}.kdbx | {{.Perm}} | {{if .Owned}}owns it{{else if .Via}}granted to {{.Via}}{{else}}granted here{{end}} | {{if and $.G.Admin (not .Owned) (not .Via)}} {{end}} |
This group reaches no vaults.
{{end}} {{if and .Admin .Vaults}} {{end}} {{if .Children}}| {{.Name}} | {{.Members}} people | {{.Owns}} vaults |
A group can only go once it has no members, no child groups and owns no vaults. Deleting it also drops the grants that hang on it.
{{if .ServerAdmin}}Every account on this server.{{else}}The people in the groups you administer.{{end}}
{{if .Error}}{{.Error}}
{{end}} {{if .Notice}}{{.Notice}}
{{end}}| Who | Signs in with | Groups | {{if .ServerAdmin}}{{end}} |
|---|---|---|---|
| {{.Username}}
{{if .ServerAdmin}}server admin{{end}}
{{if .Disabled}}disabled{{end}}
{{if .Email}} {{.Email}}{{end}} |
{{.SignIn}}{{if .TOTP}} one-time code{{end}} |
{{if .Groups}}{{.Groups}}{{else}}none{{end}} | {{if $.ServerAdmin}}{{if .Disabled}} {{else if not .Self}} {{end}} {{if .TOTP}} {{end}} {{if not .Self}} {{end}} | {{end}}
Disabling an account signs it out everywhere and keeps its history; deleting it removes the account and its group memberships. Clearing the second factor is what you do when somebody loses their phone — they can sign in with their password alone until they enrol a new one.
{{else}}Only a server administrator can change an account. You decide who is in your groups, from each group's page.
{{end}} {{end}}`)) var invitesTmpl = template.Must(template.Must(pages.Clone()).Parse(` {{define "body"}}An invite link lets somebody create their own account and lands them in one group.
{{if .Error}}{{.Error}}
{{end}} {{if .Notice}}{{.Notice}}
{{end}} {{if .Invites}}| Link | Joins | Used | Expires | |
|---|---|---|---|---|
| {{.Group}} as {{.Role}} |
{{.Uses}} of {{.Max}} | {{.Expires}}{{if .Expired}} spent or expired{{end}} {{if .By}} by {{.By}}{{end}} |
No invites outstanding.
{{end}} {{if or .Groups .ServerAdmin}} {{end}} {{end}}`)) func (s *Server) renderGroups(w http.ResponseWriter, sess *webSession, errMsg, notice string) { d := s.shell(sess, "Groups", "Groups") s.st.Read(func(st *store.State) { d["Groups"] = groupTree(st, sess.user) d["Parents"] = administeredGroups(st, sess.user) }) d["Error"], d["Notice"] = errMsg, notice render(w, groupsTmpl, d) } func (s *Server) renderGroup(w http.ResponseWriter, sess *webSession, id, errMsg, notice string) { d := s.shell(sess, "Group", "Groups") var detail *webGroupDetail s.st.Read(func(st *store.State) { detail = groupDetail(st, sess.user, id) }) if detail == nil { s.renderGroups(w, sess, "There is no such group, or it is not yours to see.", "") return } d["G"], d["Error"], d["Notice"] = detail, errMsg, notice render(w, groupTmpl, d) } func (s *Server) renderUsers(w http.ResponseWriter, sess *webSession, errMsg, notice string) { d := s.shell(sess, "People", "People") s.st.Read(func(st *store.State) { d["Users"] = userRows(st, sess.user) }) d["Error"], d["Notice"] = errMsg, notice render(w, usersTmpl, d) } func (s *Server) renderInvites(w http.ResponseWriter, sess *webSession, errMsg, notice string) { d := s.shell(sess, "Invites", "Invites") s.st.Read(func(st *store.State) { d["Invites"] = s.inviteRows(st, sess.user) d["Groups"] = administeredGroups(st, sess.user) }) d["Error"], d["Notice"] = errMsg, notice render(w, invitesTmpl, d) }