import React from "react"; import { Button } from "@/components/ui/button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog.tsx"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table.tsx"; import { Input } from "@/components/ui/input.tsx"; import { Label } from "@/components/ui/label.tsx"; import { Textarea } from "@/components/ui/textarea.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select.tsx"; import { Badge } from "@/components/ui/badge.tsx"; import { Shield, Plus, Edit, Trash2, Users, Check, ChevronsUpDown, } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { useConfirmation } from "@/hooks/use-confirmation.ts"; import { getRoles, createRole, updateRole, deleteRole, getUserList, getUserRoles, assignRoleToUser, removeRoleFromUser, type Role, type UserRole, } from "@/ui/main-axios.ts"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, } from "@/components/ui/command.tsx"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover.tsx"; import { cn } from "@/lib/utils"; interface User { id: string; username: string; is_admin: boolean; } export function RoleManagement(): React.ReactElement { const { t } = useTranslation(); const { confirmWithToast } = useConfirmation(); const [roles, setRoles] = React.useState([]); const [users, setUsers] = React.useState([]); const [loading, setLoading] = React.useState(false); // Create/Edit Role Dialog const [roleDialogOpen, setRoleDialogOpen] = React.useState(false); const [editingRole, setEditingRole] = React.useState(null); const [roleName, setRoleName] = React.useState(""); const [roleDisplayName, setRoleDisplayName] = React.useState(""); const [roleDescription, setRoleDescription] = React.useState(""); // Assign Role Dialog const [assignDialogOpen, setAssignDialogOpen] = React.useState(false); const [selectedUserId, setSelectedUserId] = React.useState(""); const [selectedRoleId, setSelectedRoleId] = React.useState( null, ); const [userRoles, setUserRoles] = React.useState([]); // Combobox states const [userComboOpen, setUserComboOpen] = React.useState(false); const [roleComboOpen, setRoleComboOpen] = React.useState(false); // Load roles const loadRoles = React.useCallback(async () => { setLoading(true); try { const response = await getRoles(); setRoles(response.roles || []); } catch (error) { toast.error(t("rbac.failedToLoadRoles")); console.error("Failed to load roles:", error); setRoles([]); } finally { setLoading(false); } }, [t]); // Load users const loadUsers = React.useCallback(async () => { try { const response = await getUserList(); // Map UserInfo to User format const mappedUsers = (response.users || []).map((user) => ({ id: user.id, username: user.username, is_admin: user.is_admin, })); setUsers(mappedUsers); } catch (error) { console.error("Failed to load users:", error); setUsers([]); } }, []); React.useEffect(() => { loadRoles(); loadUsers(); }, [loadRoles, loadUsers]); // Create role const handleCreateRole = () => { setEditingRole(null); setRoleName(""); setRoleDisplayName(""); setRoleDescription(""); setRoleDialogOpen(true); }; // Edit role const handleEditRole = (role: Role) => { setEditingRole(role); setRoleName(role.name); setRoleDisplayName(role.displayName); setRoleDescription(role.description || ""); setRoleDialogOpen(true); }; // Save role const handleSaveRole = async () => { if (!roleDisplayName.trim()) { toast.error(t("rbac.roleDisplayNameRequired")); return; } if (!editingRole && !roleName.trim()) { toast.error(t("rbac.roleNameRequired")); return; } try { if (editingRole) { // Update existing role await updateRole(editingRole.id, { displayName: roleDisplayName, description: roleDescription || null, }); toast.success(t("rbac.roleUpdatedSuccessfully")); } else { // Create new role await createRole({ name: roleName, displayName: roleDisplayName, description: roleDescription || null, }); toast.success(t("rbac.roleCreatedSuccessfully")); } setRoleDialogOpen(false); loadRoles(); } catch (error) { toast.error(t("rbac.failedToSaveRole")); } }; // Delete role const handleDeleteRole = async (role: Role) => { const confirmed = await confirmWithToast({ title: t("rbac.confirmDeleteRole"), description: t("rbac.confirmDeleteRoleDescription", { name: role.displayName, }), confirmText: t("common.delete"), cancelText: t("common.cancel"), }); if (!confirmed) return; try { await deleteRole(role.id); toast.success(t("rbac.roleDeletedSuccessfully")); loadRoles(); } catch (error) { toast.error(t("rbac.failedToDeleteRole")); } }; // Open assign dialog const handleOpenAssignDialog = async () => { setSelectedUserId(""); setSelectedRoleId(null); setUserRoles([]); setAssignDialogOpen(true); }; // Load user roles when user is selected const handleUserSelect = async (userId: string) => { setSelectedUserId(userId); setUserRoles([]); if (!userId) return; try { const response = await getUserRoles(userId); setUserRoles(response.roles || []); } catch (error) { console.error("Failed to load user roles:", error); setUserRoles([]); } }; // Assign role to user const handleAssignRole = async () => { if (!selectedUserId || !selectedRoleId) { toast.error(t("rbac.selectUserAndRole")); return; } try { await assignRoleToUser(selectedUserId, selectedRoleId); const selectedUser = users.find((u) => u.id === selectedUserId); toast.success( t("rbac.roleAssignedSuccessfully", { username: selectedUser?.username || selectedUserId, }), ); setSelectedRoleId(null); handleUserSelect(selectedUserId); } catch (error) { toast.error(t("rbac.failedToAssignRole")); } }; // Remove role from user const handleRemoveUserRole = async (roleId: number) => { if (!selectedUserId) return; try { await removeRoleFromUser(selectedUserId, roleId); const selectedUser = users.find((u) => u.id === selectedUserId); toast.success( t("rbac.roleRemovedSuccessfully", { username: selectedUser?.username || selectedUserId, }), ); handleUserSelect(selectedUserId); } catch (error) { toast.error(t("rbac.failedToRemoveRole")); } }; return (
{/* Roles Section */}

{t("rbac.roleManagement")}

{t("rbac.roleName")} {t("rbac.displayName")} {t("rbac.description")} {t("rbac.type")} {t("common.actions")} {loading ? ( {t("common.loading")} ) : roles.length === 0 ? ( {t("rbac.noRoles")} ) : ( roles.map((role) => ( {role.name} {t(role.displayName)} {role.description || "-"} {role.isSystem ? ( {t("rbac.systemRole")} ) : ( {t("rbac.customRole")} )}
{!role.isSystem && ( <> )}
)) )}
{/* User-Role Assignment Section */}

{t("rbac.userRoleAssignment")}

{/* Create/Edit Role Dialog */} {editingRole ? t("rbac.editRole") : t("rbac.createRole")} {editingRole ? t("rbac.editRoleDescription") : t("rbac.createRoleDescription")}
{!editingRole && (
setRoleName(e.target.value.toLowerCase())} placeholder="developer" disabled={!!editingRole} />

{t("rbac.roleNameHint")}

)}
setRoleDisplayName(e.target.value)} placeholder={t("rbac.displayNamePlaceholder")} />