Files
MeshTender/internal/core/templates/admin_users.html
T

121 lines
5.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "title"}}Users · MeshTender{{end}}
{{define "header"}}
<div class="row g-2 align-items-center">
<div class="col">
<div class="page-pretitle">Administration</div>
<h1 class="page-title fs-1">Users &amp; capabilities</h1>
</div>
</div>
{{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}}
<div class="list-group-item d-flex align-items-center flex-wrap gap-2" data-testid="user-row">
<div class="flex-fill">
<div>
<span class="fw-bold">{{.Name}}</span> <span class="text-secondary">@{{.Username}}</span>
{{if eq .ID $.Self}}<span class="badge bg-azure-lt">you</span>{{end}}
{{if .CapManageUsers}}<span class="badge bg-purple-lt">manage users</span>{{end}}
{{if .CapManageCatalog}}<span class="badge bg-teal-lt">manage catalog</span>{{end}}
</div>
<div class="text-secondary small">
Joined {{ts .CreatedAt "date"}} · Last login: {{if .LastLoginAt}}{{ts .LastLoginAt "datetime"}}{{else}}never{{end}}
</div>
</div>
<div class="dropdown">
<button class="btn btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" data-testid="user-actions">Actions</button>
<div class="dropdown-menu dropdown-menu-end">
<button type="button" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#user-modal" data-testid="user-permissions"
hx-get="/admin/users/{{.ID}}/permissions" hx-target="#user-modal-content" hx-swap="innerHTML">
Permissions
</button>
<button type="button" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#user-modal" data-testid="user-history"
hx-get="/admin/users/{{.ID}}/history" hx-target="#user-modal-content" hx-swap="innerHTML">
History
</button>
</div>
</div>
</div>
{{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"}}
<div class="list-group-item text-center" id="user-more">
<button class="btn btn-outline-secondary" hx-get="/admin/users?cursor={{.NextCursor}}" hx-target="#user-more" hx-swap="outerHTML">
Show more users
</button>
</div>
{{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}}<div class="alert alert-danger">{{.Error}}</div>{{end}}
<div class="row row-cards">
{{/* 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. */}}
<div class="col-lg-3 order-lg-last">
<div class="card">
<div class="card-body">
<form method="get" action="/admin/users">
<div class="mb-3">
<label class="form-label" for="user-q">Search</label>
<input type="search" id="user-q" class="form-control" name="q" value="{{.Query}}"
placeholder="Username or display name" aria-label="Search users">
</div>
<div class="mb-3">
<label class="form-label" for="user-sort">Sort by</label>
<select id="user-sort" class="form-select" name="sort" aria-label="Sort users" data-autosubmit>
<option value="name"{{if eq .Sort "name"}} selected{{end}}>Username (AZ)</option>
<option value="last_login"{{if eq .Sort "last_login"}} selected{{end}}>Last login</option>
<option value="newest"{{if eq .Sort "newest"}} selected{{end}}>Newest</option>
</select>
</div>
<div class="mb-3">
<label class="form-label" for="user-cap">Capability</label>
<select id="user-cap" class="form-select" name="cap" aria-label="Filter by capability" data-autosubmit>
<option value=""{{if eq .Cap ""}} selected{{end}}>All users</option>
<option value="managers"{{if eq .Cap "managers"}} selected{{end}}>User managers</option>
<option value="catalog"{{if eq .Cap "catalog"}} selected{{end}}>Catalog managers</option>
<option value="none"{{if eq .Cap "none"}} selected{{end}}>No capabilities</option>
</select>
</div>
<button class="btn btn-primary w-100" type="submit">{{template "icon-search" "me-1"}}Search</button>
</form>
</div>
</div>
</div>
<!-- The user list -->
<div class="col-lg-9">
<div class="card">
{{if .Users}}
<div class="list-group list-group-flush" id="user-list">
{{template "user-rows" .}}
{{if .NextCursor}}{{template "user-more" .}}{{end}}
</div>
{{else}}
<div class="card-body"><p class="text-secondary mb-0">{{if .Query}}No users match “{{.Query}}”.{{else}}No users match this filter.{{end}}</p></div>
{{end}}
</div>
</div>
</div>
<!-- Shared modal; the Actions dropdown loads Permissions/History into it via htmx. -->
<div class="modal modal-blur fade" id="user-modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content" id="user-modal-content"></div>
</div>
</div>
<a class="back-link mt-3" href="/admin">{{template "icon-arrow-left" "me-1"}}Admin</a>
{{end}}