import { memo, useEffect, useState } from 'react' import { Link } from 'react-router' import { Play, Square, RotateCw, Trash2, Terminal, ExternalLink, Pencil, Rocket } from 'lucide-react' import { Button } from '@/components/ui/button' import { StatusBadge } from '@/components/StatusBadge' import { deploymentApi } from '@/api' import { ResourceCard } from '@/components/ResourceCard' import type { LokiDeployment } from '@/types' /** 部署目标展示:优先集群,其次主机 */ const getTargetLabel = (d: LokiDeployment) => { if (d.cluster) return `集群:${d.cluster.name}` if (d.host) return `主机:${d.host.name}` return '未知' } interface DeploymentCardProps { deployment: LokiDeployment /** 部署/重启进行中的部署 ID(按钮 loading 态) */ operating: number | null onDeploy: (id: number) => void onStop: (id: number) => void onRestart: (id: number) => void onDelete: (d: LokiDeployment) => void onViewLogs: (d: LokiDeployment) => void onEditRetention: (d: LokiDeployment) => void } /** 部署卡片 — 列表项展示与操作入口(部署/停止/重启/日志/访问/删除) */ export const DeploymentCard = memo(function DeploymentCard({ deployment: d, operating, onDeploy, onStop, onRestart, onDelete, onViewLogs, onEditRetention, }: DeploymentCardProps) { // Loki 就绪探测(运行中时经数据源代理 /ready) const [ready, setReady] = useState(null) useEffect(() => { if (d.status !== 'running') return let cancelled = false deploymentApi .ready(d.id) .then((r) => { if (!cancelled) setReady(r.ready) }) .catch(() => { if (!cancelled) setReady(false) }) return () => { cancelled = true } }, [d.id, d.status]) const isDeploying = d.status === 'deploying' const isBusy = operating === d.id const canDeploy = d.status === 'pending' || d.status === 'failed' || d.status === 'stopped' return ( ) : undefined } badges={} description={d.description || d.uuid} fields={[ { label: '方式', value: d.mode === 'host-docker' ? '主机 Docker' : d.mode === 'k8s-yaml' ? 'Kubernetes' : d.mode }, { label: '版本', value: d.version }, { label: '目标', value: getTargetLabel(d) }, ...(d.mode !== 'host-docker' ? [ { label: '命名空间', value: d.namespace || 'log' }, { label: '副本', value: `${d.replicas} · 保留 ${d.retentionDays || 30} 天` }, ] : [{ label: '保留', value: `${d.retentionDays || 30} 天` }]), { label: '存储', value: d.storageConfig?.name || '本地' }, ...(d.endpoint ? [{ label: '访问', value: ( {d.endpoint.replace(/^https?:\/\//, '')} ), title: d.endpoint }] : []), ...(d.grafanaEnabled && d.grafanaEndpoint ? [{ label: 'Grafana', value: ( {d.grafanaEndpoint.replace(/^https?:\/\//, '')} ) }] : []), ]} message={ d.statusMessage || (d.status === 'running' ? '提示:到「日志查询 → 数据源」关联 Loki 后即可查询;跨集群/公网访问需先为 Loki 配置 Ingress 域名。' : undefined) } actions={ <> {canDeploy ? ( ) : isDeploying ? ( ) : ( <> )} {d.status === 'running' && ( <> )} {d.endpoint && ( )} } /> ) })