chore: full stack stability and migration fixes, plus react UI progress
This commit is contained in:
183
control-plane-ui/src/components/Dashboard/PillarCard.tsx
Normal file
183
control-plane-ui/src/components/Dashboard/PillarCard.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
Box,
|
||||
LinearProgress,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Chip,
|
||||
useTheme,
|
||||
alpha,
|
||||
Grid,
|
||||
Divider,
|
||||
} from '@mui/material'
|
||||
import {
|
||||
TrendingUp as ScaleUpIcon,
|
||||
TrendingDown as ScaleDownIcon,
|
||||
Settings as SettingsIcon,
|
||||
CheckCircle as OnlineIcon,
|
||||
Sync as ScalingIcon,
|
||||
Error as ErrorIcon,
|
||||
} from '@mui/icons-material'
|
||||
import { ResponsiveContainer, AreaChart, Area } from 'recharts'
|
||||
import { PillarStats } from '../../hooks/usePillars'
|
||||
|
||||
interface PillarCardProps {
|
||||
stats: PillarStats
|
||||
onScale?: (pillar: string, action: 'up' | 'down') => void
|
||||
}
|
||||
|
||||
const mockChartData = [
|
||||
{ val: 40 }, { val: 30 }, { val: 45 }, { val: 60 }, { val: 55 }, { val: 70 }, { val: 85 }
|
||||
]
|
||||
|
||||
export default function PillarCard({ stats, onScale }: PillarCardProps) {
|
||||
const theme = useTheme()
|
||||
const isScaling = stats.is_scaling
|
||||
const hasSuggestion = stats.suggestion && stats.suggestion.action !== 'none'
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.9)} 0%, ${alpha(theme.palette.background.paper, 0.7)} 100%)`,
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: `1px solid ${alpha(theme.palette.divider, 0.1)}`,
|
||||
transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: `0 8px 24px ${alpha(theme.palette.common.black, 0.3)}`,
|
||||
border: `1px solid ${alpha(theme.palette.primary.main, 0.3)}`,
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Scaling Animation Overlay */}
|
||||
{isScaling && (
|
||||
<LinearProgress
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 4,
|
||||
'& .MuiLinearProgress-bar': {
|
||||
backgroundImage: `linear-gradient(90deg, ${theme.palette.primary.main}, ${theme.palette.secondary.main})`
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CardContent sx={{ p: 2.5 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
|
||||
<Box>
|
||||
<Typography variant="overline" color="text.secondary" sx={{ fontWeight: 'bold', letterSpacing: 1 }}>
|
||||
{stats.pillar}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mt: 0.5 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 800, mr: 1 }}>
|
||||
{stats.active_count}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
/ {stats.node_count} nodes
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Chip
|
||||
icon={isScaling ? <ScalingIcon sx={{ animation: 'spin 2s linear infinite' }} /> : <OnlineIcon />}
|
||||
label={isScaling ? 'Scaling' : 'Online'}
|
||||
size="small"
|
||||
color={isScaling ? 'primary' : 'success'}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
'@keyframes spin': {
|
||||
'0%': { transform: 'rotate(0deg)' },
|
||||
'100%': { transform: 'rotate(360deg)' }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Mini Sparkline */}
|
||||
<Box sx={{ height: 60, width: '100%', mb: 2, opacity: 0.8 }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={mockChartData}>
|
||||
<defs>
|
||||
<linearGradient id={`grad-${stats.pillar}`} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={theme.palette.primary.main} stopOpacity={0.3}/>
|
||||
<stop offset="95%" stopColor={theme.palette.primary.main} stopOpacity={0}/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="val"
|
||||
stroke={theme.palette.primary.main}
|
||||
strokeWidth={2}
|
||||
fillOpacity={1}
|
||||
fill={`url(#grad-${stats.pillar})`}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</Box>
|
||||
|
||||
{/* Metrics Gauges */}
|
||||
<Grid container spacing={2} sx={{ mb: 2 }}>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="caption" color="text.secondary" display="block">CPU Load</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={stats.metrics?.cpu_usage || 12}
|
||||
sx={{ flexGrow: 1, height: 6, borderRadius: 3 }}
|
||||
color={ (stats.metrics?.cpu_usage || 12) > 80 ? 'error' : 'primary'}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ minWidth: 25 }}>{stats.metrics?.cpu_usage || 12}%</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="caption" color="text.secondary" display="block">Memory</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={stats.metrics?.memory_usage || 45}
|
||||
sx={{ flexGrow: 1, height: 6, borderRadius: 3 }}
|
||||
color="secondary"
|
||||
/>
|
||||
<Typography variant="caption" sx={{ minWidth: 25 }}>{stats.metrics?.memory_usage || 45}%</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Suggestion & Actions */}
|
||||
<Divider sx={{ my: 1.5, opacity: 0.5 }} />
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
{hasSuggestion ? (
|
||||
<Tooltip title={stats.suggestion?.reason}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, color: theme.palette.warning.main }}>
|
||||
<ErrorIcon fontSize="inherit" />
|
||||
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
|
||||
Sug: {stats.suggestion?.action === 'scale_up' ? '+' : '-'}{Math.abs(stats.suggestion!.target_count - stats.node_count)} nodes
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Typography variant="caption" color="text.secondary">Optimal Performance</Typography>
|
||||
)}
|
||||
|
||||
<Box>
|
||||
<IconButton size="small" onClick={() => onScale?.(stats.pillar, 'down')} disabled={isScaling || stats.node_count <= 1}>
|
||||
<ScaleDownIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton size="small" color="primary" onClick={() => onScale?.(stats.pillar, 'up')} disabled={isScaling}>
|
||||
<ScaleUpIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user