forked from BLC/AyposWeb
instructions added
This commit is contained in:
70
README.md
70
README.md
@@ -1 +1,71 @@
|
|||||||
# AyposWeb
|
# AyposWeb
|
||||||
|
|
||||||
|
AyposWeb is a web-based monitoring and management system for virtual machines and compute resources.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before running this project, make sure you have the following installed:
|
||||||
|
- Node.js (version 16 or higher)
|
||||||
|
- npm (Node Package Manager)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd AyposWeb
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To run the project in development mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This will start the development server at `http://localhost:5173` (or another available port if 5173 is in use).
|
||||||
|
|
||||||
|
## Building for Production
|
||||||
|
|
||||||
|
To create a production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
The build output will be in the `dist` directory.
|
||||||
|
|
||||||
|
## Running Production Build
|
||||||
|
|
||||||
|
To preview the production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Virtual Machine Monitoring
|
||||||
|
- Stress Testing
|
||||||
|
- Migration Management
|
||||||
|
- Temperature Monitoring
|
||||||
|
- Resource Distribution Visualization
|
||||||
|
- System Maintenance
|
||||||
|
|
||||||
|
## API Configuration
|
||||||
|
|
||||||
|
The application connects to an API server at `http://141.196.83.136:8003`. Make sure this endpoint is accessible from your network.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
No additional environment variables are required to run the application in development mode.
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
|
||||||
|
The application is optimized for modern browsers that support ES6+ features.
|
||||||
|
|||||||
378
src/pages/Test.tsx
Normal file
378
src/pages/Test.tsx
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Paper,
|
||||||
|
styled,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
Button,
|
||||||
|
Grid,
|
||||||
|
CircularProgress,
|
||||||
|
Chip,
|
||||||
|
Alert,
|
||||||
|
Snackbar,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Checkbox
|
||||||
|
} from '@mui/material';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
borderRadius: theme.spacing(2),
|
||||||
|
height: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const PageTitle = styled(Typography)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
'& svg': {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StressTestingCard = styled(Paper)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
borderRadius: theme.spacing(2),
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const VMSelectionCard = styled(Paper)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
borderRadius: theme.spacing(2),
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StressLevelChip = styled(Chip)<{ level: 'low' | 'medium' | 'high' }>(({ theme, level }) => ({
|
||||||
|
borderRadius: theme.spacing(1),
|
||||||
|
fontWeight: 500,
|
||||||
|
backgroundColor:
|
||||||
|
level === 'low' ? theme.palette.success.light :
|
||||||
|
level === 'medium' ? theme.palette.warning.light :
|
||||||
|
theme.palette.error.light,
|
||||||
|
color:
|
||||||
|
level === 'low' ? theme.palette.success.dark :
|
||||||
|
level === 'medium' ? theme.palette.warning.dark :
|
||||||
|
theme.palette.error.dark,
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface ComputeNode {
|
||||||
|
host_ip: string;
|
||||||
|
hosted_vms: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MonitoringResponse {
|
||||||
|
optimization_space: Record<string, ComputeNode>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VM {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
ip: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Test = () => {
|
||||||
|
const [stressLevel, setStressLevel] = useState<'low' | 'medium' | 'high'>('low');
|
||||||
|
const [stressedVMs, setStressedVMs] = useState<string[]>([]);
|
||||||
|
const [isStressTesting, setIsStressTesting] = useState(false);
|
||||||
|
const [isLoadingStress, setIsLoadingStress] = useState(false);
|
||||||
|
const [alert, setAlert] = useState<{ open: boolean; message: string; severity: 'success' | 'error' | 'info' }>({
|
||||||
|
open: false,
|
||||||
|
message: '',
|
||||||
|
severity: 'info',
|
||||||
|
});
|
||||||
|
const [selectedVMs, setSelectedVMs] = useState<string[]>([]);
|
||||||
|
const [availableVMs, setAvailableVMs] = useState<VM[]>([]);
|
||||||
|
const [isLoadingVMs, setIsLoadingVMs] = useState(false);
|
||||||
|
|
||||||
|
// Load optimization state from localStorage
|
||||||
|
useEffect(() => {
|
||||||
|
const savedState = localStorage.getItem('optimizationState');
|
||||||
|
if (savedState) {
|
||||||
|
const { selectedVMs: optimizedVMs } = JSON.parse(savedState);
|
||||||
|
setSelectedVMs(optimizedVMs || []);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Fetch available VMs
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchVMs = async () => {
|
||||||
|
setIsLoadingVMs(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://141.196.83.136:8003/prom/monitoring');
|
||||||
|
const data: MonitoringResponse = await response.json();
|
||||||
|
|
||||||
|
// Extract VMs from the optimization space
|
||||||
|
const vms: VM[] = [];
|
||||||
|
if (data.optimization_space) {
|
||||||
|
Object.entries(data.optimization_space).forEach(([computeName, computeData]) => {
|
||||||
|
Object.entries(computeData.hosted_vms).forEach(([vmName, vmIp]) => {
|
||||||
|
vms.push({
|
||||||
|
id: `${computeName}-${vmName}`,
|
||||||
|
name: vmName,
|
||||||
|
ip: vmIp
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setAvailableVMs(vms);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching VMs:', error);
|
||||||
|
setAlert({
|
||||||
|
open: true,
|
||||||
|
message: 'Failed to fetch available VMs',
|
||||||
|
severity: 'error'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoadingVMs(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchVMs();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Add status polling for stress test
|
||||||
|
useEffect(() => {
|
||||||
|
let interval: NodeJS.Timeout;
|
||||||
|
|
||||||
|
const pollStressStatus = async () => {
|
||||||
|
try {
|
||||||
|
if (selectedVMs.length > 0) {
|
||||||
|
const status = await stressService.getStressStatus(selectedVMs);
|
||||||
|
setStressedVMs(status);
|
||||||
|
setIsStressTesting(status.length > 0);
|
||||||
|
} else {
|
||||||
|
setStressedVMs([]);
|
||||||
|
setIsStressTesting(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error polling stress status:', error);
|
||||||
|
setStressedVMs([]);
|
||||||
|
setIsStressTesting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isStressTesting) {
|
||||||
|
interval = setInterval(pollStressStatus, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (interval) {
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isStressTesting, selectedVMs]);
|
||||||
|
|
||||||
|
const handleToggleVM = (vmIp: string) => {
|
||||||
|
setSelectedVMs(prev =>
|
||||||
|
prev.includes(vmIp)
|
||||||
|
? prev.filter(ip => ip !== vmIp)
|
||||||
|
: [...prev, vmIp]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStartStressTest = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoadingStress(true);
|
||||||
|
|
||||||
|
if (selectedVMs.length === 0) {
|
||||||
|
setAlert({
|
||||||
|
open: true,
|
||||||
|
message: 'Please select at least one VM to stress test',
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await stressService.startStressTest({
|
||||||
|
vms: selectedVMs,
|
||||||
|
level: stressLevel,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
setIsStressTesting(true);
|
||||||
|
setAlert({
|
||||||
|
open: true,
|
||||||
|
message: 'Stress test started successfully',
|
||||||
|
severity: 'success',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in handleStartStressTest:', error);
|
||||||
|
setAlert({
|
||||||
|
open: true,
|
||||||
|
message: error instanceof Error ? error.message : 'Failed to start stress test',
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoadingStress(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStopStressTest = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoadingStress(true);
|
||||||
|
|
||||||
|
await stressService.stopStressTest(selectedVMs);
|
||||||
|
setIsStressTesting(false);
|
||||||
|
setStressedVMs([]);
|
||||||
|
setAlert({
|
||||||
|
open: true,
|
||||||
|
message: 'Stress test stopped successfully',
|
||||||
|
severity: 'success',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in handleStopStressTest:', error);
|
||||||
|
setAlert({
|
||||||
|
open: true,
|
||||||
|
message: error instanceof Error ? error.message : 'Failed to stop stress test',
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoadingStress(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<PageTitle variant="h4">
|
||||||
|
<ScienceIcon fontSize="large" />
|
||||||
|
Test Page
|
||||||
|
</PageTitle>
|
||||||
|
|
||||||
|
<VMSelectionCard elevation={3}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||||
|
<ComputerIcon color="primary" sx={{ mr: 1, fontSize: '2rem' }} />
|
||||||
|
<Typography variant="h6">Virtual Machines</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{isLoadingVMs ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
|
Select VMs from your optimization space to run stress tests.
|
||||||
|
</Typography>
|
||||||
|
<List>
|
||||||
|
{availableVMs.map((vm) => (
|
||||||
|
<ListItem key={vm.id} disablePadding>
|
||||||
|
<ListItemButton
|
||||||
|
dense
|
||||||
|
onClick={() => handleToggleVM(vm.ip)}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Checkbox
|
||||||
|
edge="start"
|
||||||
|
checked={selectedVMs.includes(vm.ip)}
|
||||||
|
tabIndex={-1}
|
||||||
|
disableRipple
|
||||||
|
/>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={vm.name}
|
||||||
|
secondary={vm.ip}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</VMSelectionCard>
|
||||||
|
|
||||||
|
<StressTestingCard elevation={3}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||||
|
<SpeedIcon color="primary" sx={{ mr: 1, fontSize: '2rem' }} />
|
||||||
|
<Typography variant="h6">Stress Testing</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel>Stress Level</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={stressLevel}
|
||||||
|
label="Stress Level"
|
||||||
|
onChange={(e) => setStressLevel(e.target.value as 'low' | 'medium' | 'high')}
|
||||||
|
disabled={isStressTesting || isLoadingStress}
|
||||||
|
>
|
||||||
|
<MenuItem value="low">Low</MenuItem>
|
||||||
|
<MenuItem value="medium">Medium</MenuItem>
|
||||||
|
<MenuItem value="high">High</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={handleStartStressTest}
|
||||||
|
disabled={isStressTesting || isLoadingStress || selectedVMs.length === 0}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{isLoadingStress ? <CircularProgress size={24} /> : 'Start Stress Test'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
onClick={handleStopStressTest}
|
||||||
|
disabled={!isStressTesting || isLoadingStress}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{isLoadingStress ? <CircularProgress size={24} /> : 'Stop Stress Test'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{stressedVMs.length > 0 && (
|
||||||
|
<Box sx={{ mt: 3 }}>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>Currently Stressed VMs:</Typography>
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
{stressedVMs.map((vm) => (
|
||||||
|
<StressLevelChip
|
||||||
|
key={vm}
|
||||||
|
label={vm}
|
||||||
|
level={stressLevel}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</StressTestingCard>
|
||||||
|
|
||||||
|
<Snackbar
|
||||||
|
open={alert.open}
|
||||||
|
autoHideDuration={6000}
|
||||||
|
onClose={() => setAlert({ ...alert, open: false })}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
onClose={() => setAlert({ ...alert, open: false })}
|
||||||
|
severity={alert.severity}
|
||||||
|
variant="filled"
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{alert.message}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Test;
|
||||||
Reference in New Issue
Block a user