{{define "title"}}Users · MeshTender{{end}} {{define "header"}}
Administration

Users & capabilities

{{end}} {{/* user-rows renders just the list rows; shared by the full page and the htmx "load more" fragment so appended pages match the first. The Actions dropdown loads the Permissions/History fragments into the shared #user-modal. */}} {{define "user-rows"}} {{range .Users}}
{{.Name}} @{{.Username}} {{if eq .ID $.Self}}you{{end}} {{if .CapManageUsers}}manage users{{end}} {{if .CapManageCatalog}}manage catalog{{end}}
Joined {{ts .CreatedAt "date"}} · Last login: {{if .LastLoginAt}}{{ts .LastLoginAt "datetime"}}{{else}}never{{end}}
{{end}} {{end}} {{/* user-more is the "load more" control; it replaces itself with the next page's rows + a fresh control. The cursor already carries sort, search, and filter. */}} {{define "user-more"}}
{{end}} {{/* users-frag is the htmx response: rows + next control, no page chrome. */}} {{define "users-frag"}}{{template "user-rows" .}}{{if .NextCursor}}{{template "user-more" .}}{{end}}{{end}} {{define "content"}} {{if .Error}}
{{.Error}}
{{end}}
{{/* Search/filters come FIRST in the DOM and move to the right of the list at lg (order-lg-last). Source order governs both the mobile stack and the tab order, so ordering with CSS alone would leave keyboard and screen-reader users traversing every row to reach the control that exists to avoid exactly that. */}}
{{if .Users}}
{{template "user-rows" .}} {{if .NextCursor}}{{template "user-more" .}}{{end}}
{{else}}

{{if .Query}}No users match “{{.Query}}”.{{else}}No users match this filter.{{end}}

{{end}}
{{template "icon-arrow-left" "me-1"}}Admin {{end}}