import { useState, useEffect } from 'react'; import { Plus, Trash2, Pencil, RefreshCw, Loader2, Users, UserPlus, UserX, Shield } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { toast } from 'sonner'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { deployApi } from '@/api'; import type { JsonData } from '@/types/common'; interface Group { id: number; name: string; description: string; members?: GroupMember[]; created_by: string; created_at: string; } interface GroupMember { id: number; group_id: number; username: string; created_at: string; } export default function UserGroupsManager() { const _confirm = useConfirm(); const [groups, setGroups] = useState([]); const [loading, setLoading] = useState(true); const [expanded, setExpanded] = useState>({}); // Group form const [formOpen, setFormOpen] = useState(false); const [editId, setEditId] = useState(null); const [formName, setFormName] = useState(''); const [formDesc, setFormDesc] = useState(''); const [saving, setSaving] = useState(false); // Add member — per-group input state const [memberUsers, setMemberUsers] = useState>({}); const loadGroups = async () => { setLoading(true); try { const res = await deployApi.getGroups(); setGroups((res as JsonData)?.list || []); } catch { toast.error('加载失败'); } finally { setLoading(false); } }; useEffect(() => { loadGroups(); }, []); const handleSave = async () => { if (!formName.trim()) { toast.error('请输入组名'); return; } setSaving(true); try { if (editId) await deployApi.updateGroup(editId, { name: formName, description: formDesc }); else await deployApi.createGroup({ name: formName, description: formDesc }); toast.success(editId ? '已更新' : '已创建'); setFormOpen(false); loadGroups(); } catch { toast.error('操作失败'); } finally { setSaving(false); } }; const handleDelete = async (g: Group) => { const ok = await _confirm({ title: '删除用户组', description: `确定删除「${g.name}」?所有成员和流水线授权将同时移除。`, variant: 'destructive' }); if (!ok) return; try { await deployApi.deleteGroup(g.id); toast.success('已删除'); loadGroups(); } catch { toast.error('删除失败'); } }; const handleAddMember = async (groupId: number) => { const username = (memberUsers[groupId] || '').trim(); if (!username) { toast.error('输入用户名'); return; } try { await deployApi.addGroupMember(groupId, { username }); toast.success('已添加'); loadGroups(); setMemberUsers(prev => ({ ...prev, [groupId]: '' })); } catch { toast.error('添加失败'); } }; const handleRemoveMember = async (groupId: number, memberId: number, username: string) => { const ok = await _confirm({ title: '移除成员', description: `确定从组中移除「${username}」?` }); if (!ok) return; try { await deployApi.removeGroupMember(groupId, memberId); toast.success('已移除'); loadGroups(); } catch { toast.error('移除失败'); } }; const toggleExpand = (gid: number) => setExpanded(p => ({ ...p, [gid]: !p[gid] })); return (

用户组管理

创建用户组,将组授权给流水线,组内成员自动获得流水线权限

{loading ? (
) : groups.length === 0 ? (

暂无用户组,点击「新建用户组」创建

) : (
{groups.map(g => (
toggleExpand(g.id)}>
{g.name}
{g.description || '暂无描述'} · {(g.members || []).length} 位成员
{expanded[g.id] && (
{/* Add member */}
setMemberUsers(prev => ({ ...prev, [g.id]: e.target.value }))} placeholder="输入用户名" className="h-8 text-sm" onKeyDown={e => e.key === 'Enter' && handleAddMember(g.id)} />
{/* Members list */} {(g.members || []).length === 0 ? (
暂无成员
) : (
{(g.members || []).map(m => ( {m.username} ))}
)}
)}
))}
)} {/* Form Dialog */} {editId ? '编辑用户组' : '新建用户组'}
setFormName(e.target.value)} placeholder="例如: 后端开发组" />
setFormDesc(e.target.value)} placeholder="可选" />
); }