diff --git a/frontend/src/api/instanceGroup.ts b/frontend/src/api/instanceGroup.ts index 89ff688..2cdadb4 100644 --- a/frontend/src/api/instanceGroup.ts +++ b/frontend/src/api/instanceGroup.ts @@ -6,14 +6,15 @@ export interface InstanceGroup { description?: string; db_type: string; permissions?: string[]; - access_type?: 'readonly' | 'write'; allowed_databases?: string[]; is_active: boolean; created_at?: string; updated_at?: string; - instance_count?: number; - user_count?: number; - instances?: { id: number; name: string; db_type: string; host: string; port: number; env: string; status: string }[]; + readonly_instance_count: number; + write_instance_count: number; + user_count: number; + readonly_instances?: { id: number; name: string; db_type: string; host: string; port: number; env: string; status: string }[]; + write_instances?: { id: number; name: string; db_type: string; host: string; port: number; env: string; status: string }[]; users?: { id: number; username: string; display_name: string; role: string }[]; } @@ -23,10 +24,10 @@ export const instanceGroupApi = { create: (data: any) => request.post('/instance-groups', data), update: (id: number, data: any) => request.put(`/instance-groups/${id}`, data), delete: (id: number) => request.delete(`/instance-groups/${id}`), - assignInstances: (groupId: number, instanceIds: number[]) => - request.post(`/instance-groups/${groupId}/instances`, { instance_ids: instanceIds }), - removeInstance: (groupId: number, instanceId: number) => - request.delete(`/instance-groups/${groupId}/instances/${instanceId}`), + assignInstances: (groupId: number, instanceIds: number[], accessType: string = 'readonly') => + request.post(`/instance-groups/${groupId}/instances`, { instance_ids: instanceIds, access_type: accessType }), + removeInstance: (groupId: number, instanceId: number, accessType?: string) => + request.delete(`/instance-groups/${groupId}/instances/${instanceId}`, { params: accessType ? { access_type: accessType } : undefined }), listUsers: (groupId: number) => request.get(`/instance-groups/${groupId}/users`), assignUsers: (groupId: number, userIds: number[], permissions?: string[]) => request.post(`/instance-groups/${groupId}/users`, { user_ids: userIds, permissions }), diff --git a/frontend/src/pages/InstanceGroupPage.tsx b/frontend/src/pages/InstanceGroupPage.tsx index 262d0a5..e1f37a3 100644 --- a/frontend/src/pages/InstanceGroupPage.tsx +++ b/frontend/src/pages/InstanceGroupPage.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import { - Plus, Search, Edit2, Trash2, Server, Users, ChevronRight, X, - Eye, PenLine, ChevronLeft, ChevronRight as ChevRight, + Plus, Search, Edit2, Trash2, Server, Users, ChevronRight, + Eye, PenLine, ChevronLeft, ChevronRight as ChevRight, X, } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { instanceGroupApi, type InstanceGroup } from '@/api/instanceGroup'; @@ -11,11 +11,6 @@ import { useActionPermission } from '@/hooks/useAuth'; /* ───────── Constants ───────── */ -const ACCESS_TYPES = [ - { value: 'readonly', label: '只读查看', desc: '关联只读/从库,用于查询数据', color: 'text-emerald-400', bg: 'bg-emerald-500/15', icon: Eye }, - { value: 'write', label: '写入变更', desc: '关联读写/主库,用于数据变更工单', color: 'text-amber-400', bg: 'bg-amber-500/15', icon: PenLine }, -] as const; - const DB_TYPE_COLORS: Record = { mysql: 'bg-[rgba(0,117,143,0.15)] text-[#26a69a]', pg: 'bg-[rgba(51,103,145,0.15)] text-[#42a5f5]', @@ -33,9 +28,10 @@ interface TransferProps { items: TransferItem[]; selected: string[]; onChange: (v: string[]) => void; + emptyText?: string; } -function Transfer({ title, items, selected, onChange }: TransferProps) { +function Transfer({ title, items, selected, onChange, emptyText = '请从左侧选择实例' }: TransferProps) { const [search, setSearch] = useState(''); const [pendingAdd, setPendingAdd] = useState>(new Set()); const [pendingRemove, setPendingRemove] = useState>(new Set()); @@ -60,7 +56,7 @@ function Transfer({ title, items, selected, onChange }: TransferProps) {
可选实例 - {unselected.length} 项 + {unselected.length}
{title} - {selectedFiltered.length} 项 + {selectedFiltered.length}
- {selectedFiltered.length === 0 &&
请从左侧选择实例
} + {selectedFiltered.length === 0 &&
{emptyText}
} {selectedFiltered.map(item => (