{{define "admin_users"}} People · llmgw {{/* Forms here post and get the whole page back, which works with no script at all. htmx, when it loads, swaps only #people from that page instead of reloading it, so an open project stays open and the page does not jump. */}}

People

{{len .Pending}} waiting for approval · {{len .Accounts}} accounts · {{len .Projects}} projects

{{template "nav" .Nav}}
{{if .Error}}{{end}} {{if .Message}}

{{.Message}}

{{end}} {{if .Pending}}

Waiting for approval

{{range .Pending}}{{end}}
EmailNameSigned upApprove, and put them in
{{.Email}}{{.Name}}{{ago .CreatedAt}}
{{end}}

Organizations, teams and projects

A person is in a project, a project in a team, a team in an organization{{if .HasQuotas}}, and quotas can be set on any of them{{end}}. Open a project to see its people and keys.

{{if not .Tree}}

No organizations yet. Create one, then a team in it, then a project in the team — an account can only be approved into a project.

{{end}} {{range $org := .Tree}}
{{$org.Name}} {{len $org.Teams}} team{{if ne (len $org.Teams) 1}}s{{end}} {{template "scope-members" (dict "Kind" "org" "ID" $org.ID "Name" $org.Name "D" $)}}
rename
{{if not $org.Teams}}
{{end}}
{{range $team := $org.Teams}}
{{$team.Name}} {{len $team.Projects}} project{{if ne (len $team.Projects) 1}}s{{end}} {{template "scope-members" (dict "Kind" "team" "ID" $team.ID "Name" $team.Name "D" $)}}
rename
{{if gt (len $.Orgs) 1}}
move
{{end}} {{if not $team.Projects}}
{{end}}
{{range $p := $team.Projects}}
{{$p.Name}} {{$p.Users}} member{{if ne $p.Users 1}}s{{end}} · {{$p.ActiveKeys}} key{{if ne $p.ActiveKeys 1}}s{{end}}{{if gt $p.Keys $p.ActiveKeys}} ({{sub $p.Keys $p.ActiveKeys}} revoked){{end}}
rename
{{if gt (len $.Teams) 1}}
move to another team
{{end}} {{if and (eq $p.Users 0) (eq $p.Keys 0)}}
{{else}}Removable once it has no people and never had a key.{{end}}
{{with index $.Members (printf "project:%s" $p.ID)}} {{range .}}{{template "member-row" (dict "U" . "D" $ "Scope" (printf "project:%s" $p.ID) "Open" $p.ID)}}{{end}}
MemberStateLast sign-in
{{else}}

No members of its own. Members of its team or organization can use it too.

{{end}} {{template "add-member" (dict "Kind" "project" "ID" $p.ID "D" $ "Open" $p.ID)}} {{with index $.Keys $p.ID}} {{range .}}{{end}}
KeyOwnerStateLast used
{{.Name}} {{.Prefix}} {{or .OwnerEmail "issued by an operator"}} {{.State}} {{ago .LastUsedAt}} {{if eq .State "active"}}
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}

All accounts

{{if .Accounts}} {{range .Accounts}}{{template "account-row" (dict "U" . "D" $)}}{{end}}
PersonStateMember ofLast sign-inAdd to

Someone who is a member of nothing uses the gateway on their own: personal keys and the chat page, within their personal limits.

{{else}}

No accounts yet.

{{end}}

Not here on purpose: making someone an admin, resetting a password and issuing a key for someone else. Each turns a stolen admin session into more access than it had, so they need a shell on the control plane — llmgw users promote, llmgw users reset-password, llmgw keys create.

{{end}} {{/* The actions every account row carries: suspend or reinstate, remove. */}} {{define "state-actions"}}{{$u := .U}}{{$d := .D}} {{if ne $u.ID $d.AdminID}} {{if eq (printf "%s" $u.State) "active"}}
{{else}}
{{end}}
{{else}}you{{end}} {{end}} {{/* A person in the list of every account: where they are, and where else they could be. */}} {{define "account-row"}}{{$u := .U}}{{$d := .D}} {{$u.Email}}{{if $u.IsAdmin}} admin{{end}} {{$u.State}} {{range $u.Memberships}}{{.Path}}{{if eq .Role "manager"}} manager{{end}} {{if eq (printf "%s" .Scope) "org"}}
{{end}}
{{else}}on their own{{end}} {{ago $u.LastLoginAt}}
{{template "state-actions" (dict "U" $u "D" $d "Open" "")}} {{end}} {{/* A member in a scope's own list: taking them out of here is the action. */}} {{define "member-row"}}{{$u := .U}}{{$d := .D}} {{$u.Email}}{{if $u.IsAdmin}} admin{{end}} {{if gt (len $u.Memberships) 1}}also in {{sub (len $u.Memberships) 1}} more{{end}} {{$u.State}} {{ago $u.LastLoginAt}}
{{end}} {{/* Adding a person to a scope, from the scope. */}} {{define "add-member"}}{{$d := .D}}
{{end}} {{/* An org's or team's own members, folded away until asked for. */}} {{define "scope-members"}}{{$d := .D}}{{$k := printf "%s:%s" .Kind .ID}}
{{len (index $d.Members $k)}} member{{if ne (len (index $d.Members $k)) 1}}s{{end}}
{{with index $d.Members $k}} {{range .}}{{template "member-row" (dict "U" . "D" $d "Scope" $k "Open" "")}}{{end}}
{{else}}

Nobody is a member of {{.Name}} itself; a member here could use every project in it.

{{end}} {{template "add-member" (dict "Kind" .Kind "ID" .ID "D" $d "Open" "")}}
{{end}}