This commit is contained in:
dengqichen 2025-12-12 18:39:04 +08:00
parent 4d0c23fb96
commit 07eb96e582
3 changed files with 99 additions and 74 deletions

View File

@ -8,12 +8,11 @@ import {
} from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Trash2, Loader2, Package, Tag, FileCode, Copy, Check } from 'lucide-react';
import { Loader2, Package, Tag, FileCode, Copy, Check } from 'lucide-react';
import { useToast } from '@/components/ui/use-toast';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { YamlViewerDialog } from './YamlViewerDialog';
import type { K8sDeploymentResponse } from '../types';
import { getK8sDeploymentByNamespace, deleteK8sDeployment } from '../service';
import { getK8sDeploymentByNamespace } from '../service';
import dayjs from 'dayjs';
interface DeploymentDialogProps {
@ -34,13 +33,12 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
const { toast } = useToast();
const [deployments, setDeployments] = useState<K8sDeploymentResponse[]>([]);
const [loading, setLoading] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [deploymentToDelete, setDeploymentToDelete] = useState<K8sDeploymentResponse | null>(null);
const [labelDialogOpen, setLabelDialogOpen] = useState(false);
const [yamlDialogOpen, setYamlDialogOpen] = useState(false);
const [selectedDeployment, setSelectedDeployment] = useState<K8sDeploymentResponse | null>(null);
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
const [copiedAll, setCopiedAll] = useState(false);
const [copiedImageId, setCopiedImageId] = useState<number | null>(null);
const loadDeployments = async () => {
setLoading(true);
@ -65,16 +63,6 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
}
}, [open, namespaceId, externalSystemId]);
const handleDelete = (deployment: K8sDeploymentResponse) => {
setDeploymentToDelete(deployment);
setDeleteDialogOpen(true);
};
const confirmDelete = async () => {
if (!deploymentToDelete) return;
await deleteK8sDeployment(deploymentToDelete.id);
};
const handleViewLabels = (deployment: K8sDeploymentResponse, e: React.MouseEvent) => {
e.stopPropagation();
setSelectedDeployment(deployment);
@ -117,6 +105,18 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
});
};
const handleCopyImage = (image: string, deploymentId: number, e: React.MouseEvent) => {
e.stopPropagation();
navigator.clipboard.writeText(image).then(() => {
setCopiedImageId(deploymentId);
toast({
title: '已复制',
description: '已复制镜像地址到剪贴板',
});
setTimeout(() => setCopiedImageId(null), 2000);
});
};
const getReplicaStatus = (deployment: K8sDeploymentResponse) => {
const ready = deployment.readyReplicas ?? 0;
const desired = deployment.replicas ?? 0;
@ -137,12 +137,12 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[90vw] max-h-[85vh] flex flex-col">
<DialogContent className="max-w-6xl max-h-[85vh] flex flex-col">
<DialogHeader>
<div className="flex items-center gap-2">
<DialogTitle className="flex items-center gap-2">
<Package className="h-5 w-5 text-primary" />
<DialogTitle>: {namespaceName} Deployment</DialogTitle>
</div>
<span>: {namespaceName} Deployment</span>
</DialogTitle>
</DialogHeader>
<DialogBody className="flex-1 overflow-y-auto">
@ -155,7 +155,7 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
Deployment
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{deployments.map((deployment) => {
const status = getReplicaStatus(deployment);
return (
@ -174,15 +174,31 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
<div className="space-y-2 text-sm text-muted-foreground">
{deployment.image && (
<div className="truncate" title={deployment.image}>
<span className="font-medium">:</span> {deployment.image}
<div className="flex items-center justify-between gap-2 group">
<div className="truncate flex-1" title={deployment.image}>
<span className="font-medium">:</span> {deployment.image}
</div>
<Button
variant="ghost"
size="sm"
onClick={(e) => handleCopyImage(deployment.image!, deployment.id, e)}
className="h-6 w-6 p-0 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0"
>
{copiedImageId === deployment.id ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
</Button>
</div>
)}
{deployment.labels && Object.keys(deployment.labels).length > 0 && (
{deployment.labels && Object.keys(deployment.labels).length > 0 ? (
<div>
<span className="font-medium">:</span> {Object.keys(deployment.labels).length}
</div>
) : (
<div className="h-5 w-24 bg-muted rounded animate-pulse" />
)}
{deployment.k8sUpdateTime && (
@ -192,36 +208,27 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
)}
</div>
<div className="mt-4 pt-3 border-t space-y-2">
<div className="mt-4 pt-3 border-t">
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={(e) => handleViewLabels(deployment, e)}
className="flex-1"
className="h-8 w-8 p-0"
title="查看标签"
>
<Tag className="h-4 w-4 mr-1" />
<Tag className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={(e) => handleViewYaml(deployment, e)}
className="flex-1"
className="h-8 w-8 p-0"
title="查看YAML"
>
<FileCode className="h-4 w-4 mr-1" />
YAML
<FileCode className="h-4 w-4" />
</Button>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => handleDelete(deployment)}
className="text-destructive hover:text-destructive w-full"
>
<Trash2 className="h-4 w-4 mr-1" />
</Button>
</div>
</div>
);
@ -232,25 +239,6 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
</DialogContent>
</Dialog>
<ConfirmDialog
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
title="确认删除"
description={`确定要删除Deployment"${deploymentToDelete?.deploymentName}"吗?此操作无法撤销。`}
item={deploymentToDelete}
onConfirm={confirmDelete}
onSuccess={() => {
toast({
title: '删除成功',
description: `Deployment"${deploymentToDelete?.deploymentName}"已删除`,
});
setDeploymentToDelete(null);
loadDeployments();
}}
variant="destructive"
confirmText="确定"
/>
{/* 标签Dialog */}
<Dialog open={labelDialogOpen} onOpenChange={setLabelDialogOpen}>
<DialogContent className="max-w-2xl">

View File

@ -82,7 +82,7 @@ export const NamespaceCard: React.FC<NamespaceCardProps> = ({ namespace, onClick
{namespace.status && (
<Badge
variant={namespace.status === 'Active' ? 'default' : 'secondary'}
className="text-xs flex-shrink-0 ml-2"
className={`text-xs flex-shrink-0 ml-2 ${namespace.status === 'Active' ? 'bg-green-600 hover:bg-green-700' : ''}`}
>
{namespace.status}
</Badge>
@ -95,10 +95,7 @@ export const NamespaceCard: React.FC<NamespaceCardProps> = ({ namespace, onClick
<span>Deployment</span>
</div>
<div
className="flex items-center gap-1.5 cursor-pointer hover:text-foreground transition-colors"
onClick={handleLabelClick}
>
<div className="flex items-center gap-1.5">
<span className="font-medium text-foreground">{labelCount}</span>
<span></span>
{labelCount > 0 && <Tag className="h-3 w-3" />}
@ -120,19 +117,19 @@ export const NamespaceCard: React.FC<NamespaceCardProps> = ({ namespace, onClick
variant="outline"
size="sm"
onClick={handleLabelClick}
className="flex-1"
className="h-8 w-8 p-0"
title="查看标签"
>
<Tag className="h-4 w-4 mr-1" />
<Tag className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={handleYamlClick}
className="flex-1"
className="h-8 w-8 p-0"
title="查看YAML"
>
<FileCode className="h-4 w-4 mr-1" />
YAML
<FileCode className="h-4 w-4" />
</Button>
</div>
</CardContent>

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
Dialog,
DialogContent,
@ -6,7 +6,9 @@ import {
DialogTitle,
DialogBody,
} from '@/components/ui/dialog';
import { FileCode } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { FileCode, Copy, Check } from 'lucide-react';
import { useToast } from '@/components/ui/use-toast';
import Editor from '@/components/Editor';
interface YamlViewerDialogProps {
@ -22,13 +24,51 @@ export const YamlViewerDialog: React.FC<YamlViewerDialogProps> = ({
title,
yamlContent,
}) => {
const { toast } = useToast();
const [copied, setCopied] = useState(false);
const handleCopy = () => {
if (!yamlContent) return;
navigator.clipboard.writeText(yamlContent).then(() => {
setCopied(true);
toast({
title: '已复制',
description: '已复制YAML配置到剪贴板',
});
setTimeout(() => setCopied(false), 2000);
});
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[90vw] max-h-[85vh] flex flex-col">
<DialogHeader>
<div className="flex items-center gap-2">
<FileCode className="h-5 w-5 text-primary" />
<DialogTitle>{title}</DialogTitle>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FileCode className="h-5 w-5 text-primary" />
<DialogTitle>{title}</DialogTitle>
</div>
{yamlContent && (
<Button
variant="outline"
size="sm"
onClick={handleCopy}
className="flex items-center gap-2"
>
{copied ? (
<>
<Check className="h-4 w-4 text-green-600" />
</>
) : (
<>
<Copy className="h-4 w-4" />
</>
)}
</Button>
)}
</div>
</DialogHeader>