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[];