import { useState, useEffect, useCallback } from 'react'; import { Box, Paper, Typography, FormControl, InputLabel, Select, MenuItem, Button, Grid, useTheme, styled, SelectChangeEvent, Slider, TextField, Snackbar, Alert, Chip, CircularProgress, Tooltip, ToggleButton, ToggleButtonGroup, Dialog, DialogTitle, DialogContent, DialogActions, } from '@mui/material'; import ThermostatIcon from '@mui/icons-material/Thermostat'; import BuildIcon from '@mui/icons-material/Build'; import SwapHorizIcon from '@mui/icons-material/SwapHoriz'; import TimelineIcon from '@mui/icons-material/Timeline'; import ModelTrainingIcon from '@mui/icons-material/ModelTraining'; import SpeedIcon from '@mui/icons-material/Speed'; import StopIcon from '@mui/icons-material/Stop'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import BalanceIcon from '@mui/icons-material/Balance'; import BoltIcon from '@mui/icons-material/Bolt'; import GridViewIcon from '@mui/icons-material/GridView'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import ErrorIcon from '@mui/icons-material/Error'; import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; import HandymanIcon from '@mui/icons-material/Handyman'; import EditIcon from '@mui/icons-material/Edit'; import { monitoringService } from '../services/monitoringService'; import DebugConsole from '../components/DebugConsole'; import MonitoringSystem from './MonitoringSystem'; import { Weights, AlertState, MonitoringStatus } from '../types/monitoring'; // Professional, consistent card style (no animation) const StyledCard = styled(Paper)(({ theme }) => ({ borderRadius: '0.6rem', padding: theme.spacing(3), minHeight: 260, backgroundColor: theme.palette.background.paper, boxShadow: theme.shadows[2], display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', })); const StyledSelect = styled(Select)(({ theme }) => ({ '& .MuiOutlinedInput-notchedOutline': { borderRadius: '0.357rem', borderColor: theme.palette.divider, }, '&:hover .MuiOutlinedInput-notchedOutline': { borderColor: theme.palette.primary.main, }, })); // Professional header style const SectionTitle = styled(Typography)(({ theme }) => ({ display: 'flex', alignItems: 'center', gap: theme.spacing(1), marginBottom: theme.spacing(3), color: theme.palette.text.primary, fontWeight: 700, fontFamily: 'Montserrat, Helvetica, Arial, serif', fontSize: theme.typography.h6.fontSize, // Use theme's h6 size (usually 1.125rem) })); const IconWrapper = styled(Box)(({ theme }) => ({ display: 'flex', alignItems: 'center', gap: theme.spacing(0.5), color: theme.palette.text.primary, marginBottom: theme.spacing(1), })); const WeightSlider = styled(Slider)(({ theme }) => ({ '& .MuiSlider-thumb': { height: 24, width: 24, backgroundColor: theme.palette.background.paper, border: `2px solid ${theme.palette.primary.main}`, '&:focus, &:hover, &.Mui-active, &.Mui-focusVisible': { boxShadow: 'inherit', }, }, '& .MuiSlider-track': { height: 4, backgroundColor: theme.palette.primary.main, }, '& .MuiSlider-rail': { height: 4, opacity: 0.5, backgroundColor: theme.palette.divider, }, '& .MuiSlider-mark': { backgroundColor: theme.palette.primary.main, height: 12, width: 2, '&.MuiSlider-markActive': { backgroundColor: theme.palette.primary.main, }, }, '& .MuiSlider-markLabel': { fontSize: '0.75rem', color: theme.palette.text.secondary, }, })); const WeightInput = styled(TextField)(({ theme }) => ({ width: 70, '& input': { padding: '8px', textAlign: 'center', fontFamily: theme.typography.fontFamily, }, })); const StatusChip = styled(Chip)(({ theme }) => ({ borderRadius: '0.357rem', fontWeight: 600, fontFamily: theme.typography.fontFamily, '&.running': { backgroundColor: theme.palette.success.main, color: theme.palette.common.white, }, '&.stopped': { backgroundColor: theme.palette.error.main, color: theme.palette.common.white, }, })); const StatusCard = styled(Box)(({ theme }) => ({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: theme.spacing(1), padding: theme.spacing(2), borderRadius: '0.357rem', backgroundColor: theme.palette.background.paper, boxShadow: theme.shadows[1], minWidth: 280, position: 'relative', overflow: 'hidden', })); const StatusIndicator = styled('div')(({ theme }) => ({ position: 'absolute', bottom: 0, left: 0, right: 0, height: 3, backgroundColor: 'transparent', '&.loading': { background: `linear-gradient(90deg, ${theme.palette.primary.main}, ${theme.palette.primary.light}, ${theme.palette.primary.main})`, backgroundSize: '200% 100%', animation: 'shimmer 2s infinite', }, '&.success': { backgroundColor: theme.palette.success.main, }, '&.error': { backgroundColor: theme.palette.error.main, }, '&.partial': { backgroundColor: theme.palette.warning.main, }, })); // Add keyframes for the loading animation const shimmerKeyframes = ` @keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } } `; // Insert the keyframes const styleElement = document.createElement('style'); styleElement.type = 'text/css'; styleElement.innerHTML = shimmerKeyframes; document.head.appendChild(styleElement); const Home = () => { const theme = useTheme(); // Initialize all states from localStorage if available const savedState = localStorage.getItem('optimizationState'); const parsedState = savedState ? JSON.parse(savedState) : null; const [hasSelectedOptimization, setHasSelectedOptimization] = useState(!!parsedState); const [showOptimizationDialog, setShowOptimizationDialog] = useState(false); // Initialize state from localStorage if available const [blockList, setBlockList] = useState(parsedState?.unselectedVMs || []); // @ts-ignore - Will be used in future implementation const [selectedVMs, setSelectedVMs] = useState(parsedState?.selectedVMs || []); const [optimizationState, setOptimizationState] = useState<{ selectedVMs: string[]; unselectedVMs: string[]; }>(parsedState || { selectedVMs: [], unselectedVMs: [] }); // Environmental Temperature Section const [envTimeUnit, setEnvTimeUnit] = useState('1'); const [envSteps, setEnvSteps] = useState('3'); const [envModelType, setEnvModelType] = useState('lstm'); // Preventive Maintenance Section const [prevTimeUnit, setPrevTimeUnit] = useState('1'); const [prevSteps, setPrevSteps] = useState('3'); const [prevModelType, setPrevModelType] = useState('lstm'); // Migration Section const [migrationTime, setMigrationTime] = useState('5'); const [migrationModel, setMigrationModel] = useState('mul_reg'); const [migrationMethod, setMigrationMethod] = useState('mathematical'); // Default to semiauto since auto mode is not available yet const [migrationMode, setMigrationMode] = useState<'auto' | 'semiauto'>('semiauto'); const [isMonitoring, setIsMonitoring] = useState(false); // Weight Configuration Section const [weights, setWeights] = useState({ energy: 25, balance: 25, overload: 25, allocation: 25 }); const [isValid, setIsValid] = useState(true); const [weightError, setWeightError] = useState(false); const [isLoading, setIsLoading] = useState(false); const [alert, setAlert] = useState({ open: false, message: '', severity: 'info', }); const [monitoringStatus, setMonitoringStatus] = useState(null); const [isStatusLoading, setIsStatusLoading] = useState(false); // Add state for the last configuration sent (for debug console) const [lastConfigSent, setLastConfigSent] = useState(null); // Updated options to match endpoint expectations but keeping original time options const timeOptions = ['1', '5']; const stepOptions = ['3', ...Array.from({ length: 47 }, (_, i) => (i + 4).toString())]; const modelTypes = ['lstm']; const migrationModelTypes = { direct: ['ssl'], indirect: ['xgboost', 'mul_reg'] }; const migrationMethodTypes = ['mathematical', 'AI']; // Inside the Home component, add new state const [estimationMethod, setEstimationMethod] = useState<'direct' | 'indirect'>('indirect'); // Memoized validation function const validateWeightSum = useCallback(() => { const sum = Object.values(weights).reduce((a, b) => a + Number(b), 0); return Math.abs(sum - 100) < 0.001; // Using small epsilon for floating point comparison }, [weights]); // Real-time validation effect useEffect(() => { const isValidSum = validateWeightSum(); setIsValid(isValidSum); setWeightError(!isValidSum); }, [weights, validateWeightSum]); const handleManualWeightChange = (type: keyof Weights) => ( event: React.ChangeEvent ) => { const newValue = event.target.value === '' ? 0 : parseInt(event.target.value); if (isNaN(newValue)) return; setWeights(prev => ({ ...prev, [type]: Math.min(100, Math.max(0, Math.round(newValue))) })); }; const handleSliderChange = (type: keyof Weights) => (_: Event, newValue: number | number[]) => { const value = Math.round(newValue as number); const newWeights = { ...weights }; newWeights[type] = value; const remainingTypes = Object.keys(weights).filter(k => k !== type) as Array; const totalOthers = remainingTypes.reduce((sum, key) => sum + weights[key], 0); if (totalOthers > 0) { const scale = (100 - value) / totalOthers; remainingTypes.forEach(key => { newWeights[key] = Math.round(weights[key] * scale); }); } else { // If all others are 0, distribute remaining evenly const remaining = Math.round((100 - value) / remainingTypes.length); remainingTypes.forEach(key => { newWeights[key] = remaining; }); } setWeights(newWeights); }; const handleCloseAlert = () => { setAlert(prev => ({ ...prev, open: false })); }; const showAlert = (message: string, severity: AlertState['severity']) => { setAlert({ open: true, message, severity, }); }; const handleMigrationModeChange = ( _: React.MouseEvent, newMode: 'auto' | 'semiauto' | null, ) => { if (newMode !== null) { setMigrationMode(newMode); } }; // Helper function to check if any service is running const isAnyServiceRunning = (status: MonitoringStatus | null): boolean => { if (!status) return false; return ( status.statuses.migration.is_running || status.statuses.environmental.is_running || status.statuses.preventive.is_running ); }; const handleSelectChange = (event: SelectChangeEvent) => { const setter = (value: string) => { if (event.target.name === 'envTimeUnit') setEnvTimeUnit(value); else if (event.target.name === 'envSteps') setEnvSteps(value); else if (event.target.name === 'envModelType') setEnvModelType(value); else if (event.target.name === 'prevTimeUnit') setPrevTimeUnit(value); else if (event.target.name === 'prevSteps') setPrevSteps(value); else if (event.target.name === 'prevModelType') setPrevModelType(value); else if (event.target.name === 'migrationTime') setMigrationTime(value); else if (event.target.name === 'migrationModel') setMigrationModel(value as string); else if (event.target.name === 'migrationMethod') setMigrationMethod(value as string); else if (event.target.name === 'estimationMethod') setEstimationMethod(value as 'direct' | 'indirect'); }; setter(event.target.value as string); }; // Start polling when monitoring is active - we'll replace this with a constant polling approach useEffect(() => { // This effect is now only responsible for cleanup when component unmounts return () => { monitoringService.stopStatusPolling(); }; }, []); // Always check monitoring status, regardless of the isMonitoring state useEffect(() => { const fetchAndUpdateStatus = async () => { try { // Only show loading on initial fetch, not during regular status updates if (!monitoringStatus) { setIsStatusLoading(true); } // Directly get the status instead of checking if active const status = await monitoringService.getMonitoringStatus(); // Determine if any service is running using our helper function const anyServiceRunning = isAnyServiceRunning(status); // Update the status display setMonitoringStatus(status); // Update isMonitoring state only if it's different from current state if (anyServiceRunning !== isMonitoring) { console.log(`Monitoring state changed: ${isMonitoring} -> ${anyServiceRunning}`); setIsMonitoring(anyServiceRunning); } } catch (error) { console.error('Error fetching monitoring status:', error); } finally { setIsStatusLoading(false); } }; // Execute immediately when component mounts fetchAndUpdateStatus(); // Set up polling every 3 seconds (decreased from 5 to be more responsive) // This will help detect when monitoring is started from the backend const pollingInterval = setInterval(fetchAndUpdateStatus, 3000); // Clean up on unmount return () => { clearInterval(pollingInterval); }; }, []); // Empty dependency array means this only runs on mount // Add function to force immediate status update const forceStatusUpdate = async () => { try { setIsStatusLoading(true); const status = await monitoringService.getMonitoringStatus(); setMonitoringStatus(status); const anyServiceRunning = isAnyServiceRunning(status); if (anyServiceRunning !== isMonitoring) { setIsMonitoring(anyServiceRunning); } return status; } catch (error) { console.error('Error in force status update:', error); return null; } finally { setIsStatusLoading(false); } }; const renderStatusChip = (isRunning: boolean | undefined) => { // If status is undefined, return a default "Unknown" chip if (isRunning === undefined) { return ( ); } return ( ); }; // Handler for when optimization selection is saved const handleOptimizationSaved = (unselectedVMs: string[], selectedVMs: string[]) => { const newState = { selectedVMs, unselectedVMs }; setBlockList(unselectedVMs); setSelectedVMs(selectedVMs); setOptimizationState(newState); setHasSelectedOptimization(true); // Save to localStorage localStorage.setItem('optimizationState', JSON.stringify(newState)); }; // Handler to edit optimization selection const handleEditOptimization = () => { setShowOptimizationDialog(true); }; return ( {!hasSelectedOptimization ? ( // Show only the MonitoringSystem component initially ) : ( // Show the full Home page content after optimization selection <> {/* Add Edit Optimization button with block list count */} {/* Environmental Temperature Section */} Environmental Temperature Time Configuration {"{script time unit (mins)}"} {timeOptions.map((option) => ( {option} ))} Steps Configuration Estimation Steps {stepOptions.map((option) => ( {option} ))} Model Selection Model Type {modelTypes.map((option) => ( {option} ))} {/* Preventive Maintenance Section */} Preventive Maintenance Time Configuration {"{script time unit (mins)}"} {timeOptions.map((option) => ( {option} ))} Steps Configuration Estimation Steps {stepOptions.map((option) => ( {option} ))} Model Selection Model Type {modelTypes.map((option) => ( {option} ))} {/* Migration Advice Section */} Migration Advice {/* Vm Energy Estimation */} Auto Semi-Auto Time Configuration {"{script time unit (mins)}"} {timeOptions.slice(0).map((option) => ( {option} ))} Migration Advice Method Method Type {migrationMethodTypes.map((option) => ( {option} ))} Vm Energy Estimation Estimation Method Direct Indirect Model {migrationModelTypes[estimationMethod].map((option) => ( {option} ))} {/* Weight Configuration Section */} Weight Of Sustainability Criteria {/* Fixed height container for error message */} {/* Fixed height container */} {weightError && ( Weights must sum to 100% )} {/* Weight sliders container with fixed height */} {/* Adjust the 90px based on your header height */} {Object.entries(weights).map(([key, value]) => ( {key === 'energy' && } {key === 'balance' && } {key === 'overload' && } {key === 'allocation' && } {key.charAt(0).toUpperCase() + key.slice(1)} ))} {/* Start and Stop Monitoring Buttons with Status */} {isAnyServiceRunning(monitoringStatus) ? ( <> Monitoring is Active ) : ( <> Monitoring is Inactive )} {/* Start Monitoring Button */} {/* Stop Monitoring Button */} {/* Status Display - Always Visible */} Monitoring Status {!monitoringStatus ? ( <> ) : ( <> {/* Overall status summary */} {/* Individual service statuses */} Migration: {renderStatusChip(monitoringStatus.statuses?.migration?.is_running)} Env: {renderStatusChip(monitoringStatus.statuses?.environmental?.is_running)} Prev: {renderStatusChip(monitoringStatus.statuses?.preventive?.is_running)} {/* Subtle indicator at the bottom of the card */} )} {/* Dialog for editing optimization */} setShowOptimizationDialog(false)} maxWidth="xl" fullWidth > Edit Optimization Selection { const newState = { selectedVMs, unselectedVMs }; setBlockList(unselectedVMs); setSelectedVMs(selectedVMs); setOptimizationState(newState); setShowOptimizationDialog(false); // Save to localStorage localStorage.setItem('optimizationState', JSON.stringify(newState)); }} isDialog={true} initialBlockList={optimizationState.unselectedVMs} initialSelectedVMs={optimizationState.selectedVMs} /> {alert.message} {/* COMMENT THIS LINE OUT TO REMOVE DEBUG CONSOLE */} )} ); }; export default Home;