- 781处 tsc -b 错误清零(unknown 属性访问/隐式any返回类型/类型转换不匹配)
- 移除 App.tsx 中已删除的 AnsibleManager/AnsibleWorkflow 导入和路由
- 修复 DeploymentView 中 _prefix 解构导致的属性不存在错误
- 修复 Header/Sidebar/PipelineEditor 中无效的类型断言
- asList() 返回类型 unknown[]→any[], 恢复 AgentMonitor 正常工作
- 放宽 noImplicitAny(临时), 待 types/ 定义完善后逐步恢复
- ESLint: unused=0, console=0, any=1041(Q1 后续降低)
- npm run build: ✅ 0 errors
183 lines
7.6 KiB
TypeScript
183 lines
7.6 KiB
TypeScript
// LDAP 用户导入弹窗(OU 选择 + 用户列表多选 + 导入结果) // Logan.Li
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { Network, Loader2, RefreshCw, CheckCircle, XCircle } from 'lucide-react';
|
|
|
|
interface LdapImportDialogProps {
|
|
importDialogOpen: 'csv' | 'ldap' | null;
|
|
setImportDialogOpen: (v: 'csv' | 'ldap' | null) => void;
|
|
ldapUsers: any[];
|
|
setLdapUsers: ( u: any[]) => void;
|
|
ldapSelected: Set<string>;
|
|
setLdapSelected: (s: Set<string>) => void;
|
|
ldapLoading: boolean;
|
|
ldapImportOUs: any[];
|
|
ldapImportOUDN: string;
|
|
setLdapImportOUDN: (v: string) => void;
|
|
loadLdapUsers: (ouDn?: string) => void;
|
|
handleLdapImport: () => void;
|
|
importing: boolean;
|
|
importResult: any;
|
|
setImportResult: ( r: any) => void;
|
|
}
|
|
|
|
// LdapImportDialog 展示 LDAP 用户检索和多选导入流程 // Logan.Li
|
|
export function LdapImportDialog({
|
|
importDialogOpen,
|
|
setImportDialogOpen,
|
|
ldapUsers,
|
|
setLdapUsers,
|
|
ldapSelected,
|
|
setLdapSelected,
|
|
ldapLoading,
|
|
ldapImportOUs,
|
|
ldapImportOUDN,
|
|
setLdapImportOUDN,
|
|
loadLdapUsers,
|
|
handleLdapImport,
|
|
importing,
|
|
importResult,
|
|
setImportResult,
|
|
}: LdapImportDialogProps) {
|
|
return (
|
|
<Dialog open={importDialogOpen === 'ldap'} onOpenChange={(v) => !v && setImportDialogOpen(null)}>
|
|
<DialogContent className="bg-slate-900 border-slate-800 max-w-2xl">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-slate-100 flex items-center gap-2">
|
|
<Network className="h-5 w-5 text-cyan-400" />
|
|
从 LDAP 导入用户
|
|
</DialogTitle>
|
|
<DialogDescription className="text-slate-500">
|
|
从 LDAP 服务器检索用户列表,选择需要导入的用户
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
{!importResult ? (
|
|
<>
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xs text-slate-500 whitespace-nowrap">选择 OU</span>
|
|
<select
|
|
className="bg-slate-800 border border-slate-700 text-slate-200 text-xs rounded-md px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
value={ldapImportOUDN}
|
|
onChange={(e) => {
|
|
setLdapImportOUDN(e.target.value);
|
|
loadLdapUsers(e.target.value);
|
|
}}
|
|
>
|
|
<option value="">根 (全部)</option>
|
|
{ldapImportOUs.map((ou) => (
|
|
<option key={ou.dn} value={ou.dn}>{ou.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300 ml-auto" onClick={() => loadLdapUsers(ldapImportOUDN)} disabled={ldapLoading}>
|
|
{ldapLoading ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : <RefreshCw className="h-4 w-4 mr-1.5" />}
|
|
刷新
|
|
</Button>
|
|
</div>
|
|
|
|
<p className="text-xs text-slate-500">
|
|
{ldapUsers.length > 0 ? `找到 ${ldapUsers.length} 个用户` : '选择 OU 或点击刷新获取用户列表'}
|
|
</p>
|
|
|
|
{ldapUsers.length > 0 && (
|
|
<div className="max-h-80 overflow-auto rounded-lg border border-slate-800">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow className="border-slate-800 sticky top-0 bg-slate-900">
|
|
<TableHead className="w-10 text-slate-400 text-xs">
|
|
<input
|
|
type="checkbox"
|
|
className="accent-blue-600"
|
|
checked={ldapSelected.size === ldapUsers.length && ldapUsers.length > 0}
|
|
onChange={(e) => {
|
|
if (e.target.checked) {
|
|
setLdapSelected(new Set(ldapUsers.map((u) => u.dn)));
|
|
} else {
|
|
setLdapSelected(new Set());
|
|
}
|
|
}}
|
|
/>
|
|
</TableHead>
|
|
<TableHead className="text-slate-400 text-xs">用户名</TableHead>
|
|
<TableHead className="text-slate-400 text-xs">显示名</TableHead>
|
|
<TableHead className="text-slate-400 text-xs">邮箱</TableHead>
|
|
<TableHead className="text-slate-400 text-xs">DN</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{ldapUsers.map((u) => (
|
|
<TableRow key={u.dn} className="border-slate-800 hover:bg-slate-800/50">
|
|
<TableCell>
|
|
<input
|
|
type="checkbox"
|
|
className="accent-blue-600"
|
|
checked={ldapSelected.has(u.dn)}
|
|
onChange={(e) => {
|
|
const next = new Set(ldapSelected);
|
|
if (e.target.checked) next.add(u.dn);
|
|
else next.delete(u.dn);
|
|
setLdapSelected(next);
|
|
}}
|
|
/>
|
|
</TableCell>
|
|
<TableCell className="text-slate-200 text-xs">{u.uid}</TableCell>
|
|
<TableCell className="text-slate-300 text-xs">{u.cn}</TableCell>
|
|
<TableCell className="text-slate-400 text-xs">{u.mail}</TableCell>
|
|
<TableCell className="text-slate-500 text-xs font-mono truncate max-w-[200px]">{u.dn}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" className="border-slate-700 text-slate-300" onClick={() => { setImportDialogOpen(null); setLdapUsers([]); }}>
|
|
取消
|
|
</Button>
|
|
<Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={handleLdapImport} disabled={importing || ldapSelected.size === 0}>
|
|
{importing ? '导入中...' : `导入选中 (${ldapSelected.size})`}
|
|
</Button>
|
|
</DialogFooter>
|
|
</>
|
|
) : (
|
|
<div className="space-y-4 py-2">
|
|
<div className={`flex items-center gap-3 p-4 rounded-lg ${importResult.code === 200 ? 'bg-emerald-500/10' : 'bg-red-500/10'}`}>
|
|
{importResult.code === 200 ? (
|
|
<CheckCircle className="h-6 w-6 text-emerald-400 shrink-0" />
|
|
) : (
|
|
<XCircle className="h-6 w-6 text-red-400 shrink-0" />
|
|
)}
|
|
<span className="text-sm text-slate-200">{importResult.message}</span>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={() => { setImportDialogOpen(null); setLdapUsers([]); setImportResult(null); }}>
|
|
完成
|
|
</Button>
|
|
</DialogFooter>
|
|
</div>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|