import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { Loader2, Trash2, UserPlus } from 'lucide-react'; import { apiClient } from '@/lib/api'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; // 三级权限:大白话描述,普通操作员无需理解术语 export const LEVEL_OPTIONS = [ { value: 'viewer', label: '查看', desc: '只能看流水线与发布成功/失败日志' }, { value: 'operator', label: '发布', desc: '查看 + 触发构建、批量发布、失败重试' }, { value: 'owner', label: '管理', desc: '发布 + 编辑流水线配置、管理成员' }, ]; export function levelLabel(level: string): string { return LEVEL_OPTIONS.find((l) => l.value === level)?.label ?? level; } export const levelBadgeStyle: Record = { viewer: { bg: '#f6f8fa', color: '#6e7781' }, operator: { bg: '#ddf4ff', color: '#0969da' }, owner: { bg: '#dafbe1', color: '#1a7f37' }, }; interface Props { pipelineId: number; pipelineName?: string; open: boolean; onClose: () => void; } // 单流水线成员弹窗:直接添加成员(选用户 + 点选等级),命中的权限组只读展示 export default function PipelineMembersDialog({ pipelineId, pipelineName, open, onClose }: Props) { const queryClient = useQueryClient(); const [username, setUsername] = useState(''); const [level, setLevel] = useState('operator'); const membersQuery = useQuery({ queryKey: ['pipeline-members', pipelineId], queryFn: () => apiClient.listPipelineMembers(pipelineId), enabled: open, }); const { data: users } = useQuery({ queryKey: ['users'], queryFn: apiClient.listUsers, enabled: open, }); const invalidate = () => queryClient.invalidateQueries({ queryKey: ['pipeline-members', pipelineId] }); const addMutation = useMutation({ mutationFn: () => apiClient.addPipelineMember(pipelineId, { username, level }), onSuccess: () => { toast.success(`已授权 ${username}`); setUsername(''); invalidate(); }, onError: (err: Error) => toast.error(err.message), }); const removeMutation = useMutation({ mutationFn: (mid: any) => apiClient.deletePipelineMember(pipelineId, mid), onSuccess: () => { toast.success('已移除授权'); invalidate(); }, onError: (err: Error) => toast.error(err.message), }); const members: any[] = membersQuery.data?.members ?? []; const matchedRoles: any[] = membersQuery.data?.matchedRoles ?? []; const userOptions: any[] = users ?? []; const submit = () => { if (!username) { toast.error('请选择用户'); return; } addMutation.mutate(); }; const inputStyle = { background: 'var(--panel-bg-secondary)', borderColor: 'var(--border-color-light)' }; return ( !v && onClose()}> 成员授权 — {pipelineName || `流水线 #${pipelineId}`} {/* 添加成员 */}
{LEVEL_OPTIONS.map((opt) => ( ))}
{LEVEL_OPTIONS.find((l) => l.value === level)?.desc}
{/* 已有直接授权 */}
直接授权({members.length})
{membersQuery.isLoading ? (
加载中...
) : members.length === 0 ? (
暂无直接授权成员
) : (
{members.map((m) => { const badge = levelBadgeStyle[m.level] ?? levelBadgeStyle.viewer; return (
{m.username} {levelLabel(m.level)}
); })}
)}
{/* 命中的权限组(只读) */} {matchedRoles.length > 0 && (
命中的权限组(只读,在「流水线权限」页管理)
{matchedRoles.map((r, i) => { const badge = levelBadgeStyle[r.level] ?? levelBadgeStyle.viewer; return ( {r.name} · {r.mode === 'pattern' ? r.pattern : '多选'} ·{' '} {levelLabel(r.level)} ); })}
)}
); }