mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Add a Hash dropdown to the add-user and change-password forms — yescrypt (default), SHA-512, SHA-256, MD5 — passed through to mkpasswd. These are exactly the four hashes the infix-system:crypt-hash YANG type accepts; unknown values fall back to the default. Password inputs switch to the CSS-masked type=text used by keystore secrets, which keeps the browser password manager from offering to save device credentials. Each add-user field gets a YANG description as a hover tooltip. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
312 lines
14 KiB
HTML
312 lines
14 KiB
HTML
{{define "configure-users.html"}}
|
|
{{template "base" .}}
|
|
{{end}}
|
|
|
|
{{define "content"}}
|
|
<div class="configure-page">
|
|
{{if .Loading}}
|
|
<div class="alert alert-info"
|
|
hx-get="/configure/users"
|
|
hx-trigger="every 3s"
|
|
hx-target="#content"
|
|
hx-swap="innerHTML">
|
|
Downloading YANG schema from device… this takes a moment on first login.
|
|
</div>
|
|
{{template "configure-toolbar" .}}
|
|
</div>
|
|
{{else}}
|
|
{{if .Error}}
|
|
<div class="alert alert-error">{{.Error}}</div>
|
|
{{end}}
|
|
|
|
<section class="info-grid">
|
|
|
|
{{/* ── Users ───────────────────────────────────────────────────────── */}}
|
|
<section class="info-card info-grid-span-2">
|
|
<div class="card-header">Users</div>
|
|
<div class="data-table-wrap">
|
|
<table class="data-table cfg-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Username</th>
|
|
<th>Shell</th>
|
|
<th style="text-align:center">SSH Keys</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range $i, $u := .Users}}
|
|
<tr>
|
|
<td>
|
|
<button class="key-row-toggle" type="button"
|
|
aria-expanded="false" data-target="user-{{$i}}">
|
|
<span class="key-row-arrow" aria-hidden="true">▶</span>{{.Name}}
|
|
</button>
|
|
</td>
|
|
<td>{{.ShellLabel}}</td>
|
|
<td style="text-align:center">{{.KeyCount}}</td>
|
|
<td style="text-align:right">
|
|
<button type="button" class="btn-icon btn-icon-danger"
|
|
hx-delete="/configure/users/{{.Name}}"
|
|
hx-confirm="Delete user {{.Name}}?"
|
|
hx-swap="none"
|
|
title="Delete user">
|
|
{{template "icon-trash"}}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<tr class="key-detail-row" id="key-detail-user-{{$i}}">
|
|
<td colspan="4" class="key-detail-cell">
|
|
<div class="key-detail-body">
|
|
|
|
{{/* Login shell — match the option's bare .Label against the
|
|
user's effective ShellLabel; .Value carries a module prefix
|
|
the stored shell does not. */}}
|
|
<form hx-post="/configure/users/{{.Name}}/shell" hx-swap="none">
|
|
<div class="section-subtitle">Login Shell</div>
|
|
<select class="cfg-input" name="shell" style="max-width:16rem">
|
|
{{$shell := .ShellLabel}}
|
|
{{range $.ShellOptions}}<option value="{{.Value}}" {{if eq .Label $shell}}selected{{end}}>{{.Label}}{{if .IsDefault}} (default){{end}}</option>{{end}}
|
|
</select>
|
|
<div class="cfg-card-footer" style="padding-left:0">
|
|
<button class="btn btn-primary btn-sm" type="submit">Save</button>
|
|
<span class="cfg-save-status"></span>
|
|
</div>
|
|
</form>
|
|
|
|
{{/* Change password. type=text + CSS mask (cfg-input-mask)
|
|
dodges the browser password-manager save prompt, same as
|
|
the keystore secret fields. */}}
|
|
<form hx-post="/configure/users/{{.Name}}/password" hx-swap="none" autocomplete="off">
|
|
<div class="section-subtitle">Change Password</div>
|
|
<table class="info-table">
|
|
<tr>
|
|
<th>New password{{template "field-info" (index $.Desc "password")}}</th>
|
|
<td>
|
|
<span class="cfg-secret-wrap">
|
|
<input class="cfg-input cfg-input-mask" type="text" name="password"
|
|
id="chpw-{{.Name}}" placeholder="Leave blank to keep current"
|
|
autocomplete="off" spellcheck="false"
|
|
data-1p-ignore="true" data-lpignore="true"
|
|
data-bwignore="true" data-form-type="other">
|
|
<button type="button" class="btn-icon cfg-secret-toggle"
|
|
data-toggle-mask="chpw-{{.Name}}"
|
|
aria-label="Show / hide password">👁</button>
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Hash{{template "field-info" (index $.Desc "crypt")}}</th>
|
|
<td><select class="cfg-input" name="crypt">
|
|
{{range $.CryptMethods}}<option value="{{.Value}}" {{if .Default}}selected{{end}}>{{.Label}}</option>{{end}}
|
|
</select></td>
|
|
</tr>
|
|
</table>
|
|
<div class="cfg-card-footer" style="padding-left:0">
|
|
<button class="btn btn-primary btn-sm" type="submit">Change Password</button>
|
|
<span class="cfg-save-status"></span>
|
|
</div>
|
|
</form>
|
|
|
|
{{/* Authorized SSH keys */}}
|
|
<div>
|
|
<div class="section-subtitle">Authorized SSH Keys ({{.KeyCount}})</div>
|
|
{{if .AuthorizedKeys}}
|
|
<div class="data-table-wrap">
|
|
<table class="data-table cfg-table">
|
|
<thead>
|
|
<tr><th>Name</th><th>Algorithm</th><th></th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range .AuthorizedKeys}}
|
|
<tr>
|
|
<td>{{.Name}}</td>
|
|
<td>{{.Algorithm}}</td>
|
|
<td style="text-align:right">
|
|
<button type="button" class="btn-icon btn-icon-danger"
|
|
hx-delete="/configure/users/{{$u.Name}}/keys/{{.Name}}"
|
|
hx-confirm="Remove key {{.Name}}?"
|
|
hx-target="closest tr"
|
|
hx-swap="delete"
|
|
title="Remove key">
|
|
{{template "icon-trash"}}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{end}}
|
|
<form hx-post="/configure/users/{{.Name}}/keys" hx-swap="none"
|
|
style="margin-top:0.5rem">
|
|
<table class="info-table">
|
|
<tr>
|
|
<th>Key name</th>
|
|
<td><input class="cfg-input" type="text" name="key_name"
|
|
placeholder="e.g. laptop"></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Public key</th>
|
|
<td><textarea class="cfg-input cfg-input-mono" name="key_data" rows="3"
|
|
placeholder="ssh-rsa AAAA… or ssh-ed25519 AAAA…"></textarea></td>
|
|
</tr>
|
|
</table>
|
|
<div class="cfg-card-footer" style="padding-left:0">
|
|
<button class="btn btn-primary btn-sm" type="submit">Add Key</button>
|
|
<span class="cfg-save-status"></span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
|
|
{{/* Hidden add-user row — revealed by the + Add user button. The
|
|
password is a CSS-masked type=text (cfg-input-mask) to dodge the
|
|
browser password-manager save prompt, same as keystore secrets. */}}
|
|
<tr id="add-user-row" hidden>
|
|
<td colspan="4" class="key-detail-cell">
|
|
<div class="key-detail-body">
|
|
<form hx-post="/configure/users" hx-swap="none" autocomplete="off">
|
|
<div class="section-subtitle">Add User</div>
|
|
<table class="info-table">
|
|
<tr>
|
|
<th>Username{{template "field-info" (index $.Desc "username")}}</th>
|
|
<td><input class="cfg-input" type="text" name="username" required autocomplete="off"
|
|
pattern="[a-zA-Z0-9_][a-zA-Z0-9_.-]*" placeholder="Username"></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Password{{template "field-info" (index $.Desc "password")}}</th>
|
|
<td>
|
|
<span class="cfg-secret-wrap">
|
|
<input class="cfg-input cfg-input-mask" type="text" name="password"
|
|
id="add-user-password" required placeholder="Password"
|
|
autocomplete="off" spellcheck="false"
|
|
data-1p-ignore="true" data-lpignore="true"
|
|
data-bwignore="true" data-form-type="other">
|
|
<button type="button" class="btn-icon cfg-secret-toggle"
|
|
data-toggle-mask="add-user-password"
|
|
aria-label="Show / hide password">👁</button>
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Hash{{template "field-info" (index $.Desc "crypt")}}</th>
|
|
<td><select class="cfg-input" name="crypt">
|
|
{{range $.CryptMethods}}<option value="{{.Value}}" {{if .Default}}selected{{end}}>{{.Label}}</option>{{end}}
|
|
</select></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Shell{{template "field-info" (index $.Desc "shell")}}</th>
|
|
<td><select class="cfg-input" name="shell">
|
|
{{range $.ShellOptions}}<option value="{{.Value}}" {{if .IsDefault}}selected{{end}}>{{.Label}}{{if .IsDefault}} (default){{end}}</option>{{end}}
|
|
</select></td>
|
|
</tr>
|
|
</table>
|
|
<div class="cfg-card-footer" style="padding-left:0">
|
|
<button class="btn btn-primary btn-sm" type="submit">Add</button>
|
|
<button type="button" class="btn btn-sm" data-hide="add-user-row">Cancel</button>
|
|
<span class="cfg-save-status"></span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="cfg-card-footer">
|
|
<button type="button" class="btn-add-row" data-show="add-user-row">+ Add user</button>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
{{/* ── Groups ─────────────────────────────────────────────────────── */}}
|
|
<section class="info-card info-grid-span-2">
|
|
<div class="card-header">Groups</div>
|
|
<div class="data-table-wrap">
|
|
<table class="data-table cfg-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Group</th>
|
|
<th>Members</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range $i, $g := .Groups}}
|
|
<tr>
|
|
<td>
|
|
<button class="key-row-toggle" type="button"
|
|
aria-expanded="false" data-target="group-{{$i}}">
|
|
<span class="key-row-arrow" aria-hidden="true">▶</span>{{.Name}}
|
|
</button>
|
|
</td>
|
|
<td>{{.MembersSummary}}</td>
|
|
</tr>
|
|
<tr class="key-detail-row" id="key-detail-group-{{$i}}">
|
|
<td colspan="2" class="key-detail-cell">
|
|
<div class="key-detail-body">
|
|
|
|
<div class="section-subtitle">Members</div>
|
|
{{if .UserNames}}
|
|
<div class="data-table-wrap">
|
|
<table class="data-table cfg-table">
|
|
<thead><tr><th>Username</th><th></th></tr></thead>
|
|
<tbody>
|
|
{{range .UserNames}}
|
|
<tr>
|
|
<td>{{.}}</td>
|
|
<td style="text-align:right">
|
|
<button type="button" class="btn-icon btn-icon-danger"
|
|
hx-delete="/configure/users/groups/{{$g.Name}}/members/{{.}}"
|
|
hx-confirm="Remove {{.}} from group {{$g.Name}}?"
|
|
hx-target="closest tr"
|
|
hx-swap="delete"
|
|
title="Remove from group">
|
|
{{template "icon-trash"}}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{else}}
|
|
<p style="color:var(--text-muted);font-size:0.85rem">No members.</p>
|
|
{{end}}
|
|
|
|
{{if .Available}}
|
|
<form hx-post="/configure/users/groups/{{.Name}}/members" hx-swap="none"
|
|
style="margin-top:0.75rem">
|
|
<div class="section-subtitle">Add Members</div>
|
|
<select class="cfg-input" name="members" multiple size="4">
|
|
{{range .Available}}<option value="{{.}}">{{.}}</option>{{end}}
|
|
</select>
|
|
<div class="cfg-card-footer" style="padding-left:0">
|
|
<button class="btn btn-primary btn-sm" type="submit">Add Selected</button>
|
|
<span class="cfg-save-status"></span>
|
|
</div>
|
|
</form>
|
|
{{end}}
|
|
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
</section>
|
|
|
|
{{template "configure-toolbar" .}}
|
|
</div>
|
|
{{end}}{{/* end {{else}} — schema ready */}}
|
|
|
|
{{end}}
|