forked from BLC/AyposWeb
378 lines
11 KiB
TypeScript
378 lines
11 KiB
TypeScript
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.166.241: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; |