diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fab5202 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# API Configuration +VITE_API_URL=http://141.196.166.241:8003 + +# Add other environment variables as needed \ No newline at end of file diff --git a/README.md b/README.md index 2cb7e81..5dcfb62 100644 --- a/README.md +++ b/README.md @@ -69,3 +69,21 @@ No additional environment variables are required to run the application in devel ## Browser Support The application is optimized for modern browsers that support ES6+ features. + +## Environment Configuration + +The application uses environment variables for configuration. To set up your environment: + +1. Copy `.env.example` to `.env`: +```bash +cp .env.example .env +``` + +2. Edit `.env` and set your environment variables: +```env +VITE_API_URL=http://your-api-server:port +``` + +The following environment variables are available: + +- `VITE_API_URL`: The URL of the API server (default: http://141.196.166.241:8003) diff --git a/src/components/Migration/SummaryStats.tsx b/src/components/Migration/SummaryStats.tsx index 50eb523..d4bf3ab 100644 --- a/src/components/Migration/SummaryStats.tsx +++ b/src/components/Migration/SummaryStats.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Grid, Paper, Typography, Box, Divider } from '@mui/material'; import StorageIcon from '@mui/icons-material/Storage'; import ComputerIcon from '@mui/icons-material/Computer'; +import { config } from '../../config/env'; interface VM { name: string; @@ -21,7 +22,7 @@ interface VMPlacementData { }>; } -const ENDPOINT = 'http://141.196.166.241:8003/prom/get_chart_data/vm_placement'; +const ENDPOINT = `${config.apiUrl}/prom/get_chart_data/vm_placement`; const REFRESH_INTERVAL = 30000; // 30 seconds const SummaryStats: React.FC = () => { diff --git a/src/components/Migration/hooks.ts b/src/components/Migration/hooks.ts index 180b2ba..02346c3 100644 --- a/src/components/Migration/hooks.ts +++ b/src/components/Migration/hooks.ts @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react'; import { VMDetails, GainBeforeData, MigrationAdviceData } from './types'; +import { config } from '../../config/env'; -const API_BASE_URL = 'http://141.196.166.241:8003'; +const API_BASE_URL = config.apiUrl; const REFRESH_INTERVAL = 30000; // 30 seconds interface GainAfterData { @@ -23,13 +24,20 @@ export const useMigrationData = () => { try { setIsLoadingGainData(true); + // Log the request start + console.log('Fetching migration data...'); + const [gainResponse, migrationResponse] = await Promise.all([ fetch(`${API_BASE_URL}/prom/get_chart_data/gain_before`), fetch(`${API_BASE_URL}/prom/get_chart_data/migration`) ]); + // Log the response status + console.log('Gain Response Status:', gainResponse.status); + console.log('Migration Response Status:', migrationResponse.status); + if (!gainResponse.ok || !migrationResponse.ok) { - throw new Error('Failed to fetch migration data'); + throw new Error(`Failed to fetch migration data: Gain Status ${gainResponse.status}, Migration Status ${migrationResponse.status}`); } const [gainData, migrationData] = await Promise.all([ @@ -37,10 +45,26 @@ export const useMigrationData = () => { migrationResponse.json() ]); + // Log the received data + console.log('Received Gain Data:', gainData); + console.log('Received Migration Data:', migrationData); + + if (!gainData || typeof gainData.cur_power === 'undefined') { + console.error('Invalid gain data format:', gainData); + throw new Error('Invalid gain data format'); + } + + if (!migrationData || Object.keys(migrationData).length === 0) { + console.warn('No migration advice available:', migrationData); + } + setGainBeforeData(gainData); setMigrationAdviceData(migrationData); } catch (error) { console.error('Error fetching migration data:', error); + // Clear the data on error to prevent showing stale data + setGainBeforeData(null); + setMigrationAdviceData(null); } finally { setIsLoadingGainData(false); } diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000..4269db2 --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,9 @@ +// Environment configuration +const getApiUrl = (): string => { + // Use environment variable if available, fallback to development URL + return import.meta.env.VITE_API_URL || 'http://141.196.166.241:8003'; +}; + +export const config = { + apiUrl: getApiUrl(), +} as const; \ No newline at end of file diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..eaf6b75 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} \ No newline at end of file diff --git a/src/pages/Maintenance.tsx b/src/pages/Maintenance.tsx index 948f84a..26fd508 100644 --- a/src/pages/Maintenance.tsx +++ b/src/pages/Maintenance.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { Box, Paper, Typography, Fade, useTheme, AppBar, Toolbar, Chip } from '@mui/material'; import Plot from 'react-plotly.js'; import { Layout, PlotData } from 'plotly.js'; +import { config } from '../config/env'; interface DataItem { now_timestamp: string; @@ -15,6 +16,8 @@ interface DataItem { flag: string; } +const API_BASE_URL = config.apiUrl; + const Maintenance = () => { const theme = useTheme(); @@ -24,7 +27,7 @@ const Maintenance = () => { useEffect(() => { const fetchData = async () => { try { - const response = await fetch('http://141.196.166.241:8003/prom/get_chart_data/maintenance/20'); + const response = await fetch(`${API_BASE_URL}/prom/get_chart_data/maintenance/20`); const result = await response.json(); if (result.data && result.data.length > 0) { diff --git a/src/pages/Migration.tsx b/src/pages/Migration.tsx index e46ea8f..f26eba1 100644 --- a/src/pages/Migration.tsx +++ b/src/pages/Migration.tsx @@ -29,9 +29,10 @@ import ResourceDistributionChart from '../components/Migration/ResourceDistribut import MigrationAdviceCard from '../components/Migration/MigrationAdviceCard'; import VerifiedMigration from '../components/Migration/VerifiedMigration'; import { useMigrationData, useGainAfterData } from '../components/Migration/hooks'; +import { config } from '../config/env'; // Constants -const API_BASE_URL = 'http://141.196.166.241:8003'; +const API_BASE_URL = config.apiUrl; const REFRESH_INTERVAL = 30000; // 30 seconds interface VMPlacementData { @@ -412,8 +413,7 @@ const Migration = () => { setMigrationProgress([]); setHasProgress(true); - // First, send the POST request for migration approval - const approvalResponse = await fetch('http://141.196.166.241:8003/prom/migration/decisions4?run_migration=true', { + const approvalResponse = await fetch(`${API_BASE_URL}/prom/migration/decisions4?run_migration=true`, { method: 'POST', headers: { 'accept': 'application/json' @@ -455,7 +455,7 @@ const Migration = () => { try { setIsProcessing(true); - const response = await fetch('http://141.196.166.241:8003/prom/migration/decisions4?run_migration=false', { + const response = await fetch(`${API_BASE_URL}/prom/migration/decisions4?run_migration=false`, { method: 'POST', headers: { 'accept': 'application/json' diff --git a/src/pages/MonitoringSystem.tsx b/src/pages/MonitoringSystem.tsx index 57261a8..642415b 100644 --- a/src/pages/MonitoringSystem.tsx +++ b/src/pages/MonitoringSystem.tsx @@ -23,6 +23,9 @@ import ComputerIcon from '@mui/icons-material/Computer'; import MemoryIcon from '@mui/icons-material/Memory'; import RefreshIcon from '@mui/icons-material/Refresh'; import SaveIcon from '@mui/icons-material/Save'; +import { config } from '../config/env'; + +const API_BASE_URL = config.apiUrl; // Define the structure of our tree nodes interface TreeNode { @@ -108,7 +111,7 @@ const MonitoringSystem: React.FC = ({ const fetchData = async () => { setLoading(true); try { - const response = await fetch('http://141.196.166.241:8003/prom/monitoring'); + const response = await fetch(`${API_BASE_URL}/prom/monitoring`); const result: ApiResponse = await response.json(); // Create hierarchical structure diff --git a/src/pages/Temperature.tsx b/src/pages/Temperature.tsx index a04b04d..56f4c4e 100644 --- a/src/pages/Temperature.tsx +++ b/src/pages/Temperature.tsx @@ -5,6 +5,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import CancelIcon from '@mui/icons-material/Cancel'; import Plot from 'react-plotly.js'; import { Layout, PlotData, Config } from 'plotly.js'; +import { config } from '../config/env'; // Extend the Window interface to include Google Charts declare global { @@ -26,6 +27,8 @@ interface ChartData { power_future_min: string; } +const API_BASE_URL = config.apiUrl; + const Temperature = () => { const theme = useTheme(); const [data, setData] = useState([]); @@ -52,7 +55,7 @@ const Temperature = () => { setRefreshing(true); } - const response = await fetch('http://141.196.166.241:8003/prom/get_chart_data/temperature/20'); + const response = await fetch(`${API_BASE_URL}/prom/get_chart_data/temperature/20`); const result = await response.json(); if (result.data && result.data.length > 0) { @@ -337,7 +340,7 @@ const Temperature = () => { const handleTemperatureDecision = async (approval: boolean) => { try { setDecisionLoading(true); - const response = await fetch('http://141.196.166.241:8003/prom/temperature/decisions?approval=' + approval, { + const response = await fetch(`${API_BASE_URL}/prom/temperature/decisions?approval=${approval}`, { method: 'POST', headers: { 'accept': 'application/json', diff --git a/src/pages/Test.tsx b/src/pages/Test.tsx index 4c805aa..e719143 100644 --- a/src/pages/Test.tsx +++ b/src/pages/Test.tsx @@ -25,6 +25,7 @@ import ScienceIcon from '@mui/icons-material/Science'; import SpeedIcon from '@mui/icons-material/Speed'; import ComputerIcon from '@mui/icons-material/Computer'; import { stressService } from '../services/stressService'; +import { config } from '../config/env'; const PageTitle = styled(Typography)(({ theme }) => ({ display: 'flex', @@ -78,6 +79,8 @@ interface VM { ip: string; } +const API_BASE_URL = config.apiUrl; + const Test = () => { const [stressLevel, setStressLevel] = useState<'low' | 'medium' | 'high'>('low'); const [stressedVMs, setStressedVMs] = useState([]); @@ -106,7 +109,7 @@ const Test = () => { const fetchVMs = async () => { setIsLoadingVMs(true); try { - const response = await fetch('http://141.196.166.241:8003/prom/monitoring'); + const response = await fetch(`${API_BASE_URL}/prom/monitoring`); const data: MonitoringResponse = await response.json(); // Extract VMs from the optimization space diff --git a/src/services/monitoringService.ts b/src/services/monitoringService.ts index 0f8582a..9b5d38a 100644 --- a/src/services/monitoringService.ts +++ b/src/services/monitoringService.ts @@ -1,6 +1,7 @@ import axios from 'axios'; +import { config } from '../config/env'; -const BASE_URL = 'http://141.196.166.241:8003'; +const BASE_URL = config.apiUrl; export interface MonitoringConfig { migration: { @@ -71,11 +72,46 @@ class MonitoringService { throw new Error('Invalid configuration: Missing required sections'); } - // Ensure script_time_unit has valid values - if (!config.migration.script_time_unit || - !config.environmental.script_time_unit || - !config.preventive.script_time_unit) { - throw new Error('Invalid configuration: Missing script_time_unit values'); + // Validate migration configuration + const { migration } = config; + if (!migration.script_time_unit || + !migration.virtual_machine_estimation || + !migration.migration_advices || + !migration.block_list) { + throw new Error('Invalid migration configuration: Missing required fields'); + } + + // Validate estimation configuration + const { virtual_machine_estimation } = migration; + if (!virtual_machine_estimation.estimation_method || !virtual_machine_estimation.model_type) { + throw new Error('Invalid estimation configuration: Missing required fields'); + } + + // Validate migration advice configuration + const { migration_advices } = migration; + if (!migration_advices.migration_method || !migration_advices.migration_weights) { + throw new Error('Invalid migration advice configuration: Missing required fields'); + } + + // Validate weights + const { migration_weights } = migration_advices; + if (!migration_weights.power || !migration_weights.balance || + !migration_weights.overload || !migration_weights.allocation) { + throw new Error('Invalid weights configuration: Missing required fields'); + } + + // Validate environmental configuration + if (!config.environmental.script_time_unit || + !config.environmental.number_of_steps || + !config.environmental.model_type) { + throw new Error('Invalid environmental configuration: Missing required fields'); + } + + // Validate preventive configuration + if (!config.preventive.script_time_unit || + !config.preventive.number_of_steps || + !config.preventive.model_type) { + throw new Error('Invalid preventive configuration: Missing required fields'); } // Log the configuration being sent @@ -103,7 +139,9 @@ class MonitoringService { throw new Error(`Failed to start monitoring: Status ${response.status}`); } - console.log('Monitoring started successfully:', response.data); + // Log the response data + console.log('Monitoring started successfully. Response:', response.data); + return; } catch (error) { if (axios.isCancel(error)) { diff --git a/src/services/stressService.ts b/src/services/stressService.ts index 233b2c3..49d17f4 100644 --- a/src/services/stressService.ts +++ b/src/services/stressService.ts @@ -1,6 +1,7 @@ import axios from 'axios'; +import { config } from '../config/env'; -const BASE_URL = 'http://141.196.166.241:8003'; +const BASE_URL = config.apiUrl; export interface StressConfig { vms: string[];