1.30
This commit is contained in:
parent
4d0c23fb96
commit
07eb96e582
@ -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">
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user