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
+11
View File
@@ -1716,3 +1716,14 @@ Keine Commits in dieser Session.
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 [editedConfig, setEditedConfig] = useState<{ [key: string]: string }>({})
const [saving, setSaving] = useState(false)
const [editingShare, setEditingShare] = useState<any | null>(null)
useEffect(() => {
const token = localStorage.getItem("access_token")
@@ -88,6 +89,29 @@ export default function SharesPage() {
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) => {
setNfsShares([...nfsShares, newShare])
setShowNfsDialog(false)
@@ -211,28 +235,98 @@ export default function SharesPage() {
</tr>
</thead>
<tbody>
{sambaShares.map((share) => (
<tr key={share.name} className="border-b border-border/50 hover:bg-muted/30">
<td className="py-3 px-4 font-mono text-xs">{share.name}</td>
<td className="py-3 px-4 font-mono text-xs">{share.path}</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">{share.comment || "—"}</td>
<td className="py-3 px-4 text-right space-x-2">
<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>
))}
{sambaShares.map((share) => {
const isEditing = editingShare?.oldName === share.name
return (
<tr key={share.name} className="border-b border-border/50 hover:bg-muted/30">
<td className="py-3 px-4 text-xs">
{isEditing ? (
<input
type="text"
value={editingShare.name}
onChange={(e) => setEditingShare({ ...editingShare, name: 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.name}</span>
)}
</td>
<td className="py-3 px-4 text-xs">
{isEditing ? (
<input
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>
</table>
</div>
+5
View File
@@ -277,6 +277,11 @@ export class ZFSManagerAPI {
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 }> {
const response = await this.client.delete(`/api/shares/samba/${name}`)
return response.data