diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43c3f5b..eb062b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 0.1.0.0043 — 2026-07-11
+
+### Added
+- Edit user button in Access tab — admin can change email and groups (delete + recreate approach)
+- Edit user modal with email, groups fields, and new password display
+
## 0.1.0.0039 — 2026-07-11
### Changed
diff --git a/VERSION b/VERSION
index be6a638..40c6030 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.0.0039
+0.1.0.0043
diff --git a/main.go b/main.go
index 0f1c920..0f5e295 100644
--- a/main.go
+++ b/main.go
@@ -1316,6 +1316,8 @@ const adminHTML = `
.btn { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.45rem 0.9rem; border-radius: 6px; font-size: 0.85rem; font-weight: 500; cursor: pointer; border: none; text-decoration: none; transition: all .12s; }
.btn-primary { background: #1a1a2e; color: #fff; }
.btn-primary:hover { background: #2d3748; }
+ .btn-edit { background: #fff; color: #3182ce; border: 1px solid #bee3f8; }
+ .btn-edit:hover { background: #ebf8ff; }
.btn-danger { background: #fff; color: #e53e3e; border: 1px solid #fed7d7; }
.btn-danger:hover { background: #fff5f5; }
.btn-ghost { background: transparent; color: #718096; border: 1px solid #e2e8f0; }
@@ -1459,6 +1461,30 @@ const adminHTML = `
+
+
@@ -1527,8 +1553,9 @@ const adminHTML = `
tbody.innerHTML = users.map(u => {
const groups = (u.groups||[]).map(g => '
' + esc(g) + '').join(' ');
const status = u.disabled ? '
Disabled' : '
Active';
- const del = u.username === 'master' ? '
' : '
';
- return '
| ' + esc(u.username) + ' | ' + esc(u.display_name||'') + ' | ' + esc(u.email||'') + ' | ' + groups + ' | ' + status + ' | ' + del + ' |
';
+ const editBtn = '
';
+ const delBtn = u.username === 'master' ? '
' : '
';
+ return '
| ' + esc(u.username) + ' | ' + esc(u.display_name||'') + ' | ' + esc(u.email||'') + ' | ' + groups + ' | ' + status + ' | ' + editBtn + ' ' + delBtn + ' |
';
}).join('');
}
@@ -1560,6 +1587,65 @@ const adminHTML = `
else alert('Failed to delete user');
}
+ function editUser(username, email, groups) {
+ document.getElementById('edit-username').value = username;
+ document.getElementById('edit-username-display').textContent = 'Editing: ' + username;
+ document.getElementById('edit-email').value = email;
+ const groupList = groups.split(',');
+ document.querySelectorAll('#edit-user-form input[name="edit_groups"]').forEach(cb => {
+ cb.checked = groupList.includes(cb.value);
+ });
+ document.getElementById('edit-result').style.display = 'none';
+ document.getElementById('edit-user-modal').style.display = 'block';
+ }
+
+ function closeEditUserModal() {
+ document.getElementById('edit-user-modal').style.display = 'none';
+ }
+
+ async function saveEditUser(event) {
+ event.preventDefault();
+ const username = document.getElementById('edit-username').value;
+ const email = document.getElementById('edit-email').value;
+ const groups = [];
+ document.querySelectorAll('#edit-user-form input[name="edit_groups"]:checked').forEach(cb => {
+ groups.push(cb.value);
+ });
+
+ // 1. Delete user
+ const delResp = await fetch('/api/users/' + username, { method: 'DELETE' });
+ if (!delResp.ok) { alert('Failed to delete user for re-creation'); return; }
+
+ // 2. Recreate with new data
+ const body = JSON.stringify({
+ users: [{
+ username: username,
+ display_name: username,
+ email: email,
+ groups: groups
+ }]
+ });
+ const createResp = await fetch('/api/users/bulk', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: body
+ });
+
+ const result = await createResp.json();
+ const resultDiv = document.getElementById('edit-result');
+ resultDiv.style.display = 'block';
+
+ if (createResp.ok && result.users && result.users[0]) {
+ const pwd = result.users[0].placeholder_password || '(unchanged)';
+ resultDiv.innerHTML = '
✅ User updated.
New password: ' + pwd + '
Share this with the user.
';
+ closeEditUserModal();
+ loadUsers();
+ } else {
+ resultDiv.innerHTML = '
❌ Failed to recreate user: ' + JSON.stringify(result) + '
';
+ }
+ return false;
+ }
+
function showCreateModal() { document.getElementById('createModal').style.display = 'block'; }
function closeCreateModal() { document.getElementById('createModal').style.display = 'none'; }