Add group management and owner/group dropdowns in permission editor

Backend:
- _list_groups(): reads /etc/group, returns groups with gid >= 1000
  plus a small whitelist (root, staff, sambashare, www-data)
- GET/POST /api/groups: list and create groups (groupadd)
- DELETE /api/groups/<name>: delete group (groupdel)
- POST /api/groups/<name>/members: add user to group (gpasswd -a)
- DELETE /api/groups/<name>/members/<user>: remove user (gpasswd -d)

Frontend:
- Users tab now has a Groups section: table with GID, member badges
  with × remove buttons, per-row "add member" select populated with
  users not already in the group, delete group button
- loadUsers() caches result in _cachedUsers for groups table to use
- openEditModal is now async; fetches /api/users + /api/groups before
  rendering so Owner and Group inputs have datalist autocomplete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Sonnet 4.6 committed 2026-06-20 00:47:43 +02:00
1 parent 302a5cb023
commit 596365b805
5 files changed
+227 -11

No files matched your search

+16 -4
View File
@@ -100,22 +100,35 @@ async function addNFSShare() {
let _editCtx = null;
function openEditModal(type, share) {
async function openEditModal(type, share) {
_editCtx = { type, original: share };
document.getElementById('editModalTitle').textContent =
type === 'smb' ? `Edit SMB Share — ${esc(share.name)}` : `Edit NFS Export — ${esc(share.path)}`;
document.getElementById('editModalBody').innerHTML =
`<div style="color:var(--muted); padding:.5rem 0">Loading…</div>`;
new bootstrap.Modal(document.getElementById('editModal')).show();
const [ur, gr] = await Promise.all([apiFetch('/api/users'), apiFetch('/api/groups')]);
const users = ur ? await ur.json() : [];
const groups = gr ? await gr.json() : [];
const ownerOptions = ['root', ...users.map(u => u.name)]
.map(n => `<option value="${esc(n)}">`).join('');
const groupOptions = groups.map(g => `<option value="${esc(g.name)}">`).join('');
const permSection = `
<datalist id="ownerList">${ownerOptions}</datalist>
<datalist id="groupList">${groupOptions}</datalist>
<hr style="border-color:var(--border); margin:1.25rem 0 1rem">
<div class="fw-semibold mb-3" style="font-size:.9rem; color:var(--text)">Folder Permissions</div>
<div class="row g-2 mb-3">
<div class="col-5">
<label class="form-label" style="color:var(--muted); font-size:.85rem">Owner</label>
<input type="text" id="editOwner" class="form-control" value="${esc(share.owner || '')}" placeholder="root">
<input type="text" id="editOwner" class="form-control" value="${esc(share.owner || '')}" placeholder="root" list="ownerList">
</div>
<div class="col-5">
<label class="form-label" style="color:var(--muted); font-size:.85rem">Group</label>
<input type="text" id="editGroup" class="form-control" value="${esc(share.group || '')}" placeholder="root">
<input type="text" id="editGroup" class="form-control" value="${esc(share.group || '')}" placeholder="root" list="groupList">
</div>
<div class="col-2">
<label class="form-label" style="color:var(--muted); font-size:.85rem">Mode</label>
@@ -167,7 +180,6 @@ function openEditModal(type, share) {
}
document.getElementById('editModalBody').innerHTML = body;
new bootstrap.Modal(document.getElementById('editModal')).show();
}
async function submitEdit() {