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;
|
||||
}, [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