refactor: 全局代码模块化重构 — 所有文件<700行

后端 handler 拆分:
- automation.go(1067→4文件), server.go(1011→5文件), ldap.go(815→3文件), user.go(704→2文件)

前端组件拆分:
- SystemManager(1782→569), AlertManager(1133→220), K8sClusters(1062→4文件)
- Dashboard(941→4文件), ServerManager(905→640), ApprovalFlow(830→548)
- AlertCenter(775→693), AIPredictiveAnalytics(760→670)
- CloudBilling(728→663), GrafanaDashboard(717→643), sidebar(726→689)

Mock 数据模块化:
- mocks/data.ts(2552行→barrel+11个domain模块)

新增 CLAUDE.md 项目全局开发规范

修复: AlertCenter/LogManager ListResponse.data→.list 类型不匹配
验证: go build ✅ + tsc --noEmit ✅ + npm run build ✅
This commit is contained in:
attacker committed 2026-06-15 01:32:15 +08:00
1 parent e8dbe65955
commit 73b5a48df9
37 files changed
+5148 -5237

No files matched your search

@@ -0,0 +1,462 @@
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
ChevronDown,
Users,
Key,
RefreshCw,
Plus,
UserPlus,
Download,
Edit,
Trash2,
Loader2,
} from 'lucide-react';
interface LdapManagementSectionProps {
ldapConfig: any;
ldapMgmtOpen: boolean;
setLdapMgmtOpen: (v: boolean) => void;
ldapOUs: any[];
selectedOUDN: string;
setSelectedOUDN: (v: string) => void;
ldapUsers: any[];
selectedLDAPUsers: string[];
setSelectedLDAPUsers: (v: string[]) => void;
loadLdapOUs: () => void;
loadLdapOUUsers: (ouDn?: string) => void;
handleLdapImport: () => void;
importing: boolean;
importFeedback: string | null;
importResultOpen: boolean;
setImportResultOpen: (v: boolean) => void;
// Create OU
createOUOpen: boolean;
setCreateOUOpen: (v: boolean) => void;
ouForm: { ou_name: string; parent_dn: string; description: string };
setOuForm: React.Dispatch<React.SetStateAction<{ ou_name: string; parent_dn: string; description: string }>>;
handleCreateOU: () => void;
ldapSaving: boolean;
// Create LDAP user
createUserOpen: boolean;
setCreateUserOpen: (v: boolean) => void;
newUser: { username: string; password: string; display_name: string; email: string; ou_dn: string };
setNewUser: React.Dispatch<React.SetStateAction<{ username: string; password: string; display_name: string; email: string; ou_dn: string }>>;
handleCreateLDAPUser: () => void;
// LDAP user test
ldapUserTestOpen: boolean;
setLdapUserTestOpen: (v: boolean) => void;
ldapTestUser: string;
setLdapTestUser: (v: string) => void;
ldapTestPass: string;
setLdapTestPass: (v: string) => void;
ldapUserTesting: boolean;
ldapUserTestResult: any;
handleLdapUserTest: () => void;
// Edit LDAP user
editLdapUser: any | null;
setEditLdapUser: (u: any | null) => void;
editLdapForm: { cn: string; mail: string; phone: string; title: string };
setEditLdapForm: React.Dispatch<React.SetStateAction<{ cn: string; mail: string; phone: string; title: string }>>;
handleUpdateLdapUser: () => void;
handleDeleteLDAPEntry: (dn: string, silent?: boolean) => void;
openEditLdapUser: (user: any) => void;
}
export function LdapManagementSection(props: LdapManagementSectionProps) {
const {
ldapConfig,
ldapMgmtOpen,
setLdapMgmtOpen,
ldapOUs,
selectedOUDN,
setSelectedOUDN,
ldapUsers,
selectedLDAPUsers,
setSelectedLDAPUsers,
loadLdapOUs,
loadLdapOUUsers,
handleLdapImport,
importing,
importFeedback,
importResultOpen,
setImportResultOpen,
createOUOpen,
setCreateOUOpen,
ouForm,
setOuForm,
handleCreateOU,
ldapSaving,
createUserOpen,
setCreateUserOpen,
newUser,
setNewUser,
handleCreateLDAPUser,
ldapUserTestOpen,
setLdapUserTestOpen,
ldapTestUser,
setLdapTestUser,
ldapTestPass,
setLdapTestPass,
ldapUserTesting,
ldapUserTestResult,
handleLdapUserTest,
editLdapUser,
setEditLdapUser,
editLdapForm,
setEditLdapForm,
handleUpdateLdapUser,
handleDeleteLDAPEntry,
openEditLdapUser,
} = props;
return (
<>
{/* 折叠/展开按钮 */}
<button
onClick={() => {
const open = !ldapMgmtOpen;
setLdapMgmtOpen(open);
if (open && ldapOUs.length === 0) loadLdapOUs();
}}
className="w-full flex items-center gap-2 px-4 py-2.5 bg-slate-900/50 hover:bg-slate-800/50 text-slate-400 text-sm transition-colors"
>
<ChevronDown className={`h-4 w-4 transition-transform ${ldapMgmtOpen ? '' : '-rotate-90'}`} />
<Users className="h-4 w-4 text-amber-400" />
<span className="text-slate-300">LDAP 账号管理</span>
</button>
{ldapMgmtOpen && (
<div className="bg-slate-900/30 border-t border-slate-800/50 px-4 py-4 space-y-4">
<div className="flex items-center gap-3 flex-wrap">
{/* 账号验证测试 */}
<Dialog open={ldapUserTestOpen} onOpenChange={setLdapUserTestOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-slate-200">
<Key className="h-3.5 w-3.5 mr-1" />
账号验证
</Button>
</DialogTrigger>
<DialogContent className="max-w-md bg-slate-900 border-slate-800">
<DialogHeader>
<DialogTitle className="text-slate-100">LDAP 账号验证测试</DialogTitle>
<DialogDescription className="text-slate-500">输入 LDAP 用户凭据进行认证测试</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-2">
<div>
<label className="text-xs text-slate-500 mb-1 block">用户名</label>
<Input
value={ldapTestUser}
onChange={(e) => setLdapTestUser(e.target.value)}
placeholder="zhangsan"
className="bg-slate-800/50 border-slate-700 text-slate-200 placeholder:text-slate-600"
/>
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">密码</label>
<Input
type="password"
value={ldapTestPass}
onChange={(e) => setLdapTestPass(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleLdapUserTest()}
placeholder="••••••"
className="bg-slate-800/50 border-slate-700 text-slate-200 placeholder:text-slate-600"
/>
</div>
{ldapUserTestResult && (
<div className={`p-3 rounded-lg text-sm ${ldapUserTestResult.success ? 'bg-emerald-500/10 text-emerald-300' : 'bg-red-500/10 text-red-300'}`}>
{ldapUserTestResult.message}
{ldapUserTestResult.user_info && (
<pre className="mt-2 text-xs whitespace-pre-wrap">{JSON.stringify(ldapUserTestResult.user_info, null, 2)}</pre>
)}
</div>
)}
<Button
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
onClick={handleLdapUserTest}
disabled={ldapUserTesting || !ldapTestUser}
>
{ldapUserTesting ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
验证
</Button>
</div>
</DialogContent>
</Dialog>
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-slate-200" onClick={loadLdapOUs}>
<RefreshCw className="h-3.5 w-3.5 mr-1" />
刷新
</Button>
{/* OU 选择 */}
<Select value={selectedOUDN} onValueChange={(v) => { setSelectedOUDN(v); loadLdapOUUsers(v); }}>
<SelectTrigger className="w-60 h-7 bg-slate-800 border-slate-700 text-slate-200 text-xs">
<SelectValue placeholder="选择组织单元" />
</SelectTrigger>
<SelectContent className="bg-slate-800 border-slate-700">
<SelectItem value={ldapConfig.base_dn}>根 ({ldapConfig.base_dn})</SelectItem>
{ldapOUs.map((ou: any) => (
<SelectItem key={ou.dn} value={ou.dn}>
{ou.description ? `${ou.name} (${ou.description})` : ou.name}
</SelectItem>
))}
</SelectContent>
</Select>
{/* 新建 OU */}
<Dialog open={createOUOpen} onOpenChange={setCreateOUOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-slate-200">
<Plus className="h-3.5 w-3.5 mr-1" />
新建 OU
</Button>
</DialogTrigger>
<DialogContent className="max-w-md bg-slate-900 border-slate-800">
<DialogHeader>
<DialogTitle className="text-slate-100">新建组织单元</DialogTitle>
<DialogDescription className="text-slate-500">在 LDAP 目录中创建新的组织单元 (OU)</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-2">
<div>
<label className="text-xs text-slate-500 mb-1 block">OU 名称 *</label>
<Input value={ouForm.ou_name} onChange={(e) => setOuForm({ ...ouForm, ou_name: e.target.value })} placeholder="dev" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">父级 DN</label>
<Input value={ouForm.parent_dn} onChange={(e) => setOuForm({ ...ouForm, parent_dn: e.target.value })} placeholder={ldapConfig.base_dn} className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">描述</label>
<Input value={ouForm.description} onChange={(e) => setOuForm({ ...ouForm, description: e.target.value })} placeholder="开发团队" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={handleCreateOU} disabled={!ouForm.ou_name || ldapSaving}>
{ldapSaving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
创建
</Button>
</div>
</DialogContent>
</Dialog>
{/* 添加用户 */}
<Dialog open={createUserOpen} onOpenChange={setCreateUserOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-slate-200">
<UserPlus className="h-3.5 w-3.5 mr-1" />
添加用户
</Button>
</DialogTrigger>
<DialogContent className="max-w-md bg-slate-900 border-slate-800">
<DialogHeader>
<DialogTitle className="text-slate-100">添加 LDAP 用户</DialogTitle>
<DialogDescription className="text-slate-500">在 LDAP 目录中创建新的用户账号</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-2">
<div>
<label className="text-xs text-slate-500 mb-1 block">用户名 *</label>
<Input value={newUser.username} onChange={(e) => setNewUser({ ...newUser, username: e.target.value })} placeholder="zhangsan" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">密码 *</label>
<Input type="password" value={newUser.password} onChange={(e) => setNewUser({ ...newUser, password: e.target.value })} placeholder="••••••" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">显示名称</label>
<Input value={newUser.display_name} onChange={(e) => setNewUser({ ...newUser, display_name: e.target.value })} placeholder="张三" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">邮箱</label>
<Input value={newUser.email} onChange={(e) => setNewUser({ ...newUser, email: e.target.value })} placeholder="zs@example.com" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">目标 OU</label>
<Input value={newUser.ou_dn || selectedOUDN || ldapConfig.base_dn} onChange={(e) => setNewUser({ ...newUser, ou_dn: e.target.value })} className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={handleCreateLDAPUser} disabled={!newUser.username || !newUser.password || ldapSaving}>
{ldapSaving ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : null}
创建
</Button>
</div>
</DialogContent>
</Dialog>
{/* 导入到本地 */}
<Button
variant="outline"
size="sm"
className="border-blue-600/50 text-blue-400 hover:bg-blue-600/10 ml-auto"
onClick={handleLdapImport}
disabled={ldapUsers.length === 0 || importing}
>
{importing ? <Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" /> : <Download className="h-3.5 w-3.5 mr-1" />}
{selectedLDAPUsers.length > 0 ? `导入 (${selectedLDAPUsers.length})` : `导入全部 (${ldapUsers.length})`}
</Button>
</div>
{importFeedback && (
<Dialog open={importResultOpen} onOpenChange={setImportResultOpen}>
<DialogContent className="max-w-sm bg-slate-900 border-slate-800">
<DialogHeader>
<DialogTitle className="text-slate-100">导入结果</DialogTitle>
<DialogDescription className="text-slate-500">LDAP 用户导入到本地用户库的执行结果</DialogDescription>
</DialogHeader>
<div className="py-3 text-sm text-slate-300">
{importFeedback}
</div>
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={() => setImportResultOpen(false)}>
确定
</Button>
</DialogContent>
</Dialog>
)}
{/* 用户表格 */}
<div className="border border-slate-800 rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow className="border-slate-800 hover:bg-transparent">
<TableHead className="text-slate-400 w-8">
<input
type="checkbox"
className="rounded bg-slate-800 border-slate-600"
checked={selectedLDAPUsers.length > 0 && selectedLDAPUsers.length === ldapUsers.length}
onChange={(e) => setSelectedLDAPUsers(e.target.checked ? ldapUsers.map((u: any) => u.dn) : [])}
/>
</TableHead>
<TableHead className="text-slate-400">用户名</TableHead>
<TableHead className="text-slate-400">显示名称</TableHead>
<TableHead className="text-slate-400">邮箱</TableHead>
<TableHead className="text-slate-400">DN</TableHead>
<TableHead className="text-slate-400 w-12">操作</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{ldapUsers.length === 0 ? (
<TableRow className="border-slate-800">
<TableCell colSpan={6} className="text-center text-slate-500 py-8">
点击「刷新」加载 LDAP 用户
</TableCell>
</TableRow>
) : (
ldapUsers.map((user: any) => (
<TableRow key={user.dn} className="border-slate-800 hover:bg-slate-800/50">
<TableCell>
<input
type="checkbox"
className="rounded bg-slate-800 border-slate-600"
checked={selectedLDAPUsers.includes(user.dn)}
onChange={(e) => {
if (e.target.checked) setSelectedLDAPUsers([...selectedLDAPUsers, user.dn]);
else setSelectedLDAPUsers(selectedLDAPUsers.filter((d: string) => d !== user.dn));
}}
/>
</TableCell>
<TableCell>
<span className="text-sm text-slate-200 font-medium">{user.uid}</span>
</TableCell>
<TableCell>
<span className="text-sm text-slate-300">{user.cn}</span>
</TableCell>
<TableCell>
<span className="text-sm text-slate-400">{user.mail || '-'}</span>
</TableCell>
<TableCell>
<code className="text-xs text-slate-500">{user.dn}</code>
</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-slate-400 hover:text-blue-400 hover:bg-blue-500/10"
onClick={() => openEditLdapUser(user)}
>
<Edit className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-red-400 hover:text-red-300 hover:bg-red-500/10"
onClick={() => handleDeleteLDAPEntry(user.dn)}
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</div>
)}
{/* 编辑 LDAP 用户 Dialog */}
<Dialog open={!!editLdapUser} onOpenChange={(v) => !v && setEditLdapUser(null)}>
<DialogContent className="max-w-md bg-slate-900 border-slate-800">
<DialogHeader>
<DialogTitle className="text-slate-100 flex items-center gap-2">
<Edit className="h-5 w-5 text-blue-400" />
编辑 LDAP 用户
</DialogTitle>
<DialogDescription className="text-slate-500">
修改 {editLdapUser?.uid} 的属性
</DialogDescription>
</DialogHeader>
{editLdapUser && (
<div className="space-y-3 py-2">
<div>
<label className="text-xs text-slate-500 mb-1 block">用户名(只读)</label>
<Input value={editLdapUser.uid || ''} disabled className="bg-slate-800/50 border-slate-700 text-slate-500" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">显示名称 (cn)</label>
<Input value={editLdapForm.cn} onChange={(e) => setEditLdapForm({ ...editLdapForm, cn: e.target.value })} className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">邮箱</label>
<Input value={editLdapForm.mail} onChange={(e) => setEditLdapForm({ ...editLdapForm, mail: e.target.value })} placeholder="user@example.com" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">电话</label>
<Input value={editLdapForm.phone} onChange={(e) => setEditLdapForm({ ...editLdapForm, phone: e.target.value })} placeholder="13800138000" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">职位/头衔</label>
<Input value={editLdapForm.title} onChange={(e) => setEditLdapForm({ ...editLdapForm, title: e.target.value })} placeholder="运维工程师" className="bg-slate-800/50 border-slate-700 text-slate-200" />
</div>
</div>
)}
<DialogFooter>
<Button variant="outline" className="border-slate-700 text-slate-300" onClick={() => setEditLdapUser(null)}>取消</Button>
<Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={handleUpdateLdapUser}>保存</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}