增加团队管理页面

This commit is contained in:
dengqichen 2025-10-30 18:30:36 +08:00
parent 68bc9757e4
commit 70f54594a4

View File

@ -47,110 +47,106 @@ export const ServerCard: React.FC<ServerCardProps> = ({
};
return (
<Card className="group relative overflow-hidden bg-gradient-to-br from-card to-card/50 hover:shadow-xl hover:shadow-primary/10 transition-all duration-300 border-2 hover:border-primary/50 hover:scale-[1.02] flex flex-col">
{/* 背景装饰 */}
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
<CardContent className="p-6 relative flex-1 flex flex-col">
{/* 顶部状态栏 */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className={`h-3 w-3 rounded-full ${
server.status === 'ONLINE' ? 'bg-green-500 animate-pulse shadow-lg shadow-green-500/50' :
server.status === 'OFFLINE' ? 'bg-red-500 shadow-lg shadow-red-500/50' :
<Card className="group relative overflow-hidden hover:shadow-md transition-all duration-300 border hover:border-primary/50 flex flex-col h-full">
<CardContent className="p-4 relative flex-1 flex flex-col">
{/* 顶部:状态和分类 */}
<div className="flex items-center justify-between gap-2 mb-3">
<div className="flex items-center gap-1.5">
<div className={`h-2.5 w-2.5 rounded-full ${
server.status === 'ONLINE' ? 'bg-green-500 animate-pulse' :
server.status === 'OFFLINE' ? 'bg-red-500' :
'bg-gray-400'
}`} />
<Badge className={
<Badge className={`text-xs ${
server.status === 'ONLINE' ? 'bg-green-500/10 text-green-700 border-green-500/30 dark:text-green-400' :
server.status === 'OFFLINE' ? 'bg-red-500/10 text-red-700 border-red-500/30 dark:text-red-400' :
'bg-gray-500/10 text-gray-700 border-gray-500/30 dark:text-gray-400'
}>
}`}>
{ServerStatusLabels[server.status]?.label || server.status}
</Badge>
</div>
{server.categoryName && (
<Badge variant="outline" className="text-xs">
<Badge variant="outline" className="text-xs ml-auto">
{server.categoryName}
</Badge>
)}
</div>
{/* 服务器信息 */}
<div className="flex items-start gap-4 mb-6">
<div className="p-3 rounded-xl bg-gradient-to-br from-primary/20 to-primary/5 shadow-inner">
{/* 中间:服务器名称和基本信息 */}
<div className="flex items-start gap-3 mb-2.5 flex-1">
<div className="p-2 rounded-lg bg-muted/50">
<div className="h-5 w-5">
{getOsIcon(server.osType)}
</div>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-lg font-bold truncate mb-2 group-hover:text-primary transition-colors">
<h3 className="text-sm font-semibold truncate group-hover:text-primary transition-colors">
{server.serverName}
</h3>
<div className="space-y-1.5">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Network className="h-3.5 w-3.5" />
<span className="font-mono font-medium">{server.hostIp}</span>
<div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5">
<Network className="h-3 w-3 flex-shrink-0" />
<span className="font-mono truncate">{server.hostIp}</span>
</div>
{server.hostname && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Server className="h-3.5 w-3.5" />
<span className="truncate">{server.hostname}</span>
<div className="text-xs text-muted-foreground truncate">
{server.hostname}
</div>
)}
</div>
</div>
</div>
{/* 系统信息 */}
{server.osType && (
<div className="flex items-center gap-2 mb-4">
<Badge variant="secondary" className="font-medium">
<div className="text-xs text-muted-foreground mt-0.5">
{OsTypeLabels[server.osType]?.label || server.osType}
{server.osVersion && ` ${server.osVersion}`}
</Badge>
</div>
)}
</div>
</div>
{/* 硬件配置 */}
{/* SSH 和认证方式 - 在一行显示 */}
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-2.5 flex-wrap">
<span>SSH: <span className="font-medium text-foreground">{server.sshUser || 'root'}:{server.sshPort || 22}</span></span>
{server.authType && (
<Badge variant="outline" className="text-xs">
{server.authType === 'PASSWORD' ? '密码' : '密钥'}
</Badge>
)}
</div>
{/* 硬件配置 - 紧凑显示 */}
{(server.cpuCores || server.memorySize || server.diskSize) && (
<div className="grid grid-cols-3 gap-3 mb-5 p-4 bg-gradient-to-br from-muted/50 to-muted/30 rounded-lg border border-border/50">
<div className="grid grid-cols-3 gap-2 mb-2.5 p-2 bg-muted/20 rounded border border-border/40">
{server.cpuCores && (
<div className="flex flex-col items-center gap-1.5">
<Cpu className="h-4 w-4 text-blue-600 dark:text-blue-400" />
<div className="flex flex-col items-center gap-0.5">
<Cpu className="h-3 w-3 text-blue-600 dark:text-blue-400" />
<span className="text-xs font-medium">{server.cpuCores}</span>
</div>
)}
{server.memorySize && (
<div className="flex flex-col items-center gap-1.5">
<MemoryStick className="h-4 w-4 text-purple-600 dark:text-purple-400" />
<div className="flex flex-col items-center gap-0.5">
<MemoryStick className="h-3 w-3 text-purple-600 dark:text-purple-400" />
<span className="text-xs font-medium">{server.memorySize}GB</span>
</div>
)}
{server.diskSize && (
<div className="flex flex-col items-center gap-1.5">
<HardDrive className="h-4 w-4 text-orange-600 dark:text-orange-400" />
<div className="flex flex-col items-center gap-0.5">
<HardDrive className="h-3 w-3 text-orange-600 dark:text-orange-400" />
<span className="text-xs font-medium">{server.diskSize}GB</span>
</div>
)}
</div>
)}
{/* 描述 */}
{server.description && (
<p className="text-sm text-muted-foreground line-clamp-2 mb-4 px-1">
{server.description}
</p>
)}
{/* 标签 */}
{/* 标签 - 可选显示 */}
{server.tags && (() => {
try {
const tags = JSON.parse(server.tags);
return Array.isArray(tags) && tags.length > 0 && (
<div className="flex flex-wrap gap-2 mb-4">
{tags.map((tag, index) => (
<div className="flex flex-wrap gap-1 mb-2.5">
{tags.slice(0, 2).map((tag, index) => (
<Badge key={index} variant="outline" className="text-xs">
{tag}
</Badge>
))}
{tags.length > 2 && <Badge variant="outline" className="text-xs">+{tags.length - 2}</Badge>}
</div>
);
} catch {
@ -158,81 +154,66 @@ export const ServerCard: React.FC<ServerCardProps> = ({
}
})()}
{/* SSH 用户信息 */}
<div className="flex items-center justify-between text-xs text-muted-foreground mb-3 px-1">
<span>SSH用户: <span className="font-medium text-foreground">{server.sshUser || 'root'}</span></span>
<span>: <span className="font-medium text-foreground">{server.sshPort || 22}</span></span>
</div>
{/* 认证方式 */}
{server.authType && (
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-3 px-1">
<span>: </span>
<Badge variant="outline" className="text-xs">
{server.authType === 'PASSWORD' ? '密码认证' : '密钥认证'}
</Badge>
</div>
{/* 描述 - 始终显示 */}
{server.description && (
<p className="text-xs text-muted-foreground line-clamp-2 mb-2.5 px-1">
{server.description}
</p>
)}
{/* 最后连接时间 */}
{server.lastConnectTime && (
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-3 px-1">
<Clock className="h-3.5 w-3.5" />
<span>: {formatTime(server.lastConnectTime)}</span>
<div className="flex items-center gap-1.5 text-xs text-muted-foreground mb-2.5">
<Clock className="h-3 w-3 flex-shrink-0" />
<span className="truncate"> {formatTime(server.lastConnectTime)}</span>
</div>
)}
{/* 弹性空间,将按钮推到底部 */}
<div className="flex-1" />
{/* 操作按钮 - 固定在底部并居中 */}
<div className="flex items-center justify-center gap-2 pt-4 mt-4 border-t border-border/50">
{/* 操作按钮 - Hover 时才显示 */}
<div className="flex items-center justify-center gap-2 pt-2 mt-auto border-t border-border/30 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
variant="ghost"
size="xs"
onClick={() => onTest(server)}
disabled={isTesting}
className="group-hover:border-primary/50 transition-all"
className="h-7 px-2"
>
{isTesting ? (
<Loader2 className="h-4 w-4 animate-spin mr-1.5" />
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<TestTube className="h-4 w-4 mr-1.5" />
<TestTube className="h-3.5 w-3.5" />
)}
<span className="text-xs">{isTesting ? '测试中' : '测试'}</span>
</Button>
</TooltipTrigger>
<TooltipContent>SSH连</TooltipContent>
<TooltipContent></TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
variant="ghost"
size="xs"
onClick={() => onEdit(server)}
className="group-hover:border-primary/50 transition-all"
className="h-7 px-2"
>
<Pencil className="h-4 w-4 mr-1.5" />
<span className="text-xs"></span>
<Pencil className="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent></TooltipContent>
<TooltipContent></TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
variant="ghost"
size="xs"
onClick={() => onDelete(server)}
className="text-destructive hover:text-destructive hover:border-destructive/50 transition-all"
className="h-7 px-2 text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4 mr-1.5" />
<span className="text-xs"></span>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent></TooltipContent>
<TooltipContent></TooltipContent>
</Tooltip>
</div>
</CardContent>