/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useState, useEffect } from 'react'; import { Modal, Typography, Card, Tag, Progress, Descriptions, Spin, Empty, Button, Badge, Tooltip, } from '@douyinfe/semi-ui'; import { FaInfoCircle, FaServer, FaClock, FaMapMarkerAlt, FaDocker, FaMoneyBillWave, FaChartLine, FaCopy, FaLink, } from 'react-icons/fa'; import { IconRefresh } from '@douyinfe/semi-icons'; import { API, showError, showSuccess, timestamp2string } from '../../../../helpers'; const { Text, Title } = Typography; const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => { const [details, setDetails] = useState(null); const [loading, setLoading] = useState(false); const [containers, setContainers] = useState([]); const [containersLoading, setContainersLoading] = useState(false); const fetchDetails = async () => { if (!deployment?.id) return; setLoading(true); try { const response = await API.get(`/api/deployments/${deployment.id}`); if (response.data.success) { setDetails(response.data.data); } } catch (error) { showError(t('获取详情失败') + ': ' + (error.response?.data?.message || error.message)); } finally { setLoading(false); } }; const fetchContainers = async () => { if (!deployment?.id) return; setContainersLoading(true); try { const response = await API.get(`/api/deployments/${deployment.id}/containers`); if (response.data.success) { setContainers(response.data.data?.containers || []); } } catch (error) { showError(t('获取容器信息失败') + ': ' + (error.response?.data?.message || error.message)); } finally { setContainersLoading(false); } }; useEffect(() => { if (visible && deployment?.id) { fetchDetails(); fetchContainers(); } else if (!visible) { setDetails(null); setContainers([]); } }, [visible, deployment?.id]); const handleCopyId = () => { navigator.clipboard.writeText(deployment?.id); showSuccess(t('ID已复制到剪贴板')); }; const handleRefresh = () => { fetchDetails(); fetchContainers(); }; const getStatusConfig = (status) => { const statusConfig = { 'running': { color: 'green', text: '运行中', icon: '🟢' }, 'completed': { color: 'green', text: '已完成', icon: '✅' }, 'deployment requested': { color: 'blue', text: '部署请求中', icon: '🔄' }, 'termination requested': { color: 'orange', text: '终止请求中', icon: '⏸️' }, 'destroyed': { color: 'red', text: '已销毁', icon: '🔴' }, 'failed': { color: 'red', text: '失败', icon: '❌' } }; return statusConfig[status] || { color: 'grey', text: status, icon: '❓' }; }; const statusConfig = getStatusConfig(deployment?.status); return ( {t('容器详情')} } visible={visible} onCancel={onCancel} footer={
} width={800} className="deployment-details-modal" > {loading && !details ? (
) : details ? (
{/* Basic Info */} {t('基本信息')}
} className="border-0 shadow-sm" > {details.deployment_name || details.id} )} {ctr.events && ctr.events.length > 0 && (
{t('最近事件')}
{ctr.events.map((event, index) => (
{event.time ? timestamp2string(event.time) : '--'} {event.message || '--'}
))}
)} ))} )} {/* Location Information */} {details.locations && details.locations.length > 0 && ( {t('部署位置')} } className="border-0 shadow-sm" >
{details.locations.map((location) => (
🌍 {location.name} ({location.iso2})
))}
)} {/* Cost Information */} {t('费用信息')} } className="border-0 shadow-sm" >
{t('已支付金额')} ${details.amount_paid ? details.amount_paid.toFixed(2) : '0.00'} USDC
{t('计费开始')}: {details.started_at ? timestamp2string(details.started_at) : 'N/A'}
{t('预计结束')}: {details.finished_at ? timestamp2string(details.finished_at) : 'N/A'}
{/* Time Information */} {t('时间信息')} } className="border-0 shadow-sm" >
{t('已运行时间')}: {Math.floor(details.compute_minutes_served / 60)}h {details.compute_minutes_served % 60}m
{t('剩余时间')}: {Math.floor(details.compute_minutes_remaining / 60)}h {details.compute_minutes_remaining % 60}m
{t('创建时间')}: {timestamp2string(details.created_at)}
{t('最后更新')}: {timestamp2string(details.updated_at)}
) : ( )}
); }; export default ViewDetailsModal;