增加团队管理页面
This commit is contained in:
parent
68bc9757e4
commit
70f54594a4
@ -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">
|
||||
{getOsIcon(server.osType)}
|
||||
{/* 中间:服务器名称和基本信息 */}
|
||||
<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>
|
||||
{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>
|
||||
)}
|
||||
<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="text-xs text-muted-foreground truncate">
|
||||
{server.hostname}
|
||||
</div>
|
||||
)}
|
||||
{server.osType && (
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
{OsTypeLabels[server.osType]?.label || server.osType}
|
||||
{server.osVersion && ` ${server.osVersion}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 系统信息 */}
|
||||
{server.osType && (
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Badge variant="secondary" className="font-medium">
|
||||
{OsTypeLabels[server.osType]?.label || server.osType}
|
||||
{server.osVersion && ` ${server.osVersion}`}
|
||||
{/* 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>
|
||||
)}
|
||||
)}
|
||||
</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" />
|
||||
<span className="text-xs font-medium">{server.cpuCores} 核</span>
|
||||
<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" />
|
||||
<span className="text-xs font-medium">{server.memorySize} GB</span>
|
||||
<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" />
|
||||
<span className="text-xs font-medium">{server.diskSize} GB</span>
|
||||
<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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user