Files
aiops/frontend/src/components/system/SystemRolesTab.tsx
T
attacker 73b5a48df9 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 ✅
2026-06-15 01:32:15 +08:00

121 lines
4.5 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Plus, Shield, Edit, Eye } from 'lucide-react';
import { userApi } from '@/services/api';
import type { Role } from '@/types';
interface SystemRolesTabProps {
selectedRole: Role | null;
setSelectedRole: (role: Role | null) => void;
}
// 将后端角色数据规范化为前端 Role 类型 // Logan.Li
function normalizeRole(raw: any): Role {
return {
id: String(raw.id ?? ''),
name: raw.name ?? '',
code: raw.code ?? '',
description: raw.description ?? '',
// 后端可能不返回 permissions/createdAt,给予默认值以避免渲染崩溃
permissions: Array.isArray(raw.permissions) ? raw.permissions : [],
createdAt: raw.createdAt ?? raw.created_at ?? '',
};
}
export function SystemRolesTab({ selectedRole, setSelectedRole }: SystemRolesTabProps) {
const [roles, setRoles] = useState<Role[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
(async () => {
setLoading(true);
try {
const resp = await userApi.getRoles();
if (cancelled) return;
const list = resp?.list ?? [];
setRoles(list.map(normalizeRole));
} catch (err) {
if (!cancelled) setRoles([]);
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => {
cancelled = true;
};
}, []);
return (
<>
<div className="flex items-center justify-end">
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
<Plus className="h-4 w-4 mr-2" />
新建角色
</Button>
</div>
{loading ? (
<div className="flex items-center justify-center py-16">
<div className="w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
</div>
) : roles.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-slate-500">
<Shield className="h-10 w-10 mb-3 opacity-40" />
<p className="text-sm">暂无角色数据</p>
</div>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{roles.map((role) => (
<Card key={role.id} className="bg-slate-900 border-slate-800">
<CardHeader className="pb-2">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-purple-500/10 flex items-center justify-center">
<Shield className="h-5 w-5 text-purple-400" />
</div>
<div>
<CardTitle className="text-base text-slate-100">{role.name}</CardTitle>
<CardDescription className="text-slate-500">{role.code}</CardDescription>
</div>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-slate-400 hover:text-slate-100"
onClick={() => setSelectedRole(role)}
>
<Eye className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-slate-100">
<Edit className="h-4 w-4" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-slate-400 mb-3">{role.description}</p>
<div className="flex flex-wrap gap-1">
{role.permissions.slice(0, 5).map((permission) => (
<Badge key={permission} variant="outline" className="border-slate-700 text-slate-400 text-xs">
{permission}
</Badge>
))}
{role.permissions.length > 5 && (
<Badge variant="outline" className="border-slate-700 text-slate-400 text-xs">
+{role.permissions.length - 5}
</Badge>
)}
</div>
</CardContent>
</Card>
))}
</div>
)}
</>
);
}