Files
warpbox/templates/account_users.html

257 lines
16 KiB
HTML

{{ template "account_shell_start" . }}
<main class="account-window" aria-labelledby="account-users-title">
{{ template "account_window_titlebar" . }}
<nav class="menu-bar" aria-label="Users toolbar">
<div class="menu-item">
<button class="menu-button" type="button" aria-expanded="false">File</button>
<div class="menu-popup" role="menu">
<a class="menu-action" href="/account/users"><span>R</span><span>Refresh list</span><span class="shortcut">F5</span></a>
<div class="menu-separator"></div>
<form action="/account/logout" method="post">
{{ template "account_csrf_field" . }}
<button class="menu-action" type="submit"><span>Q</span><span>Log out</span><span></span></button>
</form>
</div>
</div>
<div class="menu-item">
<button class="menu-button" type="button" aria-expanded="false">View</button>
<div class="menu-popup" role="menu">
<a class="menu-action" href="/account/users?status=active"><span>A</span><span>Show active</span><span></span></a>
<a class="menu-action" href="/account/users?status=disabled"><span>D</span><span>Show disabled</span><span></span></a>
<a class="menu-action" href="/account/users"><span>X</span><span>Clear filters</span><span></span></a>
</div>
</div>
</nav>
<div class="account-body-content">
<section class="dashboard-hero raised-panel" aria-label="Users overview">
<div class="hero-copy">
<h2 id="account-users-title">WarpBox Users</h2>
<p>Accounts, invites, and access. Search, filter, and manage users with safe bulk actions.</p>
</div>
<div class="hero-actions">
<button class="small-action is-primary" type="button" data-users-action="focus-create">Create / Invite</button>
<button class="small-action" type="button" data-users-action="select-visible">Select visible</button>
<button class="small-action" type="button" onclick="location.href='/account/users'">Refresh</button>
</div>
</section>
{{ if .Error }}
<div class="account-error-banner">{{ .Error }}</div>
{{ end }}
{{ if .Success }}
<div class="account-success-banner">{{ .Success }}</div>
{{ end }}
<section class="stats-grid" aria-label="User statistics">
<article class="stat-card sunken-panel is-info">
<p class="stat-label">Total users</p>
<p class="stat-value">{{ .Stats.TotalUsers }}</p>
<p class="stat-note"><span class="stat-note-pill">all</span></p>
</article>
<article class="stat-card sunken-panel is-ok">
<p class="stat-label">Active</p>
<p class="stat-value">{{ .Stats.ActiveUsers }}</p>
<p class="stat-note"><span class="stat-note-pill">enabled</span></p>
</article>
<article class="stat-card sunken-panel is-warning">
<p class="stat-label">Pending invites</p>
<p class="stat-value">{{ .Stats.PendingInvites }}</p>
<p class="stat-note"><span class="stat-note-pill">awaiting setup</span></p>
</article>
<article class="stat-card sunken-panel is-danger">
<p class="stat-label">Disabled</p>
<p class="stat-value">{{ .Stats.DisabledUsers }}</p>
<p class="stat-note"><span class="stat-note-pill">blocked</span></p>
</article>
</section>
<section class="main-grid users-grid" aria-label="Users panel and form">
<aside class="win98-window section-window users-form-window">
<div class="win98-titlebar">
<div class="win98-titlebar-label">
<span class="win98-titlebar-icon">+</span>
<h2>Create or Invite</h2>
</div>
</div>
<div class="section-body sunken-panel">
<form class="form-grid" method="post" action="/account/users">
{{ template "account_csrf_field" . }}
<input type="hidden" name="action" value="create">
<div class="field-row">
<label for="users-mode">Mode</label>
<select class="win98-select" name="mode" id="users-mode">
<option value="create">Create local user</option>
<option value="invite">Send invite</option>
</select>
<div class="field-help">Invite creates a disabled account with a setup link. Create makes an active user immediately.</div>
</div>
<div class="field-row">
<label for="users-username">Username</label>
<input class="win98-input" name="username" id="users-username" required placeholder="username" autocomplete="off">
</div>
<div class="field-row">
<label for="users-email">Email</label>
<input class="win98-input" name="email" id="users-email" type="email" required placeholder="user@example.test" autocomplete="off">
</div>
<div class="field-row">
<label for="users-password">Password</label>
<input class="win98-input" name="password" id="users-password" type="password" autocomplete="new-password" placeholder="Leave empty for auto-generated">
<div class="field-help">If empty, a temporary password will be generated. Never prefill passwords.</div>
</div>
<div class="field-row">
<label for="users-role">Role</label>
<select class="win98-select" name="role" id="users-role">
<option value="all">No tag (default)</option>
{{ range .Tags }}
<option value="{{ .Name }}">{{ .Name }}</option>
{{ end }}
</select>
<div class="field-help">Assign an initial role tag. Permissions are resolved from tag settings.</div>
</div>
<div class="button-row">
<button class="small-action" type="reset">Clear</button>
<button class="small-action is-primary" type="submit">Apply</button>
</div>
</form>
</div>
</aside>
<section class="win98-window section-window span-2 users-table-window">
<div class="win98-titlebar">
<div class="win98-titlebar-label">
<span class="win98-titlebar-icon">U</span>
<h2>Users</h2>
</div>
</div>
<div class="users-filters-bar">
<form class="users-filters-form" method="get" action="/account/users" id="users-filters-form">
<input class="win98-input" name="q" value="{{ .Filters.Query }}" placeholder="Search username or email">
<select class="win98-select" name="status" onchange="this.form.submit()">
<option value="" {{ if eq .Filters.Status "" }}selected{{ end }}>all statuses</option>
<option value="active" {{ if eq .Filters.Status "active" }}selected{{ end }}>active</option>
<option value="disabled" {{ if eq .Filters.Status "disabled" }}selected{{ end }}>disabled</option>
</select>
<select class="win98-select" name="role" onchange="this.form.submit()">
<option value="" {{ if eq .Filters.Role "" }}selected{{ end }}>all roles</option>
{{ range .Tags }}
<option value="{{ .Name }}" {{ if eq $.Filters.Role .Name }}selected{{ end }}>{{ .Name }}</option>
{{ end }}
</select>
<select class="win98-select" name="sort" onchange="this.form.submit()">
<option value="username" {{ if eq .Filters.Sort "username" }}selected{{ end }}>sort username</option>
<option value="createdDesc" {{ if eq .Filters.Sort "createdDesc" }}selected{{ end }}>newest first</option>
</select>
<select class="win98-select" name="page_size" onchange="this.form.submit()">
<option value="12" {{ if eq .Filters.PageSize 12 }}selected{{ end }}>12 rows</option>
<option value="20" {{ if eq .Filters.PageSize 20 }}selected{{ end }}>20 rows</option>
<option value="50" {{ if eq .Filters.PageSize 50 }}selected{{ end }}>50 rows</option>
</select>
<noscript><button class="small-action" type="submit">Filter</button></noscript>
</form>
</div>
<form id="users-bulk-form" method="post" action="/account/users/bulk/disable">
{{ template "account_csrf_field" . }}
<input type="hidden" name="selected_ids" value="" id="bulk-selected-ids">
<div class="users-bulk-strip">
<button class="small-action" type="button" data-users-action="select-visible">Select visible</button>
<button class="small-action" type="submit" data-users-action="bulk-disable" onclick="setBulkAction('/account/users/bulk/disable')">Disable</button>
<button class="small-action" type="submit" data-users-action="bulk-enable" onclick="setBulkAction('/account/users/bulk/enable')">Enable</button>
<button class="small-action" type="submit" data-users-action="bulk-revoke" onclick="setBulkAction('/account/users/bulk/revoke-sessions')">Revoke sessions</button>
<span class="stat-note-pill" id="selected-count">0 selected</span>
</div>
<div class="section-body sunken-panel table-body-panel">
<div class="table-scroll">
<table class="account-table" aria-label="Users">
<thead>
<tr>
<th class="check-cell"><input type="checkbox" id="master-check" aria-label="Select current page"></th>
<th>User</th>
<th>Email</th>
<th>Status</th>
<th>Role</th>
<th>Plan</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{ range .Rows }}
<tr data-user-id="{{ .ID }}">
<td class="check-cell">
<input type="checkbox" class="row-check" value="{{ .ID }}" data-user-id="{{ .ID }}" aria-label="Select {{ .Username }}">
</td>
<td class="user-cell">
<div class="user-main">
<span class="username">{{ .Username }}{{ if .IsCurrent }} <span class="pill is-info">you</span>{{ end }}</span>
<span class="subtle">id: {{ .ID }}</span>
</div>
</td>
<td class="email-cell" title="{{ .Email }}">{{ .Email }}</td>
<td>
{{ if eq .Status "active" }}
<span class="pill is-ok">active</span>
{{ else }}
<span class="pill is-danger">disabled</span>
{{ end }}
</td>
<td><span class="pill is-info">{{ .Role }}</span></td>
<td><span class="pill">{{ .Plan }}</span></td>
<td>{{ .CreatedAt }}</td>
<td class="actions-cell">
<a class="tiny-button" href="/account/users/{{ .ID }}">Edit</a>
{{ if and .IsInvite (not .IsCurrent) }}
<form method="post" action="/account/users/{{ .ID }}/invite/resend" style="display:inline">
{{ template "account_csrf_field" $ }}
<button class="tiny-button" type="submit">Resend invite</button>
</form>
{{ end }}
</td>
</tr>
{{ else }}
<tr><td colspan="8">No users found.</td></tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</form>
<div class="pagination">
<span class="pagination-info">
Page {{ .Page }} of {{ .TotalPages }} &mdash; {{ .Total }} matching user(s)
</span>
<div class="pagination-controls">
{{ if .HasPrev }}
<a class="small-action" href="?q={{ .Filters.Query }}&status={{ .Filters.Status }}&role={{ .Filters.Role }}&sort={{ .Filters.Sort }}&page_size={{ .PageSize }}&page={{ .PrevPage }}">Prev</a>
{{ else }}
<button class="small-action" disabled>Prev</button>
{{ end }}
{{ if .HasNext }}
<a class="small-action" href="?q={{ .Filters.Query }}&status={{ .Filters.Status }}&role={{ .Filters.Role }}&sort={{ .Filters.Sort }}&page_size={{ .PageSize }}&page={{ .NextPage }}">Next</a>
{{ else }}
<button class="small-action" disabled>Next</button>
{{ end }}
</div>
</div>
</section>
</section>
</div>
<footer class="win98-statusbar" aria-label="Users status">
<span>signed in: {{ .AccountNav.Username }}</span>
<span>{{ if .AccountNav.IsAdmin }}admin{{ else }}account{{ end }}</span>
<span>ready</span>
</footer>
</main>
{{ template "account_shell_end" . }}