mirror of
https://github.com/khairul169/garage-webui.git
synced 2025-10-14 14:59:32 +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