| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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 { |
| | |
| | hardwareTypes, |
| | hardwareTotalAvailable, |
| | locations, |
| | locationsTotalAvailable, |
| | availableReplicas, |
| | priceEstimation, |
| |
|
| | |
| | loadingHardware, |
| | loadingLocations, |
| | loadingReplicas, |
| | loadingPrice, |
| |
|
| | |
| | fetchHardwareTypes, |
| | fetchLocations, |
| | fetchAvailableReplicas, |
| | calculatePrice, |
| | checkClusterNameAvailability, |
| | createDeployment, |
| |
|
| | |
| | clearPriceEstimation: () => setPriceEstimation(null), |
| | clearAvailableReplicas: () => setAvailableReplicas([]), |
| | }; |
| | }; |
| |
|
| | export default useDeploymentResources; |
| |
|