first commit

This commit is contained in:
MengMengCode
2026-06-05 19:23:28 +08:00
commit e306d2d06b
66 changed files with 18825 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
import { useNavigate } from 'react-router-dom'
import {
Server,
Cpu,
HardDrive,
MemoryStick,
Globe,
Play,
Square,
RotateCcw,
Trash2,
} from 'lucide-react'
import { Container, startContainer, stopContainer, restartContainer, deleteContainer } from '../services/api'
interface ContainerCardProps {
container: Container
onRefresh: () => void
}
export default function ContainerCard({ container, onRefresh }: ContainerCardProps) {
const navigate = useNavigate()
const containerIdentifier = container.uuid || container.id
const handleAction = async (action: string) => {
try {
switch (action) {
case 'start':
await startContainer(containerIdentifier)
break
case 'stop':
await stopContainer(containerIdentifier)
break
case 'restart':
await restartContainer(containerIdentifier)
break
case 'delete':
if (window.confirm(`确定要删除容器 ${container.name} 吗?此操作不可撤销。`)) {
await deleteContainer(containerIdentifier)
} else {
return
}
break
}
onRefresh()
} catch (err) {
console.error('Action failed:', err)
alert('操作失败')
}
}
const statusColor = container.status === 'running' ? 'bg-green-500' : 'bg-red-500'
const statusText = container.status === 'running' ? '运行中' : '已停止'
return (
<div className="bg-white border border-gray-200 rounded-lg p-5 hover:shadow-md transition-shadow">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center">
<Server className="w-5 h-5 text-gray-700" />
</div>
<div>
<button
onClick={() => navigate(`/container/${encodeURIComponent(String(containerIdentifier))}`)}
className="font-semibold text-black hover:underline text-left"
>
{container.name}
</button>
<div className="flex items-center gap-1.5 mt-0.5">
<span className={`w-1.5 h-1.5 rounded-full ${statusColor}`}></span>
<span className="text-xs text-gray-500">{statusText}</span>
</div>
</div>
</div>
</div>
{/* Specs */}
<div className="grid grid-cols-2 gap-3 mb-4">
<div className="flex items-center gap-2 text-sm text-gray-600">
<Cpu className="w-3.5 h-3.5" />
<span>{container.vcpu} vCPU</span>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<MemoryStick className="w-3.5 h-3.5" />
<span>{container.ram_mb} MB</span>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<HardDrive className="w-3.5 h-3.5" />
<span>{container.disk_gb} GB</span>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<Globe className="w-3.5 h-3.5" />
<span>{container.network_bw_mbps} Mbps</span>
</div>
</div>
{container.ip && (
<div className="text-xs text-gray-400 mb-3">
IP: {container.ip}
</div>
)}
{/* Actions */}
<div className="flex items-center gap-1.5 pt-3 border-t border-gray-100">
{container.status !== 'running' ? (
<button
onClick={() => handleAction('start')}
className="flex items-center gap-1 px-3 py-1.5 bg-green-600 text-white rounded text-xs hover:bg-green-700 transition-colors"
>
<Play className="w-3 h-3" />
</button>
) : (
<>
<button
onClick={() => handleAction('stop')}
className="flex items-center gap-1 px-3 py-1.5 bg-yellow-500 text-white rounded text-xs hover:bg-yellow-600 transition-colors"
>
<Square className="w-3 h-3" />
</button>
<button
onClick={() => handleAction('restart')}
className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 text-white rounded text-xs hover:bg-blue-700 transition-colors"
>
<RotateCcw className="w-3 h-3" />
</button>
</>
)}
<div className="flex-1" />
<button
onClick={() => handleAction('delete')}
className="flex items-center gap-1 px-3 py-1.5 text-red-600 hover:bg-red-50 rounded text-xs transition-colors"
>
<Trash2 className="w-3 h-3" />
</button>
</div>
</div>
)
}