feat(admin): left sidebar layout, dark theme, role column
This commit is contained in:
parent
863b87ffb7
commit
da575701e4
7 changed files with 318 additions and 407 deletions
|
|
@ -1,55 +1,20 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
templ Dashboard(userCount int) {
|
templ Dashboard(userCount int) {
|
||||||
<!DOCTYPE html>
|
@Layout("dashboard") {
|
||||||
<html lang="en">
|
<div class="stats-grid">
|
||||||
<head>
|
<div class="stat-card">
|
||||||
<meta charset="UTF-8"/>
|
<div class="stat-value">{ userCount }</div>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<div class="stat-label">Users</div>
|
||||||
<title>Admin Dashboard - Next Workspace</title>
|
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4/dist/ext/response-targets.js"></script>
|
|
||||||
<style>{ adminStyles() }</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="app-container">
|
|
||||||
<nav class="sidebar">
|
|
||||||
<div class="sidebar-header">
|
|
||||||
<h1>NextWks</h1>
|
|
||||||
<span class="version">Admin</span>
|
|
||||||
</div>
|
</div>
|
||||||
<ul class="sidebar-nav">
|
<div class="stat-card">
|
||||||
<li><a href="/admin" class="nav-link">Dashboard</a></li>
|
<div class="stat-value">—</div>
|
||||||
<li><a href="/admin/users" class="nav-link">Users</a></li>
|
<div class="stat-label">Modules</div>
|
||||||
</ul>
|
|
||||||
<div class="sidebar-footer">
|
|
||||||
<span class="status-indicator" id="health-status">Connected</span>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
<div class="stat-card">
|
||||||
<main class="main-content">
|
<div class="stat-value">OK</div>
|
||||||
<h2 style="margin-bottom:1.5rem;">Admin Dashboard</h2>
|
<div class="stat-label">Status</div>
|
||||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;">
|
|
||||||
<div class="card" style="text-align:center;">
|
|
||||||
<div style="font-size:2rem;font-weight:700;color:var(--primary);">{ userCount }</div>
|
|
||||||
<div style="color:var(--text-muted);margin-top:0.25rem;">Total Users</div>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="text-align:center;">
|
|
||||||
<div style="font-size:2rem;font-weight:700;color:var(--success);">Online</div>
|
|
||||||
<div style="color:var(--text-muted);margin-top:0.25rem;">Authelia API</div>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="text-align:center;">
|
|
||||||
<div style="font-size:2rem;font-weight:700;color:var(--warning);">/opt/</div>
|
|
||||||
<div style="color:var(--text-muted);margin-top:0.25rem;">Runtime Path</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card mt-2">
|
}
|
||||||
<h2>Quick Actions</h2>
|
|
||||||
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;">
|
|
||||||
<a href="/admin/users" class="btn btn-primary">Manage Users</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,20 +29,38 @@ func Dashboard(userCount int) 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, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Admin Dashboard - Next Workspace</title><script src=\"https://unpkg.com/htmx.org@2.0.4\"></script><script src=\"https://unpkg.com/htmx.org@2.0.4/dist/ext/response-targets.js\"></script><style>{ adminStyles() }</style></head><body><div class=\"app-container\"><nav class=\"sidebar\"><div class=\"sidebar-header\"><h1>NextWks</h1><span class=\"version\">Admin</span></div><ul class=\"sidebar-nav\"><li><a href=\"/admin\" class=\"nav-link\">Dashboard</a></li><li><a href=\"/admin/users\" class=\"nav-link\">Users</a></li></ul><div class=\"sidebar-footer\"><span class=\"status-indicator\" id=\"health-status\">Connected</span></div></nav><main class=\"main-content\"><h2 style=\"margin-bottom:1.5rem;\">Admin Dashboard</h2><div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;\"><div class=\"card\" style=\"text-align:center;\"><div style=\"font-size:2rem;font-weight:700;color:var(--primary);\">")
|
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=\"stats-grid\"><div class=\"stat-card\"><div class=\"stat-value\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var3 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(userCount)
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(userCount)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/dashboard.templ`, Line: 33, Col: 84}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/dashboard.templ`, Line: 7, Col: 39}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
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, 2, "</div><div style=\"color:var(--text-muted);margin-top:0.25rem;\">Total Users</div></div><div class=\"card\" style=\"text-align:center;\"><div style=\"font-size:2rem;font-weight:700;color:var(--success);\">Online</div><div style=\"color:var(--text-muted);margin-top:0.25rem;\">Authelia API</div></div><div class=\"card\" style=\"text-align:center;\"><div style=\"font-size:2rem;font-weight:700;color:var(--warning);\">/opt/</div><div style=\"color:var(--text-muted);margin-top:0.25rem;\">Runtime Path</div></div></div><div class=\"card mt-2\"><h2>Quick Actions</h2><div style=\"display:flex;gap:0.5rem;flex-wrap:wrap;\"><a href=\"/admin/users\" class=\"btn btn-primary\">Manage Users</a></div></div></main></div></body></html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><div class=\"stat-label\">Users</div></div><div class=\"stat-card\"><div class=\"stat-value\">—</div><div class=\"stat-label\">Modules</div></div><div class=\"stat-card\"><div class=\"stat-value\">OK</div><div class=\"stat-label\">Status</div></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = Layout("dashboard").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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,38 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
templ BaseLayout(title string) {
|
templ Layout(page string) {
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
|
||||||
<title>{ title } - Next Workspace</title>
|
<title>Admin — Next Workspace</title>
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4/dist/ext/response-targets.js"></script>
|
@adminStyles()
|
||||||
<style>{ adminStyles() }</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-container">
|
<div class="layout">
|
||||||
<nav class="sidebar">
|
<header class="topbar">
|
||||||
<div class="sidebar-header">
|
<div class="topbar-left">
|
||||||
<h1>NextWks</h1>
|
<a href="/" class="back-btn" title="Back to Workspace">
|
||||||
<span class="version">Admin</span>
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
|
||||||
|
</a>
|
||||||
|
<span class="page-title">Admin</span>
|
||||||
</div>
|
</div>
|
||||||
<ul class="sidebar-nav">
|
</header>
|
||||||
<li>
|
<nav class="sidebar">
|
||||||
<a href="/admin" class="nav-link">Dashboard</a>
|
<div class="sidebar-nav">
|
||||||
</li>
|
<a href="/admin" class={ "nav-link" + activeClass(page, "dashboard") }>
|
||||||
<li>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
||||||
<a href="/admin/users" class="nav-link">Users</a>
|
Dashboard
|
||||||
</li>
|
</a>
|
||||||
</ul>
|
<a href="/admin/users" class={ "nav-link" + activeClass(page, "users") }>
|
||||||
<div class="sidebar-footer">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||||
<span class="status-indicator" id="health-status">Connected</span>
|
Users
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<main class="main-content" id="main-content">
|
<main class="content">
|
||||||
{ children... }
|
{ children... }
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -38,135 +40,74 @@ templ BaseLayout(title string) {
|
||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func activeClass(current, target string) string {
|
||||||
|
if current == target {
|
||||||
|
return " active"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
templ adminStyles() {
|
templ adminStyles() {
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||||
:root{
|
:root{
|
||||||
--bg: #0f172a;
|
--bg:#0d1117;--surface:#161b22;--surface2:#21262d;--border:#30363d;
|
||||||
--surface: #1e293b;
|
--text:#e6edf3;--text2:#8b949e;--blue:#58a6ff;--purple:#a371f7;--green:#3fb950;
|
||||||
--surface-2: #334155;
|
--red:#f85149;--orange:#d2991d
|
||||||
--border: #475569;
|
|
||||||
--text: #f1f5f9;
|
|
||||||
--text-muted: #94a3b8;
|
|
||||||
--primary: #3b82f6;
|
|
||||||
--primary-hover: #2563eb;
|
|
||||||
--danger: #ef4444;
|
|
||||||
--success: #22c55e;
|
|
||||||
--warning: #f59e0b;
|
|
||||||
--radius: 8px;
|
|
||||||
}
|
}
|
||||||
html { font-size: 14px; }
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
|
||||||
body {
|
.layout{display:flex;min-height:100vh}
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
.topbar{display:flex;align-items:center;padding:0 16px;height:52px;background:var(--surface);border-bottom:1px solid var(--border);position:fixed;top:0;left:0;right:0;z-index:100}
|
||||||
background: var(--bg);
|
.topbar-left{display:flex;align-items:center;gap:10px}
|
||||||
color: var(--text);
|
.back-btn{color:var(--text2);text-decoration:none;display:flex;align-items:center;padding:4px;border-radius:8px}
|
||||||
line-height: 1.5;
|
.back-btn:hover{background:var(--surface2);color:var(--text)}
|
||||||
min-height: 100vh;
|
.page-title{font-weight:600;font-size:1.05rem}
|
||||||
}
|
.sidebar{width:200px;background:var(--surface);border-right:1px solid var(--border);padding:12px 8px;position:fixed;top:52px;left:0;bottom:0;overflow-y:auto}
|
||||||
.app-container { display: flex; min-height: 100vh; }
|
.sidebar-nav{display:flex;flex-direction:column;gap:2px}
|
||||||
.sidebar {
|
.nav-link{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;text-decoration:none;color:var(--text2);font-size:0.9rem;font-weight:500;transition:all 0.1s}
|
||||||
width: 240px;
|
.nav-link:hover{background:var(--surface2);color:var(--text)}
|
||||||
background: var(--surface);
|
.nav-link.active{background:var(--surface2);color:var(--blue)}
|
||||||
border-right: 1px solid var(--border);
|
.nav-link svg{flex-shrink:0}
|
||||||
display: flex;
|
.content{flex:1;margin-left:200px;margin-top:52px;padding:24px;max-width:900px}
|
||||||
flex-direction: column;
|
|
||||||
padding: 1rem;
|
/* Dashboard */
|
||||||
flex-shrink: 0;
|
.stats-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:24px}
|
||||||
}
|
.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px;text-align:center}
|
||||||
.sidebar-header { margin-bottom: 2rem; }
|
.stat-value{font-size:1.5rem;font-weight:700;color:var(--blue)}
|
||||||
.sidebar-header h1 { font-size: 1.25rem; font-weight: 700; color: var(--primary); }
|
.stat-label{font-size:0.75rem;color:var(--text2);margin-top:4px}
|
||||||
.sidebar-header .version { font-size: 0.75rem; color: var(--text-muted); }
|
|
||||||
.sidebar-nav { list-style: none; display: flex; flex-direction: column; gap: 0.25rem; }
|
/* User Management */
|
||||||
.nav-link {
|
.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}
|
||||||
display: block;
|
.section-header h2{font-size:1.15rem;font-weight:600}
|
||||||
padding: 0.625rem 0.75rem;
|
.section-header h3{font-size:1rem;font-weight:600}
|
||||||
color: var(--text);
|
.form-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px;margin-bottom:16px}
|
||||||
text-decoration: none;
|
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||||
border-radius: var(--radius);
|
.form-group{margin-bottom:12px}
|
||||||
transition: background 0.15s;
|
.form-group label{display:block;font-size:0.75rem;font-weight:500;color:var(--text2);margin-bottom:4px;text-transform:uppercase}
|
||||||
}
|
.input{width:100%;padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:0.9rem;outline:none}
|
||||||
.nav-link:hover { background: var(--surface-2); }
|
.input:focus{border-color:var(--blue)}
|
||||||
.sidebar-footer { margin-top: auto; padding-top: 1rem; }
|
.btn{display:inline-flex;align-items:center;padding:8px 16px;border-radius:8px;border:none;background:var(--blue);color:#fff;font-size:0.85rem;font-weight:500;cursor:pointer;text-decoration:none}
|
||||||
.status-indicator { font-size: 0.75rem; color: var(--success); }
|
.btn:hover{opacity:0.9}
|
||||||
.main-content { flex: 1; padding: 1.5rem; overflow-y: auto; }
|
.btn-sm{display:inline-flex;align-items:center;padding:5px 10px;border-radius:6px;border:1px solid var(--border);background:transparent;color:var(--text);font-size:0.78rem;cursor:pointer}
|
||||||
.card {
|
.btn-sm:hover{background:var(--surface2)}
|
||||||
background: var(--surface);
|
.btn-sm.danger{color:var(--red);border-color:var(--red)}
|
||||||
border: 1px solid var(--border);
|
.btn-sm.danger:hover{background:rgba(248,81,73,0.1)}
|
||||||
border-radius: var(--radius);
|
.table-toolbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
|
||||||
padding: 1.5rem;
|
.table-title{font-weight:600;font-size:0.9rem}
|
||||||
margin-bottom: 1rem;
|
.user-table-wrap{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px}
|
||||||
}
|
table{width:100%;border-collapse:collapse}
|
||||||
.card h2 { font-size: 1.125rem; margin-bottom: 1rem; }
|
th,td{text-align:left;padding:10px 8px;border-bottom:1px solid var(--border);font-size:0.85rem}
|
||||||
table {
|
th{color:var(--text2);font-weight:600;font-size:0.75rem;text-transform:uppercase}
|
||||||
width: 100%;
|
.loading-row{text-align:center;color:var(--text2);padding:2rem}
|
||||||
border-collapse: collapse;
|
.role-tag{display:inline-block;padding:2px 8px;border-radius:99px;font-size:0.7rem;font-weight:600;background:rgba(88,166,255,0.15);color:var(--blue)}
|
||||||
}
|
.status-dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:4px;vertical-align:middle}
|
||||||
th, td {
|
.status-dot.on{background:var(--green)}
|
||||||
text-align: left;
|
.status-dot.off{background:var(--red)}
|
||||||
padding: 0.75rem 0.5rem;
|
.alert{padding:12px;border-radius:8px;margin-bottom:8px;font-size:0.85rem}
|
||||||
border-bottom: 1px solid var(--border);
|
.alert.success{background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);color:var(--green)}
|
||||||
}
|
.alert.error{background:rgba(248,81,73,0.1);border:1px solid rgba(248,81,73,0.3);color:var(--red)}
|
||||||
th { color: var(--text-muted); font-weight: 600; font-size: 0.75rem; text-transform: uppercase; }
|
.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}
|
||||||
.btn {
|
.password-hint{font-size:0.75rem;color:var(--orange)}
|
||||||
display: inline-flex;
|
@media(max-width:640px){.sidebar{display:none}.content{margin-left:0}.stats-grid{grid-template-columns:1fr 1fr}.form-row{grid-template-columns:1fr}}
|
||||||
align-items: center;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: background 0.15s;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.btn-primary { background: var(--primary); color: white; }
|
|
||||||
.btn-primary:hover { background: var(--primary-hover); }
|
|
||||||
.btn-danger { background: var(--danger); color: white; }
|
|
||||||
.btn-danger:hover { opacity: 0.9; }
|
|
||||||
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.75rem; }
|
|
||||||
.form-group { margin-bottom: 1rem; }
|
|
||||||
.form-group label { display: block; margin-bottom: 0.375rem; color: var(--text-muted); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; }
|
|
||||||
.form-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.625rem 0.75rem;
|
|
||||||
background: var(--bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
color: var(--text);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
.form-input:focus { outline: none; border-color: var(--primary); }
|
|
||||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
||||||
.badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.125rem 0.5rem;
|
|
||||||
border-radius: 9999px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.badge-success { background: rgba(34,197,94,0.15); color: var(--success); }
|
|
||||||
.badge-danger { background: rgba(239,68,68,0.15); color: var(--danger); }
|
|
||||||
.badge-warning { background: rgba(245,158,11,0.15); color: var(--warning); }
|
|
||||||
.alert {
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.alert-success { background: rgba(34,197,94,0.1); border: 1px solid rgba(34,197,94,0.3); color: var(--success); }
|
|
||||||
.alert-error { background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); color: var(--danger); }
|
|
||||||
.password-display {
|
|
||||||
font-family: monospace;
|
|
||||||
background: var(--bg);
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
user-select: all;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
.mb-1 { margin-bottom: 0.5rem; }
|
|
||||||
.mb-2 { margin-bottom: 1rem; }
|
|
||||||
.mt-2 { margin-top: 1rem; }
|
|
||||||
.flex { display: flex; }
|
|
||||||
.flex-between { display: flex; justify-content: space-between; align-items: center; }
|
|
||||||
</style>
|
</style>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,45 +1,24 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
templ UserDashboard() {
|
templ UserDashboard() {
|
||||||
<div class="flex-between mb-2">
|
<div class="section-header">
|
||||||
<h2>User Management</h2>
|
<h2>User Management</h2>
|
||||||
<button class="btn btn-primary"
|
<button class="btn" hx-get="/admin/users/create-form" hx-target="#form-container" hx-swap="innerHTML">+ Add User</button>
|
||||||
hx-get="/admin/users/create-form"
|
|
||||||
hx-target="#form-container"
|
|
||||||
hx-swap="innerHTML">
|
|
||||||
+ Add User
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="form-container" class="mb-2"></div>
|
<div id="form-container" style="margin-bottom:16px"></div>
|
||||||
|
|
||||||
<div class="card" id="user-table-container">
|
<div class="user-table-wrap">
|
||||||
<div class="flex-between mb-1">
|
<div class="table-toolbar">
|
||||||
<h2>Users</h2>
|
<span class="table-title">All Users</span>
|
||||||
<button class="btn btn-sm btn-primary"
|
<button class="btn-sm" hx-get="/admin/api/users" hx-target="#user-table-body" hx-swap="innerHTML" hx-trigger="load, click">Refresh</button>
|
||||||
hx-get="/admin/api/users"
|
|
||||||
hx-target="#user-table-body"
|
|
||||||
hx-swap="innerHTML"
|
|
||||||
hx-trigger="load, click">
|
|
||||||
Refresh
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead><tr>
|
||||||
<tr>
|
<th>Username</th><th>Name</th><th>Email</th><th>Role</th><th>Status</th><th></th>
|
||||||
<th>Username</th>
|
</tr></thead>
|
||||||
<th>Display Name</th>
|
<tbody id="user-table-body" hx-get="/admin/api/users" hx-trigger="load" hx-swap="innerHTML">
|
||||||
<th>Email</th>
|
<tr><td colspan="6" class="loading-row">Loading users...</td></tr>
|
||||||
<th>Groups</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="user-table-body"
|
|
||||||
hx-get="/admin/api/users"
|
|
||||||
hx-trigger="load"
|
|
||||||
hx-swap="innerHTML">
|
|
||||||
<tr><td colspan="6" style="text-align:center;color:var(--text-muted);padding:2rem;">Loading users...</td></tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -51,119 +30,83 @@ templ UserRows(users []UserRow) {
|
||||||
<td><strong>{ u.Username }</strong></td>
|
<td><strong>{ u.Username }</strong></td>
|
||||||
<td>{ u.DisplayName }</td>
|
<td>{ u.DisplayName }</td>
|
||||||
<td>{ u.Email }</td>
|
<td>{ u.Email }</td>
|
||||||
<td>{ u.Groups }</td>
|
<td><span class="role-tag">{ u.Role }</span></td>
|
||||||
<td>
|
<td>
|
||||||
if u.Disabled {
|
if u.Disabled {
|
||||||
<span class="badge badge-danger">Disabled</span>
|
<span class="status-dot off"></span> Disabled
|
||||||
} else {
|
} else {
|
||||||
<span class="badge badge-success">Active</span>
|
<span class="status-dot on"></span> Active
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-danger"
|
<button class="btn-sm danger"
|
||||||
hx-delete="/admin/api/users/{ u.Username }"
|
hx-delete={ "/admin/api/users/" + u.Username }
|
||||||
hx-confirm="Delete user { u.Username }?"
|
hx-confirm={ "Delete " + u.Username + "?" }
|
||||||
hx-target="closest tr"
|
hx-target="closest tr"
|
||||||
hx-swap="delete">
|
hx-swap="delete">Delete</button>
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ CreateUserForm() {
|
templ CreateUserForm() {
|
||||||
<div class="card" id="create-form">
|
<div class="form-card" id="create-form">
|
||||||
<div class="flex-between mb-1">
|
<div class="section-header">
|
||||||
<h2>Create New User</h2>
|
<h3>New User</h3>
|
||||||
<button class="btn btn-sm btn-danger"
|
<button class="btn-sm"
|
||||||
hx-get="/admin/users/cancel-form"
|
hx-get="/admin/users/cancel-form" hx-target="#form-container" hx-swap="innerHTML">Cancel</button>
|
||||||
hx-target="#form-container"
|
|
||||||
hx-swap="innerHTML">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<form hx-post="/admin/api/users"
|
<form hx-post="/admin/api/users" hx-target="#form-container" hx-swap="innerHTML">
|
||||||
hx-target="#form-container"
|
|
||||||
hx-swap="innerHTML">
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Username *</label>
|
<label>Username *</label>
|
||||||
<input type="text" name="username" class="form-input" required placeholder="e.g. jdoe"/>
|
<input type="text" name="username" class="input" required placeholder="jdoe"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Display Name</label>
|
<label>Display Name</label>
|
||||||
<input type="text" name="display_name" class="form-input" placeholder="e.g. John Doe"/>
|
<input type="text" name="display_name" class="input" placeholder="John Doe"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Email</label>
|
<label>Email</label>
|
||||||
<input type="email" name="email" class="form-input" placeholder="e.g. john@example.com"/>
|
<input type="email" name="email" class="input" placeholder="john@example.com"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Groups</label>
|
<label>Groups</label>
|
||||||
<input type="text" name="groups" class="form-input" placeholder="e.g. admins,users"/>
|
<input type="text" name="groups" class="input" placeholder="admins,users"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary mt-2">Create User</button>
|
<button type="submit" class="btn" style="margin-top:8px;width:100%">Create User</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ CreateUserSuccess(results []CreateUserResultRow) {
|
templ CreateUserSuccess(results []CreateUserResultRow) {
|
||||||
<div class="alert alert-success">
|
|
||||||
<strong>Users created successfully!</strong>
|
|
||||||
<div class="flex-between mt-2">
|
|
||||||
<span></span>
|
|
||||||
<button class="btn btn-sm btn-primary"
|
|
||||||
hx-get="/admin/users/create-form"
|
|
||||||
hx-target="#form-container"
|
|
||||||
hx-swap="innerHTML">
|
|
||||||
+ Add Another
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
<div class="card">
|
|
||||||
<div class="flex-between">
|
|
||||||
<div>
|
|
||||||
<strong>{ r.Username }</strong>
|
|
||||||
if r.Error != "" {
|
if r.Error != "" {
|
||||||
<span class="badge badge-danger">Error</span>
|
<div class="alert error">{ r.Error }</div>
|
||||||
} else {
|
} else {
|
||||||
<span class="badge badge-success">Created</span>
|
<div class="alert success">
|
||||||
}
|
<strong>{ r.Username }</strong> created
|
||||||
</div>
|
<div class="password-box">{ r.GeneratedPassword }</div>
|
||||||
</div>
|
<div class="password-hint">Save this — it won't be shown again</div>
|
||||||
if r.Error != "" {
|
|
||||||
<p class="mt-2" style="color:var(--danger);">{ r.Error }</p>
|
|
||||||
} else {
|
|
||||||
<div class="mt-2">
|
|
||||||
<label style="font-size:0.75rem;color:var(--text-muted);">Generated Password (save this now)</label>
|
|
||||||
<div class="password-display">{ r.GeneratedPassword }</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Auto-refresh the user table after creation
|
setTimeout(function() { htmx.trigger('#user-table-body', 'click'); }, 500);
|
||||||
setTimeout(function() {
|
|
||||||
htmx.trigger("#user-table-body", "click");
|
|
||||||
}, 500);
|
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data types for template rendering
|
|
||||||
|
|
||||||
type UserRow struct {
|
type UserRow struct {
|
||||||
Username string
|
Username string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
Email string
|
Email string
|
||||||
|
Role string
|
||||||
Groups string
|
Groups string
|
||||||
Disabled bool
|
Disabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateUserResultRow struct {
|
type CreateUserResultRow struct {
|
||||||
Username string
|
Username string
|
||||||
GeneratedPassword string
|
GeneratedPassword string
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ 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=\"flex-between mb-2\"><h2>User Management</h2><button class=\"btn btn-primary\" hx-get=\"/admin/users/create-form\" hx-target=\"#form-container\" hx-swap=\"innerHTML\">+ Add User</button></div><div id=\"form-container\" class=\"mb-2\"></div><div class=\"card\" id=\"user-table-container\"><div class=\"flex-between mb-1\"><h2>Users</h2><button class=\"btn btn-sm btn-primary\" 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>Display Name</th><th>Email</th><th>Groups</th><th>Status</th><th>Actions</th></tr></thead> <tbody id=\"user-table-body\" hx-get=\"/admin/api/users\" hx-trigger=\"load\" hx-swap=\"innerHTML\"><tr><td colspan=\"6\" style=\"text-align:center;color:var(--text-muted);padding:2rem;\">Loading users...</td></tr></tbody></table></div>")
|
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>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ func UserRows(users []UserRow) templ.Component {
|
||||||
var templ_7745c5c3_Var3 string
|
var templ_7745c5c3_Var3 string
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(u.Username)
|
templ_7745c5c3_Var3, 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: 51, Col: 27}
|
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))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|
@ -79,7 +79,7 @@ func UserRows(users []UserRow) templ.Component {
|
||||||
var templ_7745c5c3_Var4 string
|
var templ_7745c5c3_Var4 string
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(u.DisplayName)
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(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: 52, Col: 22}
|
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))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|
@ -92,41 +92,67 @@ func UserRows(users []UserRow) templ.Component {
|
||||||
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(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: 53, Col: 16}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 32, Col: 16}
|
||||||
}
|
}
|
||||||
_, 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>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</td><td><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_Var6 string
|
var templ_7745c5c3_Var6 string
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(u.Groups)
|
templ_7745c5c3_Var6, 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: 54, Col: 17}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 33, Col: 38}
|
||||||
}
|
}
|
||||||
_, 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, "</td><td>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span></td><td>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if u.Disabled {
|
if u.Disabled {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span class=\"badge badge-danger\">Disabled</span>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span class=\"status-dot off\"></span> Disabled")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span class=\"badge badge-success\">Active</span>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span class=\"status-dot on\"></span> Active")
|
||||||
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, 9, "</td><td><button class=\"btn btn-sm btn-danger\" hx-delete=\"/admin/api/users/{ u.Username }\" hx-confirm=\"Delete user { u.Username }?\" hx-target=\"closest tr\" hx-swap=\"delete\">Delete</button></td></tr>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</td><td><button class=\"btn-sm danger\" hx-delete=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue("/admin/api/users/" + u.Username)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 43, Col: 49}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" hx-confirm=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 string
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue("Delete " + u.Username + "?")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 44, Col: 46}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" hx-target=\"closest tr\" hx-swap=\"delete\">Delete</button></td></tr>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -151,12 +177,12 @@ func CreateUserForm() templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var7 == nil {
|
if templ_7745c5c3_Var9 == nil {
|
||||||
templ_7745c5c3_Var7 = templ.NopComponent
|
templ_7745c5c3_Var9 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"card\" id=\"create-form\"><div class=\"flex-between mb-1\"><h2>Create New User</h2><button class=\"btn btn-sm btn-danger\" 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=\"form-input\" required placeholder=\"e.g. jdoe\"></div><div class=\"form-group\"><label>Display Name</label> <input type=\"text\" name=\"display_name\" class=\"form-input\" placeholder=\"e.g. John Doe\"></div></div><div class=\"form-row\"><div class=\"form-group\"><label>Email</label> <input type=\"email\" name=\"email\" class=\"form-input\" placeholder=\"e.g. john@example.com\"></div><div class=\"form-group\"><label>Groups</label> <input type=\"text\" name=\"groups\" class=\"form-input\" placeholder=\"e.g. admins,users\"></div></div><button type=\"submit\" class=\"btn btn-primary mt-2\">Create User</button></form></div>")
|
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>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -180,91 +206,64 @@ func CreateUserSuccess(results []CreateUserResultRow) templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var8 == nil {
|
if templ_7745c5c3_Var10 == nil {
|
||||||
templ_7745c5c3_Var8 = templ.NopComponent
|
templ_7745c5c3_Var10 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"alert alert-success\"><strong>Users created successfully!</strong><div class=\"flex-between mt-2\"><span></span> <button class=\"btn btn-sm btn-primary\" hx-get=\"/admin/users/create-form\" hx-target=\"#form-container\" hx-swap=\"innerHTML\">+ Add Another</button></div></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"card\"><div class=\"flex-between\"><div><strong>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var9 string
|
|
||||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(r.Username)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 131, Col: 25}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</strong> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if r.Error != "" {
|
if r.Error != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<span class=\"badge badge-danger\">Error</span>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"alert error\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span class=\"badge badge-success\">Created</span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if r.Error != "" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<p class=\"mt-2\" style=\"color:var(--danger);\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var10 string
|
|
||||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(r.Error)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 140, Col: 58}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"mt-2\"><label style=\"font-size:0.75rem;color:var(--text-muted);\">Generated Password (save this now)</label><div class=\"password-display\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var11 string
|
var templ_7745c5c3_Var11 string
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(r.GeneratedPassword)
|
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: 144, Col: 56}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 88, 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 {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"alert success\"><strong>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var12 string
|
||||||
|
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(r.Username)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 91, Col: 24}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</strong> created<div class=\"password-box\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var13 string
|
||||||
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(r.GeneratedPassword)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/admin/templates/user-dashboard.templ`, Line: 92, Col: 51}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
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>")
|
||||||
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, 21, "</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
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, 22, "<script type=\"text/javascript\">\n\t\t// Auto-refresh the user table after creation\n\t\tsetTimeout(function() {\n\t\t\thtmx.trigger(\"#user-table-body\", \"click\");\n\t\t}, 500);\n\t</script>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -272,16 +271,14 @@ func CreateUserSuccess(results []CreateUserResultRow) templ.Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data types for template rendering
|
|
||||||
|
|
||||||
type UserRow struct {
|
type UserRow struct {
|
||||||
Username string
|
Username string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
Email string
|
Email string
|
||||||
|
Role string
|
||||||
Groups string
|
Groups string
|
||||||
Disabled bool
|
Disabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateUserResultRow struct {
|
type CreateUserResultRow struct {
|
||||||
Username string
|
Username string
|
||||||
GeneratedPassword string
|
GeneratedPassword string
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ func UserToRow(u User) templates.UserRow {
|
||||||
Username: u.Username,
|
Username: u.Username,
|
||||||
DisplayName: u.DisplayName,
|
DisplayName: u.DisplayName,
|
||||||
Email: u.Email,
|
Email: u.Email,
|
||||||
|
Role: u.Role,
|
||||||
Groups: u.Groups,
|
Groups: u.Groups,
|
||||||
Disabled: u.Disabled,
|
Disabled: u.Disabled,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue