feat(admin): split-pane user management with list and detail form
This commit is contained in:
parent
56acb3b763
commit
b6e4345316
5 changed files with 215 additions and 161 deletions
|
|
@ -113,5 +113,24 @@ templ adminStyles() {
|
||||||
.password-box{font-family:monospace;background:var(--bg);padding:10px;border-radius:6px;margin:8px 0;font-size:0.85rem;user-select:all;word-break:break-all}
|
.password-box{font-family:monospace;background:var(--bg);padding:10px;border-radius:6px;margin:8px 0;font-size:0.85rem;user-select:all;word-break:break-all}
|
||||||
.password-hint{font-size:0.75rem;color:var(--orange)}
|
.password-hint{font-size:0.75rem;color:var(--orange)}
|
||||||
@media(max-width:640px){.sidebar{display:none}.content{margin-left:0}.stats-grid{grid-template-columns:1fr 1fr}.form-row{grid-template-columns:1fr}}
|
@media(max-width:640px){.sidebar{display:none}.content{margin-left:0}.stats-grid{grid-template-columns:1fr 1fr}.form-row{grid-template-columns:1fr}}
|
||||||
|
|
||||||
|
/* Split pane */
|
||||||
|
.split-pane{display:flex;flex-direction:column;gap:16px;height:calc(100vh - 100px)}
|
||||||
|
.pane-top{flex:1;overflow-y:auto;min-height:200px}
|
||||||
|
.pane-bottom{flex:1;overflow-y:auto;border-top:1px solid var(--border);padding-top:16px}
|
||||||
|
.user-list{background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden}
|
||||||
|
.list-body{max-height:300px;overflow-y:auto}
|
||||||
|
.user-row{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid var(--border);cursor:pointer;transition:background 0.1s}
|
||||||
|
.user-row:hover{background:var(--surface2)}
|
||||||
|
.user-row:last-child{border-bottom:none}
|
||||||
|
.user-row-left{display:flex;align-items:center;gap:10px}
|
||||||
|
.user-avatar{width:32px;height:32px;border-radius:50%;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:0.8rem;font-weight:600;color:var(--text2)}
|
||||||
|
.user-sub{font-size:0.75rem;color:var(--text2);margin-top:1px}
|
||||||
|
.user-row-right{display:flex;align-items:center;gap:8px}
|
||||||
|
.empty-row{padding:20px;text-align:center;color:var(--text2)}
|
||||||
|
.loading{padding:20px;text-align:center;color:var(--text2)}
|
||||||
|
.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;color:var(--text2);gap:10px}
|
||||||
|
.empty-state p{font-size:0.85rem}
|
||||||
|
.detail-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px}
|
||||||
</style>
|
</style>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,63 +1,66 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
templ UserDashboard() {
|
templ UserDashboard() {
|
||||||
|
@Layout("users") {
|
||||||
|
<div class="split-pane">
|
||||||
|
<div class="pane-top">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>User Management</h2>
|
<h2>Users</h2>
|
||||||
<button class="btn" hx-get="/admin/users/create-form" hx-target="#form-container" hx-swap="innerHTML">+ Add User</button>
|
<button class="btn" hx-get="/admin/users/create-form" hx-target="#user-detail" hx-swap="innerHTML">+ Add</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="user-list">
|
||||||
<div id="form-container" style="margin-bottom:16px"></div>
|
|
||||||
|
|
||||||
<div class="user-table-wrap">
|
|
||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<span class="table-title">All Users</span>
|
<span class="count">{ 0 } users</span>
|
||||||
<button class="btn-sm" hx-get="/admin/api/users" hx-target="#user-table-body" hx-swap="innerHTML" hx-trigger="load, click">Refresh</button>
|
<button class="btn-sm" hx-get="/admin/users/list" hx-target="#user-list-body" hx-swap="innerHTML">Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<div class="list-body" id="user-list-body" hx-get="/admin/users/list" hx-trigger="load" hx-swap="innerHTML">
|
||||||
<thead><tr>
|
<div class="loading">Loading users...</div>
|
||||||
<th>Username</th><th>Name</th><th>Email</th><th>Role</th><th>Status</th><th></th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody id="user-table-body" hx-get="/admin/api/users" hx-trigger="load" hx-swap="innerHTML">
|
|
||||||
<tr><td colspan="6" class="loading-row">Loading users...</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pane-bottom" id="user-detail">
|
||||||
|
<div class="empty-state">
|
||||||
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||||
|
<p>Select a user or click + Add</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ UserRows(users []UserRow) {
|
templ UserList(users []UserRow) {
|
||||||
for _, u := range users {
|
if len(users) == 0 {
|
||||||
<tr>
|
<div class="empty-row">No users found</div>
|
||||||
<td><strong>{ u.Username }</strong></td>
|
|
||||||
<td>{ u.DisplayName }</td>
|
|
||||||
<td>{ u.Email }</td>
|
|
||||||
<td><span class="role-tag">{ u.Role }</span></td>
|
|
||||||
<td>
|
|
||||||
if u.Disabled {
|
|
||||||
<span class="status-dot off"></span> Disabled
|
|
||||||
} else {
|
} else {
|
||||||
<span class="status-dot on"></span> Active
|
for _, u := range users {
|
||||||
|
<div class="user-row" onclick="selectUser('{ u.Username }')">
|
||||||
|
<div class="user-row-left">
|
||||||
|
<div class="user-avatar">{ initials(u.DisplayName) }</div>
|
||||||
|
<div>
|
||||||
|
<strong>{ u.Username }</strong>
|
||||||
|
<div class="user-sub">{ u.Email }</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-row-right">
|
||||||
|
<span class="role-tag">{ u.Role }</span>
|
||||||
|
if u.Disabled {
|
||||||
|
<span class="status-dot off"></span>
|
||||||
|
} else {
|
||||||
|
<span class="status-dot on"></span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="btn-sm danger"
|
|
||||||
hx-delete={ "/admin/api/users/" + u.Username }
|
|
||||||
hx-confirm={ "Delete " + u.Username + "?" }
|
|
||||||
hx-target="closest tr"
|
|
||||||
hx-swap="delete">Delete</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ CreateUserForm() {
|
templ CreateUserForm() {
|
||||||
<div class="form-card" id="create-form">
|
<div class="detail-card">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h3>New User</h3>
|
<h3>New User</h3>
|
||||||
<button class="btn-sm"
|
<button class="btn-sm" hx-get="/admin/users/cancel-form" hx-target="#user-detail" hx-swap="innerHTML">Cancel</button>
|
||||||
hx-get="/admin/users/cancel-form" hx-target="#form-container" hx-swap="innerHTML">Cancel</button>
|
|
||||||
</div>
|
</div>
|
||||||
<form hx-post="/admin/api/users" hx-target="#form-container" hx-swap="innerHTML">
|
<form hx-post="/admin/users/create" hx-target="#user-detail" hx-swap="innerHTML">
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Username *</label>
|
<label>Username *</label>
|
||||||
<input type="text" name="username" class="input" required placeholder="jdoe"/>
|
<input type="text" name="username" class="input" required placeholder="jdoe"/>
|
||||||
|
|
@ -66,18 +69,24 @@ templ CreateUserForm() {
|
||||||
<label>Display Name</label>
|
<label>Display Name</label>
|
||||||
<input type="text" name="display_name" class="input" placeholder="John Doe"/>
|
<input type="text" name="display_name" class="input" placeholder="John Doe"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Email</label>
|
<label>Email</label>
|
||||||
<input type="email" name="email" class="input" placeholder="john@example.com"/>
|
<input type="email" name="email" class="input" placeholder="john@example.com"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Role</label>
|
||||||
|
<select name="role" class="input">
|
||||||
|
<option value="user">User</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Groups</label>
|
<label>Groups</label>
|
||||||
<input type="text" name="groups" class="input" placeholder="admins,users"/>
|
<input type="text" name="groups" class="input" placeholder="admins,users"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn" style="margin-top:8px;width:100%">Create User</button>
|
<button type="submit" class="btn" style="width:100%;margin-top:8px">Create User</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
@ -90,15 +99,20 @@ templ CreateUserSuccess(results []CreateUserResultRow) {
|
||||||
<div class="alert success">
|
<div class="alert success">
|
||||||
<strong>{ r.Username }</strong> created
|
<strong>{ r.Username }</strong> created
|
||||||
<div class="password-box">{ r.GeneratedPassword }</div>
|
<div class="password-box">{ r.GeneratedPassword }</div>
|
||||||
<div class="password-hint">Save this — it won't be shown again</div>
|
<div class="password-hint">Save this password — it won't be shown again</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
setTimeout(function() { htmx.trigger('#user-table-body', 'click'); }, 500);
|
setTimeout(function() { htmx.trigger('#user-list-body', 'click'); }, 500);
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initials(s string) string {
|
||||||
|
if len(s) == 0 { return "?" }
|
||||||
|
return string(s[0])
|
||||||
|
}
|
||||||
|
|
||||||
type UserRow struct {
|
type UserRow struct {
|
||||||
Username string
|
Username string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
|
|
@ -106,6 +120,7 @@ type UserRow struct {
|
||||||
Role string
|
Role string
|
||||||
Groups string
|
Groups string
|
||||||
Disabled bool
|
Disabled bool
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
type CreateUserResultRow struct {
|
type CreateUserResultRow struct {
|
||||||
Username string
|
Username string
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,38 @@ func UserDashboard() templ.Component {
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"section-header\"><h2>User Management</h2><button class=\"btn\" hx-get=\"/admin/users/create-form\" hx-target=\"#form-container\" hx-swap=\"innerHTML\">+ Add User</button></div><div id=\"form-container\" style=\"margin-bottom:16px\"></div><div class=\"user-table-wrap\"><div class=\"table-toolbar\"><span class=\"table-title\">All Users</span> <button class=\"btn-sm\" hx-get=\"/admin/api/users\" hx-target=\"#user-table-body\" hx-swap=\"innerHTML\" hx-trigger=\"load, click\">Refresh</button></div><table><thead><tr><th>Username</th><th>Name</th><th>Email</th><th>Role</th><th>Status</th><th></th></tr></thead> <tbody id=\"user-table-body\" hx-get=\"/admin/api/users\" hx-trigger=\"load\" hx-swap=\"innerHTML\"><tr><td colspan=\"6\" class=\"loading-row\">Loading users...</td></tr></tbody></table></div>")
|
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"split-pane\"><div class=\"pane-top\"><div class=\"section-header\"><h2>Users</h2><button class=\"btn\" hx-get=\"/admin/users/create-form\" hx-target=\"#user-detail\" hx-swap=\"innerHTML\">+ Add</button></div><div class=\"user-list\"><div class=\"table-toolbar\"><span class=\"count\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(0)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 13, Col: 29}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " users</span> <button class=\"btn-sm\" hx-get=\"/admin/users/list\" hx-target=\"#user-list-body\" hx-swap=\"innerHTML\">Refresh</button></div><div class=\"list-body\" id=\"user-list-body\" hx-get=\"/admin/users/list\" hx-trigger=\"load\" hx-swap=\"innerHTML\"><div class=\"loading\">Loading users...</div></div></div></div><div class=\"pane-bottom\" id=\"user-detail\"><div class=\"empty-state\"><svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2\"></path><circle cx=\"12\" cy=\"7\" r=\"4\"></circle></svg><p>Select a user or click + Add</p></div></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = Layout("users").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +68,7 @@ func UserDashboard() templ.Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserRows(users []UserRow) templ.Component {
|
func UserList(users []UserRow) templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
|
@ -53,109 +84,90 @@ func UserRows(users []UserRow) templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var2 == nil {
|
if templ_7745c5c3_Var4 == nil {
|
||||||
templ_7745c5c3_Var2 = templ.NopComponent
|
templ_7745c5c3_Var4 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
if len(users) == 0 {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"empty-row\">No users found</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<tr><td><strong>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"user-row\" onclick=\"selectUser('{ u.Username }')\"><div class=\"user-row-left\"><div class=\"user-avatar\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 30, Col: 27}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</strong></td><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(u.DisplayName)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 31, Col: 22}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</td><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var5 string
|
var templ_7745c5c3_Var5 string
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email)
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(initials(u.DisplayName))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 32, Col: 16}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 38, Col: 55}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</td><td><span class=\"role-tag\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div><div><strong>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var6 string
|
var templ_7745c5c3_Var6 string
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(u.Role)
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 33, Col: 38}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 40, Col: 26}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span></td><td>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</strong><div class=\"user-sub\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if u.Disabled {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span class=\"status-dot off\"></span> Disabled")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span class=\"status-dot on\"></span> Active")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</td><td><button class=\"btn-sm danger\" hx-delete=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var7 string
|
var templ_7745c5c3_Var7 string
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue("/admin/api/users/" + u.Username)
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(u.Email)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 43, Col: 49}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 41, Col: 37}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" hx-confirm=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div></div></div><div class=\"user-row-right\"><span class=\"role-tag\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var8 string
|
var templ_7745c5c3_Var8 string
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue("Delete " + u.Username + "?")
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(u.Role)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 44, Col: 46}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 45, Col: 36}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" hx-target=\"closest tr\" hx-swap=\"delete\">Delete</button></td></tr>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
if u.Disabled {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<span class=\"status-dot off\"></span>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<span class=\"status-dot on\"></span>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
@ -182,7 +194,7 @@ func CreateUserForm() templ.Component {
|
||||||
templ_7745c5c3_Var9 = templ.NopComponent
|
templ_7745c5c3_Var9 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"form-card\" id=\"create-form\"><div class=\"section-header\"><h3>New User</h3><button class=\"btn-sm\" hx-get=\"/admin/users/cancel-form\" hx-target=\"#form-container\" hx-swap=\"innerHTML\">Cancel</button></div><form hx-post=\"/admin/api/users\" hx-target=\"#form-container\" hx-swap=\"innerHTML\"><div class=\"form-row\"><div class=\"form-group\"><label>Username *</label> <input type=\"text\" name=\"username\" class=\"input\" required placeholder=\"jdoe\"></div><div class=\"form-group\"><label>Display Name</label> <input type=\"text\" name=\"display_name\" class=\"input\" placeholder=\"John Doe\"></div></div><div class=\"form-row\"><div class=\"form-group\"><label>Email</label> <input type=\"email\" name=\"email\" class=\"input\" placeholder=\"john@example.com\"></div><div class=\"form-group\"><label>Groups</label> <input type=\"text\" name=\"groups\" class=\"input\" placeholder=\"admins,users\"></div></div><button type=\"submit\" class=\"btn\" style=\"margin-top:8px;width:100%\">Create User</button></form></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"detail-card\"><div class=\"section-header\"><h3>New User</h3><button class=\"btn-sm\" hx-get=\"/admin/users/cancel-form\" hx-target=\"#user-detail\" hx-swap=\"innerHTML\">Cancel</button></div><form hx-post=\"/admin/users/create\" hx-target=\"#user-detail\" hx-swap=\"innerHTML\"><div class=\"form-group\"><label>Username *</label> <input type=\"text\" name=\"username\" class=\"input\" required placeholder=\"jdoe\"></div><div class=\"form-group\"><label>Display Name</label> <input type=\"text\" name=\"display_name\" class=\"input\" placeholder=\"John Doe\"></div><div class=\"form-group\"><label>Email</label> <input type=\"email\" name=\"email\" class=\"input\" placeholder=\"john@example.com\"></div><div class=\"form-row\"><div class=\"form-group\"><label>Role</label> <select name=\"role\" class=\"input\"><option value=\"user\">User</option> <option value=\"admin\">Admin</option></select></div><div class=\"form-group\"><label>Groups</label> <input type=\"text\" name=\"groups\" class=\"input\" placeholder=\"admins,users\"></div></div><button type=\"submit\" class=\"btn\" style=\"width:100%;margin-top:8px\">Create User</button></form></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +232,7 @@ func CreateUserSuccess(results []CreateUserResultRow) templ.Component {
|
||||||
var templ_7745c5c3_Var11 string
|
var templ_7745c5c3_Var11 string
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(r.Error)
|
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(r.Error)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 88, Col: 37}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 97, Col: 37}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|
@ -238,7 +250,7 @@ func CreateUserSuccess(results []CreateUserResultRow) templ.Component {
|
||||||
var templ_7745c5c3_Var12 string
|
var templ_7745c5c3_Var12 string
|
||||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(r.Username)
|
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(r.Username)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 91, Col: 24}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 100, Col: 24}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|
@ -251,19 +263,19 @@ func CreateUserSuccess(results []CreateUserResultRow) templ.Component {
|
||||||
var templ_7745c5c3_Var13 string
|
var templ_7745c5c3_Var13 string
|
||||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(r.GeneratedPassword)
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(r.GeneratedPassword)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 92, Col: 51}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 101, Col: 51}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div><div class=\"password-hint\">Save this — it won't be shown again</div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div><div class=\"password-hint\">Save this password — it won't be shown again</div></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<script type=\"text/javascript\">\n\t\tsetTimeout(function() { htmx.trigger('#user-table-body', 'click'); }, 500);\n\t</script>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<script type=\"text/javascript\">\n\t\tsetTimeout(function() { htmx.trigger('#user-list-body', 'click'); }, 500);\n\t</script>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -271,6 +283,13 @@ func CreateUserSuccess(results []CreateUserResultRow) templ.Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initials(s string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return "?"
|
||||||
|
}
|
||||||
|
return string(s[0])
|
||||||
|
}
|
||||||
|
|
||||||
type UserRow struct {
|
type UserRow struct {
|
||||||
Username string
|
Username string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
|
|
@ -278,6 +297,7 @@ type UserRow struct {
|
||||||
Role string
|
Role string
|
||||||
Groups string
|
Groups string
|
||||||
Disabled bool
|
Disabled bool
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
type CreateUserResultRow struct {
|
type CreateUserResultRow struct {
|
||||||
Username string
|
Username string
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ func (h *Handler) userRowsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
rows = append(rows, UserToRow(u))
|
rows = append(rows, UserToRow(u))
|
||||||
}
|
}
|
||||||
|
|
||||||
component := templates.UserRows(rows)
|
component := templates.UserList(rows)
|
||||||
component.Render(r.Context(), w)
|
component.Render(r.Context(), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue