mirror of
				https://github.com/khairul169/garage-webui.git
				synced 2025-10-30 22:59:31 +07:00 
			
		
		
		
	Revert and delete objectlock
This commit is contained in:
		
							parent
							
								
									a0fb18192b
								
							
						
					
					
						commit
						2f86d52dac
					
				
							
								
								
									
										233
									
								
								backend/router/bucket_assignments.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								backend/router/bucket_assignments.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,233 @@ | |||||||
|  | package router | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"khairul169/garage-webui/schema" | ||||||
|  | 	"khairul169/garage-webui/utils" | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gorilla/mux" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type BucketAssignments struct{} | ||||||
|  | 
 | ||||||
|  | // AssignBucketRequest represents request to assign bucket to user/tenant | ||||||
|  | type AssignBucketRequest struct { | ||||||
|  | 	BucketID         string  `json:"bucket_id"` | ||||||
|  | 	AssignedUserID   *string `json:"assigned_user_id,omitempty"` | ||||||
|  | 	AssignedTenantID *string `json:"assigned_tenant_id,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetBucketAssignmentResponse represents bucket assignment response | ||||||
|  | type GetBucketAssignmentResponse struct { | ||||||
|  | 	BucketID         string                `json:"bucket_id"` | ||||||
|  | 	BucketName       string                `json:"bucket_name"` | ||||||
|  | 	AssignedUserID   *string               `json:"assigned_user_id,omitempty"` | ||||||
|  | 	AssignedTenantID *string               `json:"assigned_tenant_id,omitempty"` | ||||||
|  | 	AssignedUser     *schema.User          `json:"assigned_user,omitempty"` | ||||||
|  | 	AssignedTenant   *schema.Tenant        `json:"assigned_tenant,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetBucketAssignment returns assignment information for a bucket | ||||||
|  | func (ba *BucketAssignments) GetBucketAssignment(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	// Check permissions | ||||||
|  | 	if !ba.checkPermission(r, schema.PermissionReadBuckets) { | ||||||
|  | 		utils.ResponseErrorStatus(w, nil, http.StatusForbidden) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	vars := mux.Vars(r) | ||||||
|  | 	bucketID := vars["bucketId"] | ||||||
|  | 
 | ||||||
|  | 	// Get bucket info from Garage | ||||||
|  | 	body, err := utils.Garage.Fetch(fmt.Sprintf("/v2/GetBucketInfo?id=%s", bucketID), &utils.FetchOptions{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		utils.ResponseError(w, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var bucket schema.Bucket | ||||||
|  | 	if err := json.Unmarshal(body, &bucket); err != nil { | ||||||
|  | 		utils.ResponseError(w, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	response := GetBucketAssignmentResponse{ | ||||||
|  | 		BucketID:         bucket.ID, | ||||||
|  | 		BucketName:       getBucketDisplayName(&bucket), | ||||||
|  | 		AssignedUserID:   bucket.AssignedUserID, | ||||||
|  | 		AssignedTenantID: bucket.AssignedTenantID, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get assigned user details if exists | ||||||
|  | 	if bucket.AssignedUserID != nil { | ||||||
|  | 		if user, err := utils.DB.GetUser(*bucket.AssignedUserID); err == nil { | ||||||
|  | 			response.AssignedUser = user | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get assigned tenant details if exists | ||||||
|  | 	if bucket.AssignedTenantID != nil { | ||||||
|  | 		if tenant, err := utils.DB.GetTenant(*bucket.AssignedTenantID); err == nil { | ||||||
|  | 			response.AssignedTenant = tenant | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	utils.ResponseSuccess(w, response) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AssignBucket assigns a bucket to a user or tenant | ||||||
|  | func (ba *BucketAssignments) AssignBucket(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	// Check permissions - only admins can assign buckets | ||||||
|  | 	if !ba.checkPermission(r, schema.PermissionWriteBuckets) { | ||||||
|  | 		utils.ResponseErrorStatus(w, nil, http.StatusForbidden) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	vars := mux.Vars(r) | ||||||
|  | 	bucketID := vars["bucketId"] | ||||||
|  | 
 | ||||||
|  | 	var req AssignBucketRequest | ||||||
|  | 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||||||
|  | 		utils.ResponseError(w, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Validate that we have either user or tenant, but not both | ||||||
|  | 	if req.AssignedUserID != nil && req.AssignedTenantID != nil { | ||||||
|  | 		utils.ResponseErrorStatus(w, fmt.Errorf("cannot assign bucket to both user and tenant"), http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Validate user exists if provided | ||||||
|  | 	if req.AssignedUserID != nil { | ||||||
|  | 		if _, err := utils.DB.GetUser(*req.AssignedUserID); err != nil { | ||||||
|  | 			utils.ResponseErrorStatus(w, fmt.Errorf("user not found"), http.StatusNotFound) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Validate tenant exists if provided | ||||||
|  | 	if req.AssignedTenantID != nil { | ||||||
|  | 		if _, err := utils.DB.GetTenant(*req.AssignedTenantID); err != nil { | ||||||
|  | 			utils.ResponseErrorStatus(w, fmt.Errorf("tenant not found"), http.StatusNotFound) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Since Garage doesn't store bucket assignments, we'll store them in our database | ||||||
|  | 	// For now, we'll simulate this by returning success | ||||||
|  | 	// In a real implementation, you'd want to store this in a separate table or metadata | ||||||
|  | 
 | ||||||
|  | 	utils.ResponseSuccess(w, map[string]interface{}{ | ||||||
|  | 		"message":           "Bucket assignment updated successfully", | ||||||
|  | 		"bucket_id":         bucketID, | ||||||
|  | 		"assigned_user_id":  req.AssignedUserID, | ||||||
|  | 		"assigned_tenant_id": req.AssignedTenantID, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnassignBucket removes assignment from a bucket | ||||||
|  | func (ba *BucketAssignments) UnassignBucket(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	// Check permissions - only admins can unassign buckets | ||||||
|  | 	if !ba.checkPermission(r, schema.PermissionWriteBuckets) { | ||||||
|  | 		utils.ResponseErrorStatus(w, nil, http.StatusForbidden) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	vars := mux.Vars(r) | ||||||
|  | 	bucketID := vars["bucketId"] | ||||||
|  | 
 | ||||||
|  | 	// Remove assignment (simulate for now) | ||||||
|  | 	utils.ResponseSuccess(w, map[string]interface{}{ | ||||||
|  | 		"message":   "Bucket assignment removed successfully", | ||||||
|  | 		"bucket_id": bucketID, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ListUserBuckets returns buckets assigned to a specific user | ||||||
|  | func (ba *BucketAssignments) ListUserBuckets(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	// Check permissions | ||||||
|  | 	if !ba.checkPermission(r, schema.PermissionReadBuckets) { | ||||||
|  | 		utils.ResponseErrorStatus(w, nil, http.StatusForbidden) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	vars := mux.Vars(r) | ||||||
|  | 	userID := vars["userId"] | ||||||
|  | 
 | ||||||
|  | 	// Validate user exists | ||||||
|  | 	user, err := utils.DB.GetUser(userID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		utils.ResponseErrorStatus(w, fmt.Errorf("user not found"), http.StatusNotFound) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get all buckets and filter by assignment | ||||||
|  | 	// For now, return empty list since we're not storing assignments yet | ||||||
|  | 	response := map[string]interface{}{ | ||||||
|  | 		"user_id":  userID, | ||||||
|  | 		"username": user.Username, | ||||||
|  | 		"buckets":  []interface{}{}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	utils.ResponseSuccess(w, response) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ListTenantBuckets returns buckets assigned to a specific tenant | ||||||
|  | func (ba *BucketAssignments) ListTenantBuckets(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	// Check permissions | ||||||
|  | 	if !ba.checkPermission(r, schema.PermissionReadBuckets) { | ||||||
|  | 		utils.ResponseErrorStatus(w, nil, http.StatusForbidden) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	vars := mux.Vars(r) | ||||||
|  | 	tenantID := vars["tenantId"] | ||||||
|  | 
 | ||||||
|  | 	// Validate tenant exists | ||||||
|  | 	tenant, err := utils.DB.GetTenant(tenantID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		utils.ResponseErrorStatus(w, fmt.Errorf("tenant not found"), http.StatusNotFound) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get all buckets and filter by assignment | ||||||
|  | 	// For now, return empty list since we're not storing assignments yet | ||||||
|  | 	response := map[string]interface{}{ | ||||||
|  | 		"tenant_id":   tenantID, | ||||||
|  | 		"tenant_name": tenant.Name, | ||||||
|  | 		"buckets":     []interface{}{}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	utils.ResponseSuccess(w, response) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Helper functions | ||||||
|  | 
 | ||||||
|  | // getBucketDisplayName returns the best display name for a bucket | ||||||
|  | func getBucketDisplayName(bucket *schema.Bucket) string { | ||||||
|  | 	if len(bucket.GlobalAliases) > 0 { | ||||||
|  | 		return bucket.GlobalAliases[0] | ||||||
|  | 	} | ||||||
|  | 	if len(bucket.LocalAliases) > 0 { | ||||||
|  | 		return bucket.LocalAliases[0].Alias | ||||||
|  | 	} | ||||||
|  | 	return bucket.ID | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // checkPermission checks if user has required permission | ||||||
|  | func (ba *BucketAssignments) checkPermission(r *http.Request, permission schema.Permission) bool { | ||||||
|  | 	userID := utils.Session.Get(r, "user_id") | ||||||
|  | 	if userID == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	user, err := utils.DB.GetUser(userID.(string)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return user.HasPermission(permission) | ||||||
|  | } | ||||||
							
								
								
									
										264
									
								
								src/components/bucket-assignment/bucket-assignment-manager.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								src/components/bucket-assignment/bucket-assignment-manager.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,264 @@ | |||||||
|  | import { useState } from "react"; | ||||||
|  | import { | ||||||
|  |   Button, | ||||||
|  |   Card, | ||||||
|  |   Loading, | ||||||
|  |   Modal, | ||||||
|  |   Select, | ||||||
|  |   Alert, | ||||||
|  |   Badge, | ||||||
|  | } from "react-daisyui"; | ||||||
|  | import { | ||||||
|  |   useBucketAssignment, | ||||||
|  |   useAssignBucket, | ||||||
|  |   useUnassignBucket, | ||||||
|  | } from "@/hooks/useBucketAssignments"; | ||||||
|  | import { useUsers, useTenants } from "@/hooks/useAdmin"; | ||||||
|  | import { UserPlus, Building2, X, CheckCircle, AlertCircle } from "lucide-react"; | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   bucketId: string; | ||||||
|  |   bucketName: string; | ||||||
|  |   isOpen: boolean; | ||||||
|  |   onClose: () => void; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default function BucketAssignmentManager({ | ||||||
|  |   bucketId, | ||||||
|  |   bucketName, | ||||||
|  |   isOpen, | ||||||
|  |   onClose, | ||||||
|  | }: Props) { | ||||||
|  |   const [assignmentType, setAssignmentType] = useState<"user" | "tenant" | "">(""); | ||||||
|  |   const [selectedUserId, setSelectedUserId] = useState(""); | ||||||
|  |   const [selectedTenantId, setSelectedTenantId] = useState(""); | ||||||
|  | 
 | ||||||
|  |   // Hooks
 | ||||||
|  |   const { data: assignment, isLoading } = useBucketAssignment(bucketId); | ||||||
|  |   const { data: users } = useUsers(); | ||||||
|  |   const { data: tenants } = useTenants(); | ||||||
|  |   const assignBucket = useAssignBucket(); | ||||||
|  |   const unassignBucket = useUnassignBucket(); | ||||||
|  | 
 | ||||||
|  |   const handleAssign = async () => { | ||||||
|  |     const request = { | ||||||
|  |       bucket_id: bucketId, | ||||||
|  |       assigned_user_id: assignmentType === "user" ? selectedUserId : undefined, | ||||||
|  |       assigned_tenant_id: assignmentType === "tenant" ? selectedTenantId : undefined, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       await assignBucket.mutateAsync(request); | ||||||
|  |       onClose(); | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error("Error assigning bucket:", error); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const handleUnassign = async () => { | ||||||
|  |     if (window.confirm("¿Estás seguro de que quieres remover la asignación de este bucket?")) { | ||||||
|  |       try { | ||||||
|  |         await unassignBucket.mutateAsync(bucketId); | ||||||
|  |         onClose(); | ||||||
|  |       } catch (error) { | ||||||
|  |         console.error("Error unassigning bucket:", error); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   if (isLoading) { | ||||||
|  |     return ( | ||||||
|  |       <Modal open={isOpen} onClickBackdrop={onClose}> | ||||||
|  |         <Modal.Header> | ||||||
|  |           <h3>Asignación de Bucket</h3> | ||||||
|  |         </Modal.Header> | ||||||
|  |         <Modal.Body className="text-center py-8"> | ||||||
|  |           <Loading /> | ||||||
|  |         </Modal.Body> | ||||||
|  |       </Modal> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const isAssigned = assignment?.assigned_user_id || assignment?.assigned_tenant_id; | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Modal open={isOpen} onClickBackdrop={onClose} className="w-11/12 max-w-2xl"> | ||||||
|  |       <Modal.Header> | ||||||
|  |         <h3 className="flex items-center gap-2"> | ||||||
|  |           <UserPlus size={20} /> | ||||||
|  |           Asignación de Bucket: {bucketName} | ||||||
|  |         </h3> | ||||||
|  |       </Modal.Header> | ||||||
|  | 
 | ||||||
|  |       <Modal.Body className="space-y-6"> | ||||||
|  |         {/* Current Assignment Status */} | ||||||
|  |         <Card className="bg-base-100 p-4"> | ||||||
|  |           <h4 className="font-semibold mb-3">Estado Actual</h4> | ||||||
|  |           {isAssigned ? ( | ||||||
|  |             <div className="space-y-3"> | ||||||
|  |               <div className="flex items-center justify-between"> | ||||||
|  |                 <div className="flex items-center gap-2"> | ||||||
|  |                   {assignment?.assigned_user && ( | ||||||
|  |                     <> | ||||||
|  |                       <UserPlus size={16} /> | ||||||
|  |                       <span>Asignado a Usuario:</span> | ||||||
|  |                       <Badge color="primary">{assignment.assigned_user.username}</Badge> | ||||||
|  |                     </> | ||||||
|  |                   )} | ||||||
|  |                   {assignment?.assigned_tenant && ( | ||||||
|  |                     <> | ||||||
|  |                       <Building2 size={16} /> | ||||||
|  |                       <span>Asignado a Tenant:</span> | ||||||
|  |                       <Badge color="info">{assignment.assigned_tenant.name}</Badge> | ||||||
|  |                     </> | ||||||
|  |                   )} | ||||||
|  |                 </div> | ||||||
|  |                 <Button | ||||||
|  |                   size="sm" | ||||||
|  |                   variant="outline" | ||||||
|  |                   color="error" | ||||||
|  |                   onClick={handleUnassign} | ||||||
|  |                   loading={unassignBucket.isPending} | ||||||
|  |                 > | ||||||
|  |                   <X size={14} /> | ||||||
|  |                   Remover | ||||||
|  |                 </Button> | ||||||
|  |               </div> | ||||||
|  | 
 | ||||||
|  |               <Alert status="success" className="text-sm"> | ||||||
|  |                 <CheckCircle size={16} /> | ||||||
|  |                 <div> | ||||||
|  |                   Este bucket está asignado y puede ser gestionado por el usuario/tenant especificado. | ||||||
|  |                 </div> | ||||||
|  |               </Alert> | ||||||
|  |             </div> | ||||||
|  |           ) : ( | ||||||
|  |             <Alert status="info" className="text-sm"> | ||||||
|  |               <AlertCircle size={16} /> | ||||||
|  |               <div> | ||||||
|  |                 Este bucket no está asignado a ningún usuario o tenant. | ||||||
|  |               </div> | ||||||
|  |             </Alert> | ||||||
|  |           )} | ||||||
|  |         </Card> | ||||||
|  | 
 | ||||||
|  |         {/* Assignment Form */} | ||||||
|  |         <Card className="bg-base-100 p-4"> | ||||||
|  |           <h4 className="font-semibold mb-3"> | ||||||
|  |             {isAssigned ? "Cambiar Asignación" : "Nueva Asignación"} | ||||||
|  |           </h4> | ||||||
|  | 
 | ||||||
|  |           <div className="space-y-4"> | ||||||
|  |             {/* Assignment Type Selection */} | ||||||
|  |             <div> | ||||||
|  |               <label className="label"> | ||||||
|  |                 <span className="label-text">Tipo de Asignación</span> | ||||||
|  |               </label> | ||||||
|  |               <div className="flex gap-4"> | ||||||
|  |                 <label className="flex items-center gap-2 cursor-pointer"> | ||||||
|  |                   <input | ||||||
|  |                     type="radio" | ||||||
|  |                     name="assignment-type" | ||||||
|  |                     value="user" | ||||||
|  |                     checked={assignmentType === "user"} | ||||||
|  |                     onChange={(e) => setAssignmentType(e.target.value as "user")} | ||||||
|  |                     className="radio radio-primary" | ||||||
|  |                   /> | ||||||
|  |                   <UserPlus size={16} /> | ||||||
|  |                   <span>Usuario</span> | ||||||
|  |                 </label> | ||||||
|  |                 <label className="flex items-center gap-2 cursor-pointer"> | ||||||
|  |                   <input | ||||||
|  |                     type="radio" | ||||||
|  |                     name="assignment-type" | ||||||
|  |                     value="tenant" | ||||||
|  |                     checked={assignmentType === "tenant"} | ||||||
|  |                     onChange={(e) => setAssignmentType(e.target.value as "tenant")} | ||||||
|  |                     className="radio radio-primary" | ||||||
|  |                   /> | ||||||
|  |                   <Building2 size={16} /> | ||||||
|  |                   <span>Tenant</span> | ||||||
|  |                 </label> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             {/* User Selection */} | ||||||
|  |             {assignmentType === "user" && ( | ||||||
|  |               <div> | ||||||
|  |                 <label className="label"> | ||||||
|  |                   <span className="label-text">Seleccionar Usuario</span> | ||||||
|  |                 </label> | ||||||
|  |                 <Select | ||||||
|  |                   value={selectedUserId} | ||||||
|  |                   onChange={(e) => setSelectedUserId(e.target.value)} | ||||||
|  |                   className="w-full" | ||||||
|  |                 > | ||||||
|  |                   <option value="">Seleccionar usuario...</option> | ||||||
|  |                   {users?.map((user) => ( | ||||||
|  |                     <option key={user.id} value={user.id}> | ||||||
|  |                       {user.username} ({user.email}) - {user.role} | ||||||
|  |                     </option> | ||||||
|  |                   ))} | ||||||
|  |                 </Select> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             {/* Tenant Selection */} | ||||||
|  |             {assignmentType === "tenant" && ( | ||||||
|  |               <div> | ||||||
|  |                 <label className="label"> | ||||||
|  |                   <span className="label-text">Seleccionar Tenant</span> | ||||||
|  |                 </label> | ||||||
|  |                 <Select | ||||||
|  |                   value={selectedTenantId} | ||||||
|  |                   onChange={(e) => setSelectedTenantId(e.target.value)} | ||||||
|  |                   className="w-full" | ||||||
|  |                 > | ||||||
|  |                   <option value="">Seleccionar tenant...</option> | ||||||
|  |                   {tenants?.map((tenant) => ( | ||||||
|  |                     <option key={tenant.id} value={tenant.id}> | ||||||
|  |                       {tenant.name} - {tenant.description} | ||||||
|  |                     </option> | ||||||
|  |                   ))} | ||||||
|  |                 </Select> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |             {/* Help Text */} | ||||||
|  |             {assignmentType && ( | ||||||
|  |               <div className="bg-base-200 p-3 rounded text-sm"> | ||||||
|  |                 <p className="font-medium mb-1">¿Qué significa asignar un bucket?</p> | ||||||
|  |                 <ul className="list-disc list-inside space-y-1 text-xs"> | ||||||
|  |                   <li>El usuario/tenant asignado puede gestionar las llaves de acceso del bucket</li> | ||||||
|  |                   <li>Puede modificar permisos y configuraciones específicas del bucket</li> | ||||||
|  |                   <li>Los administradores siempre mantienen acceso completo</li> | ||||||
|  |                   <li>Solo se puede asignar a un usuario O tenant, no ambos</li> | ||||||
|  |                 </ul> | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|  |         </Card> | ||||||
|  |       </Modal.Body> | ||||||
|  | 
 | ||||||
|  |       <Modal.Actions> | ||||||
|  |         <Button onClick={onClose} variant="outline"> | ||||||
|  |           Cancelar | ||||||
|  |         </Button> | ||||||
|  |         {assignmentType && ( | ||||||
|  |           <Button | ||||||
|  |             onClick={handleAssign} | ||||||
|  |             loading={assignBucket.isPending} | ||||||
|  |             disabled={ | ||||||
|  |               assignBucket.isPending || | ||||||
|  |               (assignmentType === "user" && !selectedUserId) || | ||||||
|  |               (assignmentType === "tenant" && !selectedTenantId) | ||||||
|  |             } | ||||||
|  |             color="primary" | ||||||
|  |           > | ||||||
|  |             {isAssigned ? "Cambiar Asignación" : "Asignar Bucket"} | ||||||
|  |           </Button> | ||||||
|  |         )} | ||||||
|  |       </Modal.Actions> | ||||||
|  |     </Modal> | ||||||
|  |   ); | ||||||
|  | } | ||||||
							
								
								
									
										104
									
								
								src/hooks/useBucketAssignments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/hooks/useBucketAssignments.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | |||||||
|  | import api from "@/lib/api"; | ||||||
|  | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; | ||||||
|  | import { | ||||||
|  |   BucketAssignment, | ||||||
|  |   AssignBucketRequest, | ||||||
|  |   UserBucketsResponse, | ||||||
|  |   TenantBucketsResponse, | ||||||
|  | } from "@/types/bucket-assignments"; | ||||||
|  | import { toast } from "sonner"; | ||||||
|  | 
 | ||||||
|  | // Get bucket assignment
 | ||||||
|  | export const useBucketAssignment = (bucketId: string) => { | ||||||
|  |   return useQuery({ | ||||||
|  |     queryKey: ["bucket-assignment", bucketId], | ||||||
|  |     queryFn: async () => { | ||||||
|  |       const response = await api.get<BucketAssignment>(`/buckets/${bucketId}/assignment`); | ||||||
|  |       return response?.data; | ||||||
|  |     }, | ||||||
|  |     enabled: !!bucketId, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Assign bucket to user/tenant
 | ||||||
|  | export const useAssignBucket = () => { | ||||||
|  |   const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |   return useMutation({ | ||||||
|  |     mutationFn: (data: AssignBucketRequest) => | ||||||
|  |       api.put(`/buckets/${data.bucket_id}/assignment`, data), | ||||||
|  |     onSuccess: (_, variables) => { | ||||||
|  |       queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["bucket-assignment", variables.bucket_id], | ||||||
|  |       }); | ||||||
|  |       queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["buckets"], | ||||||
|  |       }); | ||||||
|  |       if (variables.assigned_user_id) { | ||||||
|  |         queryClient.invalidateQueries({ | ||||||
|  |           queryKey: ["user-buckets", variables.assigned_user_id], | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       if (variables.assigned_tenant_id) { | ||||||
|  |         queryClient.invalidateQueries({ | ||||||
|  |           queryKey: ["tenant-buckets", variables.assigned_tenant_id], | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       toast.success("Bucket asignado exitosamente"); | ||||||
|  |     }, | ||||||
|  |     onError: (error: any) => { | ||||||
|  |       toast.error(error.message || "Error al asignar bucket"); | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Unassign bucket
 | ||||||
|  | export const useUnassignBucket = () => { | ||||||
|  |   const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |   return useMutation({ | ||||||
|  |     mutationFn: (bucketId: string) => api.delete(`/buckets/${bucketId}/assignment`), | ||||||
|  |     onSuccess: (_, bucketId) => { | ||||||
|  |       queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["bucket-assignment", bucketId], | ||||||
|  |       }); | ||||||
|  |       queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["buckets"], | ||||||
|  |       }); | ||||||
|  |       queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["user-buckets"], | ||||||
|  |       }); | ||||||
|  |       queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["tenant-buckets"], | ||||||
|  |       }); | ||||||
|  |       toast.success("Asignación de bucket removida exitosamente"); | ||||||
|  |     }, | ||||||
|  |     onError: (error: any) => { | ||||||
|  |       toast.error(error.message || "Error al remover asignación de bucket"); | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Get buckets assigned to a user
 | ||||||
|  | export const useUserBuckets = (userId: string) => { | ||||||
|  |   return useQuery({ | ||||||
|  |     queryKey: ["user-buckets", userId], | ||||||
|  |     queryFn: async () => { | ||||||
|  |       const response = await api.get<UserBucketsResponse>(`/users/${userId}/buckets`); | ||||||
|  |       return response?.data; | ||||||
|  |     }, | ||||||
|  |     enabled: !!userId, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Get buckets assigned to a tenant
 | ||||||
|  | export const useTenantBuckets = (tenantId: string) => { | ||||||
|  |   return useQuery({ | ||||||
|  |     queryKey: ["tenant-buckets", tenantId], | ||||||
|  |     queryFn: async () => { | ||||||
|  |       const response = await api.get<TenantBucketsResponse>(`/tenants/${tenantId}/buckets`); | ||||||
|  |       return response?.data; | ||||||
|  |     }, | ||||||
|  |     enabled: !!tenantId, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
| @ -30,7 +30,11 @@ const BucketsPage = () => { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     buckets = buckets.sort((a, b) => a.aliases[0].localeCompare(b.aliases[0])); |     buckets = buckets.sort((a, b) => { | ||||||
|  |       const aliasA = (a.aliases && a.aliases.length > 0) ? a.aliases[0] : a.id; | ||||||
|  |       const aliasB = (b.aliases && b.aliases.length > 0) ? b.aliases[0] : b.id; | ||||||
|  |       return aliasA.localeCompare(aliasB); | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|     return buckets; |     return buckets; | ||||||
|   }, [data, search]); |   }, [data, search]); | ||||||
|  | |||||||
							
								
								
									
										28
									
								
								src/types/bucket-assignments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/types/bucket-assignments.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | |||||||
|  | import { User, Tenant } from "@/types/admin"; | ||||||
|  | 
 | ||||||
|  | export interface BucketAssignment { | ||||||
|  |   bucket_id: string; | ||||||
|  |   bucket_name: string; | ||||||
|  |   assigned_user_id?: string; | ||||||
|  |   assigned_tenant_id?: string; | ||||||
|  |   assigned_user?: User; | ||||||
|  |   assigned_tenant?: Tenant; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface AssignBucketRequest { | ||||||
|  |   bucket_id: string; | ||||||
|  |   assigned_user_id?: string; | ||||||
|  |   assigned_tenant_id?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface UserBucketsResponse { | ||||||
|  |   user_id: string; | ||||||
|  |   username: string; | ||||||
|  |   buckets: BucketAssignment[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface TenantBucketsResponse { | ||||||
|  |   tenant_id: string; | ||||||
|  |   tenant_name: string; | ||||||
|  |   buckets: BucketAssignment[]; | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aluisco Ricardo
						Aluisco Ricardo