feat: implement main application UI with dashboard, service management, and project configuration panels
This commit is contained in:
+66
-28
@@ -474,8 +474,6 @@ function App(): JSX.Element {
|
||||
|
||||
const getStatusChip = (s: string | { status: string, cpu?: number, memory?: number }) => {
|
||||
const state = typeof s === 'string' ? s : s.status
|
||||
const cpu = typeof s === 'object' ? s.cpu : null
|
||||
const memory = typeof s === 'object' ? s.memory : null
|
||||
|
||||
if (state === 'starting') {
|
||||
return <Chip label={t('common.starting')} color="warning" size="small" sx={{ fontWeight: 'bold' }} icon={<CircularProgress size={14} color="inherit" />} />
|
||||
@@ -485,26 +483,62 @@ function App(): JSX.Element {
|
||||
}
|
||||
const color = state === 'running' ? 'success' : state === 'stopped' ? 'error' : 'warning'
|
||||
|
||||
return (
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Chip label={t(`common.${state}`).toUpperCase()} color={color} size="small" sx={{ fontWeight: 'bold' }} />
|
||||
{state === 'running' && cpu !== null && (
|
||||
<Tooltip title="CPU / RAM Usage">
|
||||
<Stack direction="row" spacing={0.5} sx={{ opacity: 0.8 }}>
|
||||
<Typography variant="caption" sx={{ color: 'primary.light', fontWeight: 'bold' }}>
|
||||
{cpu}%
|
||||
</Typography>
|
||||
<Divider orientation="vertical" flexItem sx={{ height: 12, my: 'auto', bgcolor: 'rgba(255,255,255,0.2)' }} />
|
||||
<Typography variant="caption" sx={{ color: 'secondary.light', fontWeight: 'bold' }}>
|
||||
{formatMemory(memory as number)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
return <Chip label={t(`common.${state}`).toUpperCase()} color={color} size="small" sx={{ fontWeight: 'bold' }} />
|
||||
}
|
||||
|
||||
const ResourceMonitor = ({ cpu, memory }: { cpu?: number; memory?: number }) => {
|
||||
if (cpu === undefined && memory === undefined) return null;
|
||||
|
||||
const ramMB = memory ? Math.round(memory / (1024 * 1024)) : 0;
|
||||
const ramLimit = 1024; // 1GB scale for visual
|
||||
const cpuValue = cpu || 0;
|
||||
const ramPercent = Math.min((ramMB / ramLimit) * 100, 100);
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 1.5, mb: 1 }}>
|
||||
<Box sx={{ mb: 1.5 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.5)', fontSize: '0.7rem', fontWeight: 'bold' }}>CPU</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'primary.light', fontSize: '0.7rem', fontWeight: 'bold' }}>{cpuValue}%</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={cpuValue}
|
||||
sx={{
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
bgcolor: 'rgba(255,255,255,0.05)',
|
||||
'.MuiLinearProgress-bar': {
|
||||
borderRadius: 2,
|
||||
backgroundImage: `linear-gradient(90deg, #33d9b2 ${cpuValue}%, #ff5252 100%)`,
|
||||
bgcolor: 'transparent'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.5)', fontSize: '0.7rem', fontWeight: 'bold' }}>RAM</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'secondary.light', fontSize: '0.7rem', fontWeight: 'bold' }}>{formatMemory(memory as number)}</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={ramPercent}
|
||||
sx={{
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
bgcolor: 'rgba(255,255,255,0.05)',
|
||||
'.MuiLinearProgress-bar': {
|
||||
borderRadius: 2,
|
||||
bgcolor: 'secondary.main'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const handleAddProject = async () => {
|
||||
if (!window.api) return
|
||||
if (newProject.id) {
|
||||
@@ -1155,8 +1189,9 @@ function App(): JSX.Element {
|
||||
{getStatusChip(status.nginx)}
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Nginx</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.nginxPort}</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2" color="text.secondary">Port: {settings.nginxPort}</Typography>
|
||||
<ResourceMonitor cpu={status.nginx?.cpu} memory={status.nginx?.memory} />
|
||||
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<IconButton onClick={() => handleOpenLogs('nginx')} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||
<TerminalIcon fontSize="small" />
|
||||
@@ -1217,8 +1252,9 @@ function App(): JSX.Element {
|
||||
{getStatusChip(status.apache)}
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Apache</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.apachePort}</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2" color="text.secondary">Port: {settings.apachePort}</Typography>
|
||||
<ResourceMonitor cpu={status.apache?.cpu} memory={status.apache?.memory} />
|
||||
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<IconButton onClick={() => handleOpenLogs('apache')} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||
<TerminalIcon fontSize="small" />
|
||||
@@ -1279,8 +1315,9 @@ function App(): JSX.Element {
|
||||
{getStatusChip(status[`php:${v.version}`] || { status: 'stopped' })}
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>PHP {v.version}</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {v.port || '?'}</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2" color="text.secondary">Port: {v.port || '?'}</Typography>
|
||||
<ResourceMonitor cpu={status[`php:${v.version}`]?.cpu} memory={status[`php:${v.version}`]?.memory} />
|
||||
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<IconButton onClick={() => handleOpenLogs(`php:${v.version}`)} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||
<TerminalIcon fontSize="small" />
|
||||
</IconButton>
|
||||
@@ -1310,8 +1347,9 @@ function App(): JSX.Element {
|
||||
{getStatusChip(status[`mariadb:${v.version}`] || { status: 'stopped' })}
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MariaDB {v.version}</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.mariaDbPorts?.[v.id] || v.port}</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2" color="text.secondary">Port: {settings.mariaDbPorts?.[v.id] || v.port}</Typography>
|
||||
<ResourceMonitor cpu={status[`mariadb:${v.version}`]?.cpu} memory={status[`mariadb:${v.version}`]?.memory} />
|
||||
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<IconButton onClick={() => handleOpenLogs(`mariadb:${v.version}`)} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||
<TerminalIcon fontSize="small" />
|
||||
|
||||
Reference in New Issue
Block a user