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'; } from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; 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 { useToast } from '@/components/ui/use-toast';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { YamlViewerDialog } from './YamlViewerDialog'; import { YamlViewerDialog } from './YamlViewerDialog';
import type { K8sDeploymentResponse } from '../types'; import type { K8sDeploymentResponse } from '../types';
import { getK8sDeploymentByNamespace, deleteK8sDeployment } from '../service'; import { getK8sDeploymentByNamespace } from '../service';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
interface DeploymentDialogProps { interface DeploymentDialogProps {
@ -34,13 +33,12 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
const { toast } = useToast(); const { toast } = useToast();
const [deployments, setDeployments] = useState<K8sDeploymentResponse[]>([]); const [deployments, setDeployments] = useState<K8sDeploymentResponse[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [deploymentToDelete, setDeploymentToDelete] = useState<K8sDeploymentResponse | null>(null);
const [labelDialogOpen, setLabelDialogOpen] = useState(false); const [labelDialogOpen, setLabelDialogOpen] = useState(false);
const [yamlDialogOpen, setYamlDialogOpen] = useState(false); const [yamlDialogOpen, setYamlDialogOpen] = useState(false);
const [selectedDeployment, setSelectedDeployment] = useState<K8sDeploymentResponse | null>(null); const [selectedDeployment, setSelectedDeployment] = useState<K8sDeploymentResponse | null>(null);
const [copiedIndex, setCopiedIndex] = useState<number | null>(null); const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
const [copiedAll, setCopiedAll] = useState(false); const [copiedAll, setCopiedAll] = useState(false);
const [copiedImageId, setCopiedImageId] = useState<number | null>(null);
const loadDeployments = async () => { const loadDeployments = async () => {
setLoading(true); setLoading(true);
@ -65,16 +63,6 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
} }
}, [open, namespaceId, externalSystemId]); }, [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) => { const handleViewLabels = (deployment: K8sDeploymentResponse, e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
setSelectedDeployment(deployment); 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 getReplicaStatus = (deployment: K8sDeploymentResponse) => {
const ready = deployment.readyReplicas ?? 0; const ready = deployment.readyReplicas ?? 0;
const desired = deployment.replicas ?? 0; const desired = deployment.replicas ?? 0;
@ -137,12 +137,12 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
return ( return (
<> <>
<Dialog open={open} onOpenChange={onOpenChange}> <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> <DialogHeader>
<div className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
<Package className="h-5 w-5 text-primary" /> <Package className="h-5 w-5 text-primary" />
<DialogTitle>: {namespaceName} Deployment</DialogTitle> <span>: {namespaceName} Deployment</span>
</div> </DialogTitle>
</DialogHeader> </DialogHeader>
<DialogBody className="flex-1 overflow-y-auto"> <DialogBody className="flex-1 overflow-y-auto">
@ -155,7 +155,7 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
Deployment Deployment
</div> </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) => { {deployments.map((deployment) => {
const status = getReplicaStatus(deployment); const status = getReplicaStatus(deployment);
return ( return (
@ -174,15 +174,31 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
<div className="space-y-2 text-sm text-muted-foreground"> <div className="space-y-2 text-sm text-muted-foreground">
{deployment.image && ( {deployment.image && (
<div className="truncate" title={deployment.image}> <div className="flex items-center justify-between gap-2 group">
<span className="font-medium">:</span> {deployment.image} <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> </div>
)} )}
{deployment.labels && Object.keys(deployment.labels).length > 0 && ( {deployment.labels && Object.keys(deployment.labels).length > 0 ? (
<div> <div>
<span className="font-medium">:</span> {Object.keys(deployment.labels).length} <span className="font-medium">:</span> {Object.keys(deployment.labels).length}
</div> </div>
) : (
<div className="h-5 w-24 bg-muted rounded animate-pulse" />
)} )}
{deployment.k8sUpdateTime && ( {deployment.k8sUpdateTime && (
@ -192,36 +208,27 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
)} )}
</div> </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"> <div className="flex gap-2">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={(e) => handleViewLabels(deployment, e)} 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>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={(e) => handleViewYaml(deployment, e)} 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" /> <FileCode className="h-4 w-4" />
YAML
</Button> </Button>
</div> </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>
</div> </div>
); );
@ -232,25 +239,6 @@ export const DeploymentDialog: React.FC<DeploymentDialogProps> = ({
</DialogContent> </DialogContent>
</Dialog> </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 */}
<Dialog open={labelDialogOpen} onOpenChange={setLabelDialogOpen}> <Dialog open={labelDialogOpen} onOpenChange={setLabelDialogOpen}>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-2xl">

View File

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

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -6,7 +6,9 @@ import {
DialogTitle, DialogTitle,
DialogBody, DialogBody,
} from '@/components/ui/dialog'; } 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'; import Editor from '@/components/Editor';
interface YamlViewerDialogProps { interface YamlViewerDialogProps {
@ -22,13 +24,51 @@ export const YamlViewerDialog: React.FC<YamlViewerDialogProps> = ({
title, title,
yamlContent, 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 ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[90vw] max-h-[85vh] flex flex-col"> <DialogContent className="max-w-[90vw] max-h-[85vh] flex flex-col">
<DialogHeader> <DialogHeader>
<div className="flex items-center gap-2"> <div className="flex items-center justify-between">
<FileCode className="h-5 w-5 text-primary" /> <div className="flex items-center gap-2">
<DialogTitle>{title}</DialogTitle> <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> </div>
</DialogHeader> </DialogHeader>