This commit is contained in:
dengqichen 2025-12-15 16:40:16 +08:00
parent a19b4b8ec8
commit e70f6c5d41
2 changed files with 155 additions and 3 deletions

View File

@ -35,7 +35,7 @@ import { PodRowSkeleton } from './PodRowSkeleton';
import { YamlViewerDialog } from './YamlViewerDialog';
import { restartK8sDeployment, scaleK8sDeployment, deleteK8sDeployment } from '../service';
import type { K8sDeploymentResponse, K8sPodResponse } from '../types';
import { getDeploymentHealth, HealthStatusLabels } from '../types';
import { getDeploymentHealth } from '../types';
import { getK8sPodsByDeployment } from '../service';
import { useAutoRefresh } from '../hooks/useAutoRefresh';
import { formatCpuPair } from '../utils/resourceFormatter';
@ -64,6 +64,7 @@ export const DeploymentRow: React.FC<DeploymentRowProps> = ({
const { toast } = useToast();
const [pods, setPods] = useState<K8sPodResponse[]>([]);
const [initialLoading, setInitialLoading] = useState(true);
const [restartDialogOpen, setRestartDialogOpen] = useState(false);
const [scaleDialogOpen, setScaleDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [yamlDialogOpen, setYamlDialogOpen] = useState(false);
@ -130,6 +131,7 @@ export const DeploymentRow: React.FC<DeploymentRowProps> = ({
});
} finally {
setLoading(false);
setRestartDialogOpen(false);
}
};
@ -292,8 +294,7 @@ export const DeploymentRow: React.FC<DeploymentRowProps> = ({
<Button
variant="ghost"
size="sm"
onClick={handleRestart}
disabled={loading}
onClick={() => setRestartDialogOpen(true)}
className="h-8 w-8 p-0"
>
<RotateCw className="h-4 w-4" />
@ -388,6 +389,28 @@ export const DeploymentRow: React.FC<DeploymentRowProps> = ({
</>
)}
{/* 重启确认对话框 */}
<Dialog open={restartDialogOpen} onOpenChange={setRestartDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
Deployment <strong>{deployment.deploymentName}</strong>
Pod会逐个重启
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setRestartDialogOpen(false)}>
</Button>
<Button onClick={handleRestart} disabled={loading}>
{loading && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 扩缩容对话框 */}
<Dialog open={scaleDialogOpen} onOpenChange={setScaleDialogOpen}>
<DialogContent>

View File

@ -0,0 +1,129 @@
/**
* CPU值为核数
* @param value CPU字符串 "500m" "1" "1.5"
* @returns
*/
export function parseCpu(value: string): number {
if (!value) return 0;
const trimmed = value.trim();
// 处理毫核(如 "500m"
if (trimmed.endsWith('m')) {
const milliCores = parseFloat(trimmed.slice(0, -1));
return milliCores / 1000;
}
// 处理纯数字(如 "1" 或 "1.5"
return parseFloat(trimmed);
}
/**
*
* @param value "512Mi" "1Gi"
* @returns
*/
export function parseMemory(value: string): number {
if (!value) return 0;
const trimmed = value.trim();
const units: Record<string, number> = {
'Ki': 1024,
'Mi': 1024 * 1024,
'Gi': 1024 * 1024 * 1024,
'Ti': 1024 * 1024 * 1024 * 1024,
'K': 1000,
'M': 1000 * 1000,
'G': 1000 * 1000 * 1000,
'T': 1000 * 1000 * 1000 * 1000,
};
for (const [unit, multiplier] of Object.entries(units)) {
if (trimmed.endsWith(unit)) {
const value = parseFloat(trimmed.slice(0, -unit.length));
return value * multiplier;
}
}
// 纯数字,默认为字节
return parseFloat(trimmed);
}
/**
* CPU显示
* @param request CPU请求值
* @param limit CPU限制值
* @returns "0.5 / 1" "500m / 800m"
*/
export function formatCpuPair(request?: string, limit?: string): string {
if (!request && !limit) return '- / -';
if (!request) return `- / ${limit}`;
if (!limit) return `${request} / -`;
const reqCores = parseCpu(request);
const limitCores = parseCpu(limit);
// 如果都小于1核使用毫核显示
if (reqCores < 1 && limitCores < 1) {
const reqMilli = Math.round(reqCores * 1000);
const limitMilli = Math.round(limitCores * 1000);
return `${reqMilli}m / ${limitMilli}m`;
}
// 否则统一使用核数显示
const formatCore = (cores: number) => {
// 如果是整数,不显示小数点
if (cores === Math.floor(cores)) {
return cores.toString();
}
// 保留最多2位小数
return cores.toFixed(2).replace(/\.?0+$/, '');
};
return `${formatCore(reqCores)} / ${formatCore(limitCores)}`;
}
/**
*
* @param request
* @param limit
* @returns "0.5Gi / 1Gi" "512Mi / 1024Mi"
*/
export function formatMemoryPair(request?: string, limit?: string): string {
if (!request && !limit) return '- / -';
if (!request) return `- / ${limit}`;
if (!limit) return `${request} / -`;
const reqBytes = parseMemory(request);
const limitBytes = parseMemory(limit);
const units = [
{ name: 'Ti', value: 1024 * 1024 * 1024 * 1024 },
{ name: 'Gi', value: 1024 * 1024 * 1024 },
{ name: 'Mi', value: 1024 * 1024 },
{ name: 'Ki', value: 1024 },
];
// 选择合适的单位(以较大值为准)
const maxBytes = Math.max(reqBytes, limitBytes);
let selectedUnit = units[units.length - 1]; // 默认Ki
for (const unit of units) {
if (maxBytes >= unit.value) {
selectedUnit = unit;
break;
}
}
const formatValue = (bytes: number) => {
const value = bytes / selectedUnit.value;
// 如果是整数,不显示小数点
if (value === Math.floor(value)) {
return value.toString();
}
// 保留最多2位小数
return value.toFixed(2).replace(/\.?0+$/, '');
};
return `${formatValue(reqBytes)}${selectedUnit.name} / ${formatValue(limitBytes)}${selectedUnit.name}`;
}