Fix: Identities Group Management - bessere Fehlermeldungen

- add_user_to_group: Exception werfen mit stderr Nachricht
- remove_user_from_group: Exception werfen mit stderr Nachricht
- text=True für subprocess für besseres Error Handling
- Router aktualisiert um Fehlermeldungen an Frontend weiterzugeben
- Benutzer sehen jetzt detaillierte Fehlermeldungen beim Gruppe-Entfernen

Behebt: 'Failed to remove user from group' verschluckt die echte Fehlermeldung

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 12:52:53 +02:00
parent 448026d91a
commit a187b625bc
8 changed files with 506 additions and 41 deletions
+298
View File
@@ -771,3 +771,301 @@ Keine Commits in dieser Session.
- update-179.sh | 4 +-- - update-179.sh | 4 +--
--- ---
## 2026-04-22 00:41 00:43 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- 92bed20 ZMB Webui: Complete Project Rebrand & Initial Clean Commit
### Geänderte Dateien
Keine Änderungen ermittelbar.
---
## 2026-04-22 00:44 00:45 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
Keine Änderungen ermittelbar.
---
## 2026-04-22 00:46 00:51 (5m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
Keine Änderungen ermittelbar.
---
## 2026-04-22 00:57 00:58 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
Keine Änderungen ermittelbar.
---
## 2026-04-22 00:58 00:58 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
Keine Änderungen ermittelbar.
---
## 2026-04-22 00:59 01:03 (3m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- 52b9c02 Improve Samba Global Configuration display in WebUI
- 8dbf0e4 .env.local~ gelöscht
### Geänderte Dateien
- backend/services/shares.py | 23 +++++++++++++-------
- frontend/app/shares/page.tsx | 52 ++++++++++++++++++++++++++++++++++++++++++--
- frontend/lib/api.ts | 5 +++++
---
## 2026-04-22 01:03 01:04 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/services/shares.py | 23 +++++++++++++-------
- frontend/app/shares/page.tsx | 52 ++++++++++++++++++++++++++++++++++++++++++--
- frontend/lib/api.ts | 5 +++++
---
## 2026-04-22 01:07 01:09 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- afa74d4 Add Samba registry setup script for initial configuration
- 0504b5b Read Samba config from registry instead of smb.conf
### Geänderte Dateien
- deploy/setup-samba-registry.sh | 65 ++++++++++++++++++++++++++++++++++++++++++
---
## 2026-04-22 01:10 01:11 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- deploy/setup-samba-registry.sh | 65 ++++++++++++++++++++++++++++++++++++++++++
---
## 2026-04-22 01:12 01:14 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- e5afa47 Fix systemd service paths for correct backend location
### Geänderte Dateien
- deploy/zfs-manager-backend.service | 32 ++++++--------------------------
---
## 2026-04-22 01:14 01:20 (6m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- 87e2dec Update service file to use /opt/zmb-webui paths
### Geänderte Dateien
- deploy/zfs-manager-backend.service | 8 ++++----
---
## 2026-04-22 01:21 01:22 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- a373378 Fix: Use full path for 'net' command in Samba config reading
### Geänderte Dateien
- backend/services/shares.py | 2 +-
---
## 2026-04-22 01:26 01:26 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/services/shares.py | 2 +-
---
## 2026-04-22 01:30 01:31 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- 07cf45a Add editable Samba global configuration with net conf setparm
### Geänderte Dateien
- backend/routers/shares.py | 6 ++---
- backend/services/shares.py | 49 +++++++++++--------------------------
- frontend/app/shares/page.tsx | 58 ++++++++++++++++++++++++++++++++++++++++++--
- frontend/lib/api.ts | 5 ++++
---
## 2026-04-22 01:33 01:33 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/routers/shares.py | 6 ++---
- backend/services/shares.py | 49 +++++++++++--------------------------
- frontend/app/shares/page.tsx | 58 ++++++++++++++++++++++++++++++++++++++++++--
- frontend/lib/api.ts | 5 ++++
---
## 2026-04-22 01:34 01:36 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- 71164dd Update /file-sharing page to support new Samba parameters
### Geänderte Dateien
- frontend/app/file-sharing/page.tsx | 48 ++++++++++++++++++++++++--------------
---
## 2026-04-22 01:37 01:37 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/app/file-sharing/page.tsx | 48 ++++++++++++++++++++++++--------------
---
## 2026-04-22 01:38 01:43 (5m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/app/file-sharing/page.tsx | 48 ++++++++++++++++++++++++--------------
---
## 2026-04-22 01:44 01:44 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/app/file-sharing/page.tsx | 48 ++++++++++++++++++++++++--------------
---
## 2026-04-22 01:46 01:47 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/app/file-sharing/page.tsx | 48 ++++++++++++++++++++++++--------------
---
## 2026-04-22 01:58 02:00 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** frontend
### Commits
- ea6b250 Hinzufügen: INSTALLATION.md mit umfassender Installationsanleitung
### Geänderte Dateien
- INSTALLATION.md | 491 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-04-22 10:26 10:26 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- INSTALLATION.md | 491 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-04-22 10:28 10:29 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- INSTALLATION.md | 491 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-04-22 10:31 10:34 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- 90d62fa Dokumentation: Data Persistence für System- und Samba-User
### Geänderte Dateien
- CLAUDE.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
---
## 2026-04-22 12:48 12:49 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- CLAUDE.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
---
## 2026-04-22 12:49 12:50 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
- 6401027 Fix: Navigator Permissions - absolute Pfade für chmod/chown
### Geänderte Dateien
- backend/services/file_manager.py | 6 +++---
---
+4 -8
View File
@@ -229,12 +229,10 @@ async def add_user_to_group(
): ):
"""Add user to group""" """Add user to group"""
try: try:
success = identities_manager.add_user_to_group(username, request.groupname) identities_manager.add_user_to_group(username, request.groupname)
if not success:
raise HTTPException(status_code=400, detail="Failed to add user to group")
return {"status": "added", "username": username, "groupname": request.groupname} return {"status": "added", "username": username, "groupname": request.groupname}
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@router.delete("/users/{username}/groups/{groupname}") @router.delete("/users/{username}/groups/{groupname}")
@@ -245,12 +243,10 @@ async def remove_user_from_group(
): ):
"""Remove user from group""" """Remove user from group"""
try: try:
success = identities_manager.remove_user_from_group(username, groupname) identities_manager.remove_user_from_group(username, groupname)
if not success:
raise HTTPException(status_code=400, detail="Failed to remove user from group")
return {"status": "removed", "username": username, "groupname": groupname} return {"status": "removed", "username": username, "groupname": groupname}
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
# ============== SAMBA USERS ============== # ============== SAMBA USERS ==============
+23
View File
@@ -78,6 +78,29 @@ async def create_samba_share(
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.put("/samba/{name}")
async def update_samba_share(
name: str,
request: CreateSambaShareRequest,
current_user: str = Depends(get_current_user)
):
"""Update Samba share"""
if not request.name.strip() or not request.path.strip():
raise HTTPException(status_code=400, detail="Name and path are required")
try:
success = share_manager.update_samba_share(
name,
request.name,
request.path,
request.comment
)
if not success:
raise HTTPException(status_code=404, detail=f"Samba share '{name}' not found")
return {"status": "updated", "name": request.name}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/samba/{name}") @router.delete("/samba/{name}")
async def delete_samba_share( async def delete_samba_share(
name: str, name: str,
+10 -6
View File
@@ -234,6 +234,7 @@ class IdentitiesManager:
result = subprocess.run( result = subprocess.run(
["/usr/sbin/usermod", "-aG", groupname, username], ["/usr/sbin/usermod", "-aG", groupname, username],
capture_output=True, capture_output=True,
text=True,
timeout=10 timeout=10
) )
@@ -241,11 +242,12 @@ class IdentitiesManager:
logger.info(f"User {username} added to group {groupname}") logger.info(f"User {username} added to group {groupname}")
return True return True
else: else:
logger.error(f"Failed to add user to group: {result.stderr.decode()}") error_msg = result.stderr.strip() or result.stdout.strip() or "Unknown error"
return False logger.error(f"Failed to add user to group: {error_msg}")
raise Exception(f"Failed to add user to group: {error_msg}")
except Exception as e: except Exception as e:
logger.error(f"Error adding user to group: {e}") logger.error(f"Error adding user to group: {e}")
return False raise
def remove_user_from_group(self, username: str, groupname: str) -> bool: def remove_user_from_group(self, username: str, groupname: str) -> bool:
"""Remove user from group""" """Remove user from group"""
@@ -253,6 +255,7 @@ class IdentitiesManager:
result = subprocess.run( result = subprocess.run(
["/usr/sbin/gpasswd", "-d", username, groupname], ["/usr/sbin/gpasswd", "-d", username, groupname],
capture_output=True, capture_output=True,
text=True,
timeout=10 timeout=10
) )
@@ -260,11 +263,12 @@ class IdentitiesManager:
logger.info(f"User {username} removed from group {groupname}") logger.info(f"User {username} removed from group {groupname}")
return True return True
else: else:
logger.error(f"Failed to remove user from group: {result.stderr.decode()}") error_msg = result.stderr.strip() or result.stdout.strip() or "Unknown error"
return False logger.error(f"Failed to remove user from group: {error_msg}")
raise Exception(f"Failed to remove user from group: {error_msg}")
except Exception as e: except Exception as e:
logger.error(f"Error removing user from group: {e}") logger.error(f"Error removing user from group: {e}")
return False raise
def change_password(self, username: str, password: str) -> bool: def change_password(self, username: str, password: str) -> bool:
"""Change user password via chpasswd""" """Change user password via chpasswd"""
+39 -5
View File
@@ -73,13 +73,47 @@ class SharesManager:
with open(SAMBA_CONFIG, 'a') as f: with open(SAMBA_CONFIG, 'a') as f:
f.write(section) f.write(section)
subprocess.run(['smbcontrol', 'smbd', 'reload-config'], capture_output=True, timeout=10) subprocess.run(['/usr/bin/smbcontrol', 'smbd', 'reload-config'], capture_output=True, timeout=10)
logger.info(f"Samba share created: {name}") logger.info(f"Samba share created: {name}")
return True return True
except Exception as e: except Exception as e:
logger.error(f"Error creating Samba share: {e}") logger.error(f"Error creating Samba share: {e}")
return False return False
def update_samba_share(self, old_name: str, new_name: str, path: str, comment: Optional[str] = None) -> bool:
"""Update Samba share in /etc/samba/smb.conf"""
if not SAMBA_CONFIG.exists():
return False
try:
with open(SAMBA_CONFIG, 'r') as f:
content = f.read()
# Find and replace the share section
pattern = rf"\n\[{re.escape(old_name)}\].*?(?=\n\[|\Z)"
match = re.search(pattern, content, flags=re.DOTALL)
if not match:
return False
# Build new section
section = f"\n[{new_name}]\n path = {path}\n"
if comment:
section += f" comment = {comment}\n"
section += f" browseable = yes\n read only = no\n"
new_content = re.sub(pattern, section, content, flags=re.DOTALL)
with open(SAMBA_CONFIG, 'w') as f:
f.write(new_content)
subprocess.run(['/usr/bin/smbcontrol', 'smbd', 'reload-config'], capture_output=True, timeout=10)
logger.info(f"Samba share updated: {old_name}{new_name}")
return True
except Exception as e:
logger.error(f"Error updating Samba share: {e}")
return False
def delete_samba_share(self, name: str) -> bool: def delete_samba_share(self, name: str) -> bool:
"""Remove Samba share from /etc/samba/smb.conf""" """Remove Samba share from /etc/samba/smb.conf"""
if not SAMBA_CONFIG.exists(): if not SAMBA_CONFIG.exists():
@@ -98,7 +132,7 @@ class SharesManager:
with open(SAMBA_CONFIG, 'w') as f: with open(SAMBA_CONFIG, 'w') as f:
f.write(new_content) f.write(new_content)
subprocess.run(['smbcontrol', 'smbd', 'reload-config'], capture_output=True, timeout=10) subprocess.run(['/usr/bin/smbcontrol', 'smbd', 'reload-config'], capture_output=True, timeout=10)
logger.info(f"Samba share deleted: {name}") logger.info(f"Samba share deleted: {name}")
return True return True
except Exception as e: except Exception as e:
@@ -148,7 +182,7 @@ class SharesManager:
with open(NFS_EXPORTS, 'a') as f: with open(NFS_EXPORTS, 'a') as f:
f.write(export_line) f.write(export_line)
subprocess.run(['exportfs', '-r'], capture_output=True, timeout=10) subprocess.run(['/usr/sbin/exportfs', '-r'], capture_output=True, timeout=10)
logger.info(f"NFS share created: {path}") logger.info(f"NFS share created: {path}")
return True return True
except Exception as e: except Exception as e:
@@ -172,7 +206,7 @@ class SharesManager:
with open(NFS_EXPORTS, 'w') as f: with open(NFS_EXPORTS, 'w') as f:
f.writelines(new_lines) f.writelines(new_lines)
subprocess.run(['exportfs', '-r'], capture_output=True, timeout=10) subprocess.run(['/usr/sbin/exportfs', '-r'], capture_output=True, timeout=10)
logger.info(f"NFS share deleted: {path}") logger.info(f"NFS share deleted: {path}")
return True return True
except Exception as e: except Exception as e:
@@ -277,7 +311,7 @@ class SharesManager:
with open(NFS_EXPORTS, 'w') as f: with open(NFS_EXPORTS, 'w') as f:
f.write(content) f.write(content)
subprocess.run(['exportfs', '-r'], capture_output=True, timeout=10) subprocess.run(['/usr/sbin/exportfs', '-r'], capture_output=True, timeout=10)
logger.info("NFS config updated") logger.info("NFS config updated")
return True return True
except Exception as e: except Exception as e:
+11
View File
@@ -1716,3 +1716,14 @@ Keine Commits in dieser Session.
Keine Änderungen ermittelbar. Keine Änderungen ermittelbar.
--- ---
## 2026-04-22 01:48 01:50 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** zmb-webui
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/app/file-sharing/page.tsx | 48 ++++++++++++++++++++++++--------------
---
+116 -22
View File
@@ -26,6 +26,7 @@ export default function SharesPage() {
const [editMode, setEditMode] = useState(false) const [editMode, setEditMode] = useState(false)
const [editedConfig, setEditedConfig] = useState<{ [key: string]: string }>({}) const [editedConfig, setEditedConfig] = useState<{ [key: string]: string }>({})
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const [editingShare, setEditingShare] = useState<any | null>(null)
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("access_token") const token = localStorage.getItem("access_token")
@@ -88,6 +89,29 @@ export default function SharesPage() {
setShowSambaDialog(false) setShowSambaDialog(false)
} }
const handleSaveShare = async () => {
if (!editingShare) return
try {
setSaving(true)
await api.updateSambaShare(editingShare.oldName, {
name: editingShare.name,
path: editingShare.path,
comment: editingShare.comment
})
setSambaShares(sambaShares.map(s =>
s.name === editingShare.oldName
? { name: editingShare.name, path: editingShare.path, comment: editingShare.comment, ...s }
: s
))
setEditingShare(null)
setError(null)
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to save share")
} finally {
setSaving(false)
}
}
const handleNfsCreated = (newShare: any) => { const handleNfsCreated = (newShare: any) => {
setNfsShares([...nfsShares, newShare]) setNfsShares([...nfsShares, newShare])
setShowNfsDialog(false) setShowNfsDialog(false)
@@ -211,28 +235,98 @@ export default function SharesPage() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{sambaShares.map((share) => ( {sambaShares.map((share) => {
<tr key={share.name} className="border-b border-border/50 hover:bg-muted/30"> const isEditing = editingShare?.oldName === share.name
<td className="py-3 px-4 font-mono text-xs">{share.name}</td> return (
<td className="py-3 px-4 font-mono text-xs">{share.path}</td> <tr key={share.name} className="border-b border-border/50 hover:bg-muted/30">
<td className="py-3 px-4 text-xs">{share.valid_users || "—"}</td> <td className="py-3 px-4 text-xs">
<td className="py-3 px-4 text-xs"> {isEditing ? (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700"> <input
{share.read_only ? "RO" : "RW"} type="text"
</span> value={editingShare.name}
</td> onChange={(e) => setEditingShare({ ...editingShare, name: e.target.value })}
<td className="py-3 px-4 text-xs">{share.comment || "—"}</td> className="px-2 py-1 rounded border border-border bg-background text-xs font-mono w-full"
<td className="py-3 px-4 text-right space-x-2"> disabled={saving}
<button />
onClick={() => setDeleteConfirm({ type: "samba", name: share.name })} ) : (
className="text-red-600 hover:text-red-700 transition-colors" <span className="font-mono">{share.name}</span>
title="Delete" )}
> </td>
<Trash2 className="w-4 h-4 inline" /> <td className="py-3 px-4 text-xs">
</button> {isEditing ? (
</td> <input
</tr> type="text"
))} value={editingShare.path}
onChange={(e) => setEditingShare({ ...editingShare, path: e.target.value })}
className="px-2 py-1 rounded border border-border bg-background text-xs font-mono w-full"
disabled={saving}
/>
) : (
<span className="font-mono">{share.path}</span>
)}
</td>
<td className="py-3 px-4 text-xs">{share.valid_users || "—"}</td>
<td className="py-3 px-4 text-xs">
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700">
{share.read_only ? "RO" : "RW"}
</span>
</td>
<td className="py-3 px-4 text-xs">
{isEditing ? (
<input
type="text"
value={editingShare.comment || ""}
onChange={(e) => setEditingShare({ ...editingShare, comment: e.target.value })}
className="px-2 py-1 rounded border border-border bg-background text-xs w-full"
disabled={saving}
placeholder="Optional comment"
/>
) : (
share.comment || "—"
)}
</td>
<td className="py-3 px-4 text-right space-x-2">
{isEditing ? (
<>
<button
onClick={handleSaveShare}
className="text-green-600 hover:text-green-700 transition-colors text-xs font-medium"
disabled={saving}
title="Save"
>
Save
</button>
<button
onClick={() => setEditingShare(null)}
className="text-gray-600 hover:text-gray-700 transition-colors text-xs font-medium"
disabled={saving}
title="Cancel"
>
Cancel
</button>
</>
) : (
<>
<button
onClick={() => setEditingShare({ ...share, oldName: share.name })}
className="text-blue-600 hover:text-blue-700 transition-colors"
title="Edit"
>
Edit
</button>
<button
onClick={() => setDeleteConfirm({ type: "samba", name: share.name })}
className="text-red-600 hover:text-red-700 transition-colors"
title="Delete"
>
<Trash2 className="w-4 h-4 inline" />
</button>
</>
)}
</td>
</tr>
)
})}
</tbody> </tbody>
</table> </table>
</div> </div>
+5
View File
@@ -277,6 +277,11 @@ export class ZFSManagerAPI {
return response.data return response.data
} }
async updateSambaShare(oldName: string, share: SambaShare): Promise<{ status: string }> {
const response = await this.client.put(`/api/shares/samba/${oldName}`, share)
return response.data
}
async deleteSambaShare(name: string): Promise<{ status: string }> { async deleteSambaShare(name: string): Promise<{ status: string }> {
const response = await this.client.delete(`/api/shares/samba/${name}`) const response = await this.client.delete(`/api/shares/samba/${name}`)
return response.data return response.data