/* 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 { useState, useCallback } from 'react'; import { API } from '../../helpers'; import { showError } from '../../helpers'; export const useDeploymentResources = () => { const [hardwareTypes, setHardwareTypes] = useState([]); const [hardwareTotalAvailable, setHardwareTotalAvailable] = useState(0); const [locations, setLocations] = useState([]); const [locationsTotalAvailable, setLocationsTotalAvailable] = useState(0); const [availableReplicas, setAvailableReplicas] = useState([]); const [priceEstimation, setPriceEstimation] = useState(null); const [loadingHardware, setLoadingHardware] = useState(false); const [loadingLocations, setLoadingLocations] = useState(false); const [loadingReplicas, setLoadingReplicas] = useState(false); const [loadingPrice, setLoadingPrice] = useState(false); const fetchHardwareTypes = useCallback(async () => { try { setLoadingHardware(true); const response = await API.get('/api/deployments/hardware-types'); if (response.data.success) { const { hardware_types: hardwareList = [], total_available } = response.data.data || {}; const normalizedHardware = hardwareList.map((hardware) => { const availableCountValue = Number(hardware.available_count); const availableCount = Number.isNaN(availableCountValue) ? 0 : availableCountValue; const availableBool = typeof hardware.available === 'boolean' ? hardware.available : availableCount > 0; return { ...hardware, available: availableBool, available_count: availableCount, }; }); const providedTotal = Number(total_available); const fallbackTotal = normalizedHardware.reduce( (acc, item) => acc + (Number.isNaN(item.available_count) ? 0 : item.available_count), 0, ); const hasProvidedTotal = total_available !== undefined && total_available !== null && total_available !== '' && !Number.isNaN(providedTotal); setHardwareTypes(normalizedHardware); setHardwareTotalAvailable( hasProvidedTotal ? providedTotal : fallbackTotal, ); return normalizedHardware; } else { showError('获取硬件类型失败: ' + response.data.message); setHardwareTotalAvailable(0); return []; } } catch (error) { showError('获取硬件类型失败: ' + error.message); setHardwareTotalAvailable(0); return []; } finally { setLoadingHardware(false); } }, []); const fetchLocations = useCallback(async () => { try { setLoadingLocations(true); const response = await API.get('/api/deployments/locations'); if (response.data.success) { const { locations: locationsList = [], total } = response.data.data || {}; const normalizedLocations = locationsList.map((location) => { const iso2 = (location.iso2 || '').toString().toUpperCase(); const availableValue = Number(location.available); const available = Number.isNaN(availableValue) ? 0 : availableValue; return { ...location, iso2, available, }; }); const providedTotal = Number(total); const fallbackTotal = normalizedLocations.reduce( (acc, item) => acc + (Number.isNaN(item.available) ? 0 : item.available), 0, ); const hasProvidedTotal = total !== undefined && total !== null && total !== '' && !Number.isNaN(providedTotal); setLocations(normalizedLocations); setLocationsTotalAvailable( hasProvidedTotal ? providedTotal : fallbackTotal, ); return normalizedLocations; } else { showError('获取部署位置失败: ' + response.data.message); setLocationsTotalAvailable(0); return []; } } catch (error) { showError('获取部署位置失败: ' + error.message); setLocationsTotalAvailable(0); return []; } finally { setLoadingLocations(false); } }, []); const fetchAvailableReplicas = useCallback(async (hardwareId, gpuCount = 1) => { if (!hardwareId) { setAvailableReplicas([]); return []; } try { setLoadingReplicas(true); const response = await API.get( `/api/deployments/available-replicas?hardware_id=${hardwareId}&gpu_count=${gpuCount}` ); if (response.data.success) { const replicas = response.data.data.replicas || []; setAvailableReplicas(replicas); return replicas; } else { showError('获取可用资源失败: ' + response.data.message); setAvailableReplicas([]); return []; } } catch (error) { console.error('Load available replicas error:', error); setAvailableReplicas([]); return []; } finally { setLoadingReplicas(false); } }, []); const calculatePrice = useCallback(async (params) => { const { locationIds, hardwareId, gpusPerContainer, durationHours, replicaCount } = params; if (!locationIds?.length || !hardwareId || !gpusPerContainer || !durationHours || !replicaCount) { setPriceEstimation(null); return null; } try { setLoadingPrice(true); const requestData = { location_ids: locationIds, hardware_id: hardwareId, gpus_per_container: gpusPerContainer, duration_hours: durationHours, replica_count: replicaCount, }; const response = await API.post('/api/deployments/price-estimation', requestData); if (response.data.success) { const estimation = response.data.data; setPriceEstimation(estimation); return estimation; } else { showError('价格计算失败: ' + response.data.message); setPriceEstimation(null); return null; } } catch (error) { console.error('Price calculation error:', error); setPriceEstimation(null); return null; } finally { setLoadingPrice(false); } }, []); const checkClusterNameAvailability = useCallback(async (name) => { if (!name?.trim()) return false; try { const response = await API.get(`/api/deployments/check-name?name=${encodeURIComponent(name.trim())}`); if (response.data.success) { return response.data.data.available; } else { showError('检查名称可用性失败: ' + response.data.message); return false; } } catch (error) { console.error('Check cluster name availability error:', error); return false; } }, []); const createDeployment = useCallback(async (deploymentData) => { try { const response = await API.post('/api/deployments', deploymentData); if (response.data.success) { return response.data.data; } else { throw new Error(response.data.message || '创建部署失败'); } } catch (error) { throw error; } }, []); return { // Data hardwareTypes, hardwareTotalAvailable, locations, locationsTotalAvailable, availableReplicas, priceEstimation, // Loading states loadingHardware, loadingLocations, loadingReplicas, loadingPrice, // Functions fetchHardwareTypes, fetchLocations, fetchAvailableReplicas, calculatePrice, checkClusterNameAvailability, createDeployment, // Clear functions clearPriceEstimation: () => setPriceEstimation(null), clearAvailableReplicas: () => setAvailableReplicas([]), }; }; export default useDeploymentResources;