web-ui(wave 2): migrate users.html to new primitives
This commit is contained in:
parent
596fe228ed
commit
5650b279c3
1 changed files with 240 additions and 184 deletions
|
|
@ -3,53 +3,106 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>Users — Z-AMPP</title>
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="/dist/app.css">
|
||||
<style>
|
||||
.users-shell {
|
||||
flex: 1;
|
||||
body { margin: 0; }
|
||||
|
||||
/* Local-only tabs styling — JS toggles .tab.active and .tab-content.active,
|
||||
so we keep those class names and just retheme. */
|
||||
.users-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
gap: 4px;
|
||||
padding: 0 20px;
|
||||
background: var(--bg-deep);
|
||||
border-bottom: 1px solid var(--border-faint);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.users-tabs .tab {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-tertiary);
|
||||
font: 500 12px/1 var(--font);
|
||||
letter-spacing: 0.04em;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.users-tabs .tab:hover { color: var(--text-secondary); }
|
||||
.users-tabs .tab.active {
|
||||
color: var(--accent-bright);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
.tab-content { display: none; }
|
||||
.tab-content.active { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
|
||||
.table-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--sp-6);
|
||||
}
|
||||
.table-area::-webkit-scrollbar { width: 5px; }
|
||||
.table-area::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; }
|
||||
|
||||
.section-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--sp-4);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: var(--text-md);
|
||||
font-weight: 500;
|
||||
font: 500 14px/1.2 var(--font);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.action-row {
|
||||
display: flex;
|
||||
gap: var(--sp-2);
|
||||
|
||||
.wd-list-row.users-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(140px, 1fr) minmax(140px, 1.2fr) minmax(80px, 0.6fr) minmax(80px, 0.6fr) minmax(80px, 0.6fr) auto;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
}
|
||||
.wd-list-row.users-row.header {
|
||||
font: 600 10px/1 var(--font);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.wd-list-row.groups-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(140px, 1fr) minmax(180px, 1.5fr) minmax(80px, 0.6fr) minmax(80px, 0.6fr) auto;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
}
|
||||
.wd-list-row.groups-row.header {
|
||||
font: 600 10px/1 var(--font);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.empty-row {
|
||||
padding: 48px 12px;
|
||||
text-align: center;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.text-tertiary { color: var(--text-tertiary); }
|
||||
.text-secondary { color: var(--text-secondary); }
|
||||
.text-xs { font-size: 11px; }
|
||||
.text-sm { font-size: 12px; }
|
||||
|
||||
.member-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sp-1);
|
||||
margin-top: var(--sp-2);
|
||||
gap: 4px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.member-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-1);
|
||||
gap: 4px;
|
||||
padding: 2px 8px 2px 6px;
|
||||
border-radius: 100px;
|
||||
font-size: var(--text-xs);
|
||||
font-size: 11px;
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border: 1px solid var(--border-faint);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.member-chip button {
|
||||
|
|
@ -62,233 +115,230 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.member-chip button:hover { color: var(--status-red); }
|
||||
.member-chip button:hover { color: var(--signal-bad); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<div class="wd-shell" style="display:flex;min-height:100vh;">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar" aria-label="Main navigation">
|
||||
<div class="sidebar-brand">
|
||||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" class="sidebar-logo">
|
||||
<span class="sidebar-brand-name">Z-AMPP</span>
|
||||
<nav class="wd-sidebar" aria-label="Main navigation">
|
||||
<div class="wd-sidebar-header">
|
||||
<img src="img/dragon-logo.png?v=1" alt="Wild Dragon" style="width:18px;height:18px;">
|
||||
<span class="wd-sidebar-brand">Z-AMPP</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.html" class="nav-item">
|
||||
<div class="wd-sidebar-nav">
|
||||
<a href="home.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 7l6-5 6 5v7a1 1 0 0 1-1 1h-3v-5H6v5H3a1 1 0 0 1-1-1z"/></svg>
|
||||
Home
|
||||
</a>
|
||||
<a href="index.html" class="nav-item">
|
||||
<a href="index.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="6" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/><rect x="9" y="9" width="6" height="6" rx="1"/></svg>
|
||||
Library
|
||||
</a>
|
||||
<a href="projects.html" class="nav-item">
|
||||
<a href="projects.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 4a1 1 0 0 1 1-1h4l2 2h5a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1z"/></svg>
|
||||
Projects
|
||||
</a>
|
||||
<a href="upload.html" class="nav-item">
|
||||
<a href="upload.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 11V3M5 6l3-3 3 3"/><path d="M2 13h12"/></svg>
|
||||
Ingest
|
||||
</a>
|
||||
<a href="recorders.html" class="nav-item">
|
||||
<a href="recorders.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="4" width="10" height="8" rx="1"/><path d="M11 7l4-2v6l-4-2"/></svg>
|
||||
Recorders
|
||||
</a>
|
||||
<a href="capture.html" class="nav-item">
|
||||
<a href="capture.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="3"/><circle cx="8" cy="8" r="6.5"/></svg>
|
||||
Capture
|
||||
</a>
|
||||
<a href="jobs.html" class="nav-item">
|
||||
<a href="jobs.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M2 8h8M2 12h5"/></svg>
|
||||
Jobs
|
||||
</a>
|
||||
<a href="editor.html" class="nav-item">
|
||||
<a href="editor.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 13l1.5-3.5L11 2l3 3-7.5 7.5L3 14zM10 3l3 3"/></svg>
|
||||
Editor
|
||||
</a>
|
||||
<div class="sidebar-section-label">Admin</div>
|
||||
<a href="users.html" class="nav-item active">
|
||||
<div class="wd-sidebar-section">Admin</div>
|
||||
<a href="users.html" class="wd-nav-item is-active">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6" cy="5" r="2.5"/><path d="M1 13c0-2.8 2.2-5 5-5s5 2.2 5 5"/><circle cx="12" cy="5" r="2"/><path d="M15 12c0-1.9-1.3-3.5-3-4"/></svg>
|
||||
Users
|
||||
</a>
|
||||
<a href="tokens.html" class="nav-item">
|
||||
<a href="tokens.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6" cy="10" r="3.5"/><path d="M8.7 7.3L13 3M11.5 3.5l1.5 1.5M13.5 2.5l1 1"/></svg>
|
||||
Tokens
|
||||
</a>
|
||||
<a href="containers.html" class="nav-item">
|
||||
<a href="containers.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="5" width="14" height="4" rx="1"/><rect x="1" y="10" width="14" height="4" rx="1"/><path d="M4 7h1M4 12h1"/></svg>
|
||||
Containers
|
||||
</a>
|
||||
<a href="cluster.html" class="nav-item">
|
||||
<a href="cluster.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="2"/><circle cx="2" cy="3" r="1.5"/><circle cx="14" cy="3" r="1.5"/><circle cx="2" cy="13" r="1.5"/><circle cx="14" cy="13" r="1.5"/><path d="M3.1 4.1L6.5 6.5M12.9 4.1L9.5 6.5M3.1 11.9L6.5 9.5M12.9 11.9L9.5 9.5"/></svg>
|
||||
Cluster
|
||||
</a>
|
||||
<a href="settings.html" class="nav-item">
|
||||
<a href="settings.html" class="wd-nav-item">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M2.9 2.9l1.1 1.1M12 12l1.1 1.1M2.9 13.1L4 12M12 4l1.1-1.1"/></svg>
|
||||
Settings
|
||||
</a>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name" id="userName">—</div>
|
||||
<div class="sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<div class="wd-sidebar-footer">
|
||||
<div class="wd-sidebar-user">
|
||||
<div class="wd-sidebar-user-avatar" id="userAvatar">?</div>
|
||||
<div class="wd-sidebar-user-info">
|
||||
<div class="wd-sidebar-user-name" id="userName">—</div>
|
||||
<div class="wd-sidebar-user-role" id="userRole"></div>
|
||||
</div>
|
||||
<button class="btn btn-ghost" id="logoutBtn" title="Sign out" style="padding:0;width:24px;height:24px;flex-shrink:0;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14"><path d="M10 8H3M6 5l-3 3 3 3"/><path d="M7 3h5a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H7"/></svg>
|
||||
<button class="wd-btn wd-btn--ghost wd-btn--sm wd-btn--icon wd-sidebar-user-logout" id="logoutBtn" title="Sign out">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="12" height="12"><path d="M10 8H3M6 5l-3 3 3 3"/><path d="M7 3h5a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H7"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main -->
|
||||
<div class="main">
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<span class="page-title">Users & Groups</span>
|
||||
<div style="flex:1;display:flex;flex-direction:column;">
|
||||
<header class="wd-topbar">
|
||||
<div class="wd-topbar-left">
|
||||
<nav class="wd-breadcrumb"><span class="wd-breadcrumb-crumb">Users & Groups</span></nav>
|
||||
</div>
|
||||
<div class="wd-topbar-right">
|
||||
<button class="wd-btn wd-btn--primary wd-btn--sm" onclick="openUserPanel()">New user</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="users-shell">
|
||||
<!-- Tabs -->
|
||||
<div class="tabs" style="padding:0 var(--sp-6);background:var(--bg-panel);border-bottom:1px solid var(--border);flex-shrink:0;">
|
||||
<button class="tab active" onclick="switchTab('users',this)">Users</button>
|
||||
<button class="tab" onclick="switchTab('groups',this)">Groups</button>
|
||||
</div>
|
||||
<!-- Tabs -->
|
||||
<div class="users-tabs">
|
||||
<button class="tab active" onclick="switchTab('users',this)">Users</button>
|
||||
<button class="tab" onclick="switchTab('groups',this)">Groups</button>
|
||||
</div>
|
||||
|
||||
<!-- Users tab -->
|
||||
<div class="tab-content active" id="tab-users">
|
||||
<div class="table-area">
|
||||
<div class="section-toolbar">
|
||||
<span class="section-title" id="userCount">Users</span>
|
||||
<button class="btn btn-primary btn-sm" onclick="openUserPanel()">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v12M2 8h12"/></svg>
|
||||
New user
|
||||
</button>
|
||||
</div>
|
||||
<table class="data-table" id="usersTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Display name</th>
|
||||
<th>Role</th>
|
||||
<th>Groups</th>
|
||||
<th>Created</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="usersTbody">
|
||||
<tr><td colspan="6" style="text-align:center;color:var(--text-tertiary);padding:var(--sp-8)">Loading…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Users tab -->
|
||||
<div class="tab-content active" id="tab-users">
|
||||
<main style="flex:1;padding:20px 20px 32px;overflow:auto;">
|
||||
<div class="section-toolbar">
|
||||
<span class="section-title" id="userCount">Users</span>
|
||||
<button class="wd-btn wd-btn--primary wd-btn--sm" onclick="openUserPanel()">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="12" height="12"><path d="M8 2v12M2 8h12"/></svg>
|
||||
New user
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wd-list" id="usersTable">
|
||||
<div class="wd-list-row users-row header">
|
||||
<span>Username</span>
|
||||
<span>Display name</span>
|
||||
<span>Role</span>
|
||||
<span>Groups</span>
|
||||
<span>Created</span>
|
||||
<span style="text-align:right">Actions</span>
|
||||
</div>
|
||||
<div id="usersTbody">
|
||||
<div class="empty-row">Loading…</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Groups tab -->
|
||||
<div class="tab-content" id="tab-groups">
|
||||
<div class="table-area">
|
||||
<div class="section-toolbar">
|
||||
<span class="section-title" id="groupCount">Groups</span>
|
||||
<button class="btn btn-primary btn-sm" onclick="openGroupPanel()">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v12M2 8h12"/></svg>
|
||||
New group
|
||||
</button>
|
||||
</div>
|
||||
<table class="data-table" id="groupsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Members</th>
|
||||
<th>Created</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="groupsTbody">
|
||||
<tr><td colspan="5" style="text-align:center;color:var(--text-tertiary);padding:var(--sp-8)">Loading…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Groups tab -->
|
||||
<div class="tab-content" id="tab-groups">
|
||||
<main style="flex:1;padding:20px 20px 32px;overflow:auto;">
|
||||
<div class="section-toolbar">
|
||||
<span class="section-title" id="groupCount">Groups</span>
|
||||
<button class="wd-btn wd-btn--primary wd-btn--sm" onclick="openGroupPanel()">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="12" height="12"><path d="M8 2v12M2 8h12"/></svg>
|
||||
New group
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wd-list" id="groupsTable">
|
||||
<div class="wd-list-row groups-row header">
|
||||
<span>Name</span>
|
||||
<span>Description</span>
|
||||
<span>Members</span>
|
||||
<span>Created</span>
|
||||
<span style="text-align:right">Actions</span>
|
||||
</div>
|
||||
<div id="groupsTbody">
|
||||
<div class="empty-row">Loading…</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User slide panel -->
|
||||
<div class="slide-overlay" id="userOverlay" onclick="closeUserPanel()"></div>
|
||||
<div class="slide-panel" id="userPanel">
|
||||
<div class="slide-panel-header">
|
||||
<span class="slide-panel-title" id="userPanelTitle">New user</span>
|
||||
<button class="btn btn-ghost btn-sm" onclick="closeUserPanel()" style="padding:0;width:28px;height:28px;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 3l10 10M13 3L3 13"/></svg>
|
||||
<div class="wd-slide-panel-overlay" id="userOverlay" onclick="closeUserPanel()"></div>
|
||||
<div class="wd-slide-panel" id="userPanel">
|
||||
<div class="wd-slide-panel-header">
|
||||
<span class="wd-slide-panel-title" id="userPanelTitle">New user</span>
|
||||
<button class="wd-btn wd-btn--ghost wd-btn--sm wd-btn--icon" onclick="closeUserPanel()" aria-label="Close">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="12" height="12"><path d="M3 3l10 10M13 3L3 13"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="slide-panel-body">
|
||||
<div class="wd-slide-panel-body">
|
||||
<input type="hidden" id="editUserId">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="uUsername">Username</label>
|
||||
<input type="text" id="uUsername" placeholder="e.g. jsmith">
|
||||
<div class="wd-form-group">
|
||||
<label class="wd-label" for="uUsername">Username</label>
|
||||
<input class="wd-input" type="text" id="uUsername" placeholder="e.g. jsmith">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="uDisplayName">Display name</label>
|
||||
<input type="text" id="uDisplayName" placeholder="e.g. Jane Smith">
|
||||
<div class="wd-form-group">
|
||||
<label class="wd-label" for="uDisplayName">Display name</label>
|
||||
<input class="wd-input" type="text" id="uDisplayName" placeholder="e.g. Jane Smith">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="uRole">Role</label>
|
||||
<select id="uRole">
|
||||
<div class="wd-form-group">
|
||||
<label class="wd-label" for="uRole">Role</label>
|
||||
<select class="wd-select" id="uRole">
|
||||
<option value="editor">Editor</option>
|
||||
<option value="viewer">Viewer</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="uPassword" id="uPasswordLabel">Password</label>
|
||||
<input type="password" id="uPassword" placeholder="Min 8 characters">
|
||||
<div class="form-hint" id="uPasswordHint"></div>
|
||||
<div class="wd-form-group">
|
||||
<label class="wd-label" for="uPassword" id="uPasswordLabel">Password</label>
|
||||
<input class="wd-input" type="password" id="uPassword" placeholder="Min 8 characters">
|
||||
<div class="wd-hint" id="uPasswordHint"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide-panel-footer">
|
||||
<button class="btn btn-ghost" onclick="closeUserPanel()">Cancel</button>
|
||||
<button class="btn btn-primary" id="saveUserBtn" onclick="saveUser()">Create user</button>
|
||||
<div class="wd-slide-panel-footer">
|
||||
<button class="wd-btn wd-btn--ghost wd-btn--md" onclick="closeUserPanel()">Cancel</button>
|
||||
<button class="wd-btn wd-btn--primary wd-btn--md" id="saveUserBtn" onclick="saveUser()">Create user</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Group slide panel -->
|
||||
<div class="slide-overlay" id="groupOverlay" onclick="closeGroupPanel()"></div>
|
||||
<div class="slide-panel" id="groupPanel">
|
||||
<div class="slide-panel-header">
|
||||
<span class="slide-panel-title" id="groupPanelTitle">New group</span>
|
||||
<button class="btn btn-ghost btn-sm" onclick="closeGroupPanel()" style="padding:0;width:28px;height:28px;">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 3l10 10M13 3L3 13"/></svg>
|
||||
<div class="wd-slide-panel-overlay" id="groupOverlay" onclick="closeGroupPanel()"></div>
|
||||
<div class="wd-slide-panel" id="groupPanel">
|
||||
<div class="wd-slide-panel-header">
|
||||
<span class="wd-slide-panel-title" id="groupPanelTitle">New group</span>
|
||||
<button class="wd-btn wd-btn--ghost wd-btn--sm wd-btn--icon" onclick="closeGroupPanel()" aria-label="Close">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" width="12" height="12"><path d="M3 3l10 10M13 3L3 13"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="slide-panel-body">
|
||||
<div class="wd-slide-panel-body">
|
||||
<input type="hidden" id="editGroupId">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="gName">Group name</label>
|
||||
<input type="text" id="gName" placeholder="e.g. News Team">
|
||||
<div class="wd-form-group">
|
||||
<label class="wd-label" for="gName">Group name</label>
|
||||
<input class="wd-input" type="text" id="gName" placeholder="e.g. News Team">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="gDescription">Description</label>
|
||||
<textarea id="gDescription" rows="2" placeholder="Optional description"></textarea>
|
||||
<div class="wd-form-group">
|
||||
<label class="wd-label" for="gDescription">Description</label>
|
||||
<textarea class="wd-textarea" id="gDescription" rows="2" placeholder="Optional description"></textarea>
|
||||
</div>
|
||||
<div class="form-group" id="gMembersSection" style="display:none;">
|
||||
<label class="form-label">Members</label>
|
||||
<div class="wd-form-group" id="gMembersSection" style="display:none;">
|
||||
<label class="wd-label">Members</label>
|
||||
<div class="member-chips" id="memberChips"></div>
|
||||
<select id="addMemberSelect" style="margin-top:var(--sp-2);">
|
||||
<select class="wd-select" id="addMemberSelect" style="margin-top:8px;">
|
||||
<option value="">Add member…</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide-panel-footer">
|
||||
<button class="btn btn-ghost" onclick="closeGroupPanel()">Cancel</button>
|
||||
<button class="btn btn-primary" id="saveGroupBtn" onclick="saveGroup()">Create group</button>
|
||||
<div class="wd-slide-panel-footer">
|
||||
<button class="wd-btn wd-btn--ghost wd-btn--md" onclick="closeGroupPanel()">Cancel</button>
|
||||
<button class="wd-btn wd-btn--primary wd-btn--md" id="saveGroupBtn" onclick="saveGroup()">Create group</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast-container" id="toastContainer" aria-live="polite"></div>
|
||||
<div class="wd-toast-container" id="toastContainer" aria-live="polite"></div>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script>
|
||||
|
|
@ -319,23 +369,29 @@
|
|||
const tbody = document.getElementById('usersTbody');
|
||||
document.getElementById('userCount').textContent = `${allUsers.length} user${allUsers.length !== 1 ? 's' : ''}`;
|
||||
if (!allUsers.length) {
|
||||
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:var(--text-tertiary);padding:var(--sp-8)">No users yet</td></tr>`;
|
||||
tbody.innerHTML = `<div class="empty-row">No users yet</div>`;
|
||||
return;
|
||||
}
|
||||
const roleBadge = (role) => {
|
||||
const mod = role === 'admin' ? 'wd-badge--good'
|
||||
: role === 'editor' ? 'wd-badge--info'
|
||||
: 'wd-badge--idle';
|
||||
return `<span class="wd-badge ${mod}">${esc(role)}</span>`;
|
||||
};
|
||||
tbody.innerHTML = allUsers.map(u => `
|
||||
<tr>
|
||||
<td><code style="font-size:var(--text-xs)">${esc(u.username)}</code></td>
|
||||
<td>${esc(u.display_name || '—')}</td>
|
||||
<td><span class="badge badge-${u.role}">${esc(u.role)}</span></td>
|
||||
<td><span class="text-tertiary text-xs">${u.group_count || 0} group${u.group_count !== 1 ? 's' : ''}</span></td>
|
||||
<td class="text-xs text-tertiary">${u.created_at ? new Date(u.created_at).toLocaleDateString() : '—'}</td>
|
||||
<td>
|
||||
<div style="display:flex;gap:var(--sp-1);justify-content:flex-end;">
|
||||
<button class="btn btn-ghost btn-sm" onclick="editUser('${u.id}')">Edit</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="confirmDeleteUser('${u.id}',${esc(JSON.stringify(u.username))})">Delete</button>
|
||||
<div class="wd-list-row users-row">
|
||||
<div class="wd-list-cell wd-list-cell--name"><code style="font-size:11px;font-family:var(--font-mono);">${esc(u.username)}</code></div>
|
||||
<div class="wd-list-cell">${esc(u.display_name || '—')}</div>
|
||||
<div class="wd-list-cell">${roleBadge(u.role)}</div>
|
||||
<div class="wd-list-cell"><span class="text-tertiary text-xs">${u.group_count || 0} group${u.group_count !== 1 ? 's' : ''}</span></div>
|
||||
<div class="wd-list-cell text-xs text-tertiary">${u.created_at ? new Date(u.created_at).toLocaleDateString() : '—'}</div>
|
||||
<div class="wd-list-cell wd-list-cell--actions" style="text-align:right">
|
||||
<div style="display:flex;gap:4px;justify-content:flex-end;">
|
||||
<button class="wd-btn wd-btn--ghost wd-btn--sm" onclick="editUser('${u.id}')">Edit</button>
|
||||
<button class="wd-btn wd-btn--danger wd-btn--sm" onclick="confirmDeleteUser('${u.id}',${esc(JSON.stringify(u.username))})">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
</div>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
// ── User panel ────────────────────────────────────────────
|
||||
|
|
@ -427,22 +483,22 @@
|
|||
const tbody = document.getElementById('groupsTbody');
|
||||
document.getElementById('groupCount').textContent = `${allGroups.length} group${allGroups.length !== 1 ? 's' : ''}`;
|
||||
if (!allGroups.length) {
|
||||
tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:var(--text-tertiary);padding:var(--sp-8)">No groups yet</td></tr>`;
|
||||
tbody.innerHTML = `<div class="empty-row">No groups yet</div>`;
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = allGroups.map(g => `
|
||||
<tr>
|
||||
<td style="font-weight:500">${esc(g.name)}</td>
|
||||
<td class="text-secondary text-sm">${esc(g.description || '—')}</td>
|
||||
<td class="text-xs text-tertiary">${g.member_count || 0} member${g.member_count !== 1 ? 's' : ''}</td>
|
||||
<td class="text-xs text-tertiary">${g.created_at ? new Date(g.created_at).toLocaleDateString() : '—'}</td>
|
||||
<td>
|
||||
<div style="display:flex;gap:var(--sp-1);justify-content:flex-end;">
|
||||
<button class="btn btn-ghost btn-sm" onclick="editGroup('${g.id}')">Edit</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="confirmDeleteGroup('${g.id}',${esc(JSON.stringify(g.name))})">Delete</button>
|
||||
<div class="wd-list-row groups-row">
|
||||
<div class="wd-list-cell wd-list-cell--name" style="font-weight:500">${esc(g.name)}</div>
|
||||
<div class="wd-list-cell text-secondary text-sm">${esc(g.description || '—')}</div>
|
||||
<div class="wd-list-cell text-xs text-tertiary">${g.member_count || 0} member${g.member_count !== 1 ? 's' : ''}</div>
|
||||
<div class="wd-list-cell text-xs text-tertiary">${g.created_at ? new Date(g.created_at).toLocaleDateString() : '—'}</div>
|
||||
<div class="wd-list-cell wd-list-cell--actions" style="text-align:right">
|
||||
<div style="display:flex;gap:4px;justify-content:flex-end;">
|
||||
<button class="wd-btn wd-btn--ghost wd-btn--sm" onclick="editGroup('${g.id}')">Edit</button>
|
||||
<button class="wd-btn wd-btn--danger wd-btn--sm" onclick="confirmDeleteGroup('${g.id}',${esc(JSON.stringify(g.name))})">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
</div>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
// ── Group panel ───────────────────────────────────────────
|
||||
|
|
@ -571,8 +627,8 @@
|
|||
info: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M8 7v5M8 5v.5"/></svg>`,
|
||||
};
|
||||
const el = document.createElement('div');
|
||||
el.className = `toast toast--${type}`;
|
||||
el.innerHTML = `<div class="toast-icon">${icons[type]||icons.info}</div><div class="toast-body"><div class="toast-title">${esc(title)}</div>${msg?`<div class="toast-msg">${esc(msg)}</div>`:''}</div>`;
|
||||
el.className = `wd-toast wd-toast--${type}`;
|
||||
el.innerHTML = `<div class="wd-toast-icon">${icons[type]||icons.info}</div><div class="wd-toast-body"><div class="wd-toast-title">${esc(title)}</div>${msg?`<div class="wd-toast-msg">${esc(msg)}</div>`:''}</div>`;
|
||||
document.getElementById('toastContainer').appendChild(el);
|
||||
setTimeout(() => el.remove(), 4000);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue