From 2f86d52dac03f6b635e8200ab4dd876cb583a572 Mon Sep 17 00:00:00 2001 From: Aluisco Ricardo Date: Sat, 27 Sep 2025 19:05:18 -0300 Subject: [PATCH] Revert and delete objectlock --- backend/router/bucket_assignments.go | 233 ++++++++++++++++ .../bucket-assignment-manager.tsx | 264 ++++++++++++++++++ src/hooks/useBucketAssignments.ts | 104 +++++++ src/pages/buckets/page.tsx | 6 +- src/types/bucket-assignments.ts | 28 ++ 5 files changed, 634 insertions(+), 1 deletion(-) create mode 100644 backend/router/bucket_assignments.go create mode 100644 src/components/bucket-assignment/bucket-assignment-manager.tsx create mode 100644 src/hooks/useBucketAssignments.ts create mode 100644 src/types/bucket-assignments.ts diff --git a/backend/router/bucket_assignments.go b/backend/router/bucket_assignments.go new file mode 100644 index 0000000..8a9e104 --- /dev/null +++ b/backend/router/bucket_assignments.go @@ -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) +} \ No newline at end of file diff --git a/src/components/bucket-assignment/bucket-assignment-manager.tsx b/src/components/bucket-assignment/bucket-assignment-manager.tsx new file mode 100644 index 0000000..3a45e1a --- /dev/null +++ b/src/components/bucket-assignment/bucket-assignment-manager.tsx @@ -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 ( + + +

Asignación de Bucket

+
+ + + +
+ ); + } + + const isAssigned = assignment?.assigned_user_id || assignment?.assigned_tenant_id; + + return ( + + +

+ + Asignación de Bucket: {bucketName} +

+
+ + + {/* Current Assignment Status */} + +

Estado Actual

+ {isAssigned ? ( +
+
+
+ {assignment?.assigned_user && ( + <> + + Asignado a Usuario: + {assignment.assigned_user.username} + + )} + {assignment?.assigned_tenant && ( + <> + + Asignado a Tenant: + {assignment.assigned_tenant.name} + + )} +
+ +
+ + + +
+ Este bucket está asignado y puede ser gestionado por el usuario/tenant especificado. +
+
+
+ ) : ( + + +
+ Este bucket no está asignado a ningún usuario o tenant. +
+
+ )} +
+ + {/* Assignment Form */} + +

+ {isAssigned ? "Cambiar Asignación" : "Nueva Asignación"} +

+ +
+ {/* Assignment Type Selection */} +
+ +
+ + +
+
+ + {/* User Selection */} + {assignmentType === "user" && ( +
+ + +
+ )} + + {/* Tenant Selection */} + {assignmentType === "tenant" && ( +
+ + +
+ )} + + {/* Help Text */} + {assignmentType && ( +
+

¿Qué significa asignar un bucket?

+
    +
  • El usuario/tenant asignado puede gestionar las llaves de acceso del bucket
  • +
  • Puede modificar permisos y configuraciones específicas del bucket
  • +
  • Los administradores siempre mantienen acceso completo
  • +
  • Solo se puede asignar a un usuario O tenant, no ambos
  • +
+
+ )} +
+
+
+ + + + {assignmentType && ( + + )} + +
+ ); +} \ No newline at end of file diff --git a/src/hooks/useBucketAssignments.ts b/src/hooks/useBucketAssignments.ts new file mode 100644 index 0000000..c430226 --- /dev/null +++ b/src/hooks/useBucketAssignments.ts @@ -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(`/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(`/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(`/tenants/${tenantId}/buckets`); + return response?.data; + }, + enabled: !!tenantId, + }); +}; \ No newline at end of file diff --git a/src/pages/buckets/page.tsx b/src/pages/buckets/page.tsx index 6521faf..ebe4ddb 100644 --- a/src/pages/buckets/page.tsx +++ b/src/pages/buckets/page.tsx @@ -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; }, [data, search]); diff --git a/src/types/bucket-assignments.ts b/src/types/bucket-assignments.ts new file mode 100644 index 0000000..9534f62 --- /dev/null +++ b/src/types/bucket-assignments.ts @@ -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[]; +} \ No newline at end of file