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 getStatusChip = (s: string | { status: string, cpu?: number, memory?: number }) => {
|
||||||
const state = typeof s === 'string' ? s : s.status
|
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') {
|
if (state === 'starting') {
|
||||||
return <Chip label={t('common.starting')} color="warning" size="small" sx={{ fontWeight: 'bold' }} icon={<CircularProgress size={14} color="inherit" />} />
|
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'
|
const color = state === 'running' ? 'success' : state === 'stopped' ? 'error' : 'warning'
|
||||||
|
|
||||||
return (
|
return <Chip label={t(`common.${state}`).toUpperCase()} color={color} size="small" sx={{ fontWeight: 'bold' }} />
|
||||||
<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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 () => {
|
const handleAddProject = async () => {
|
||||||
if (!window.api) return
|
if (!window.api) return
|
||||||
if (newProject.id) {
|
if (newProject.id) {
|
||||||
@@ -1155,8 +1189,9 @@ function App(): JSX.Element {
|
|||||||
{getStatusChip(status.nginx)}
|
{getStatusChip(status.nginx)}
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Nginx</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Nginx</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.nginxPort}</Typography>
|
<Typography variant="body2" color="text.secondary">Port: {settings.nginxPort}</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<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 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
<IconButton onClick={() => handleOpenLogs('nginx')} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
<IconButton onClick={() => handleOpenLogs('nginx')} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||||
<TerminalIcon fontSize="small" />
|
<TerminalIcon fontSize="small" />
|
||||||
@@ -1217,8 +1252,9 @@ function App(): JSX.Element {
|
|||||||
{getStatusChip(status.apache)}
|
{getStatusChip(status.apache)}
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Apache</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Apache</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.apachePort}</Typography>
|
<Typography variant="body2" color="text.secondary">Port: {settings.apachePort}</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<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 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
<IconButton onClick={() => handleOpenLogs('apache')} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
<IconButton onClick={() => handleOpenLogs('apache')} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||||
<TerminalIcon fontSize="small" />
|
<TerminalIcon fontSize="small" />
|
||||||
@@ -1279,8 +1315,9 @@ function App(): JSX.Element {
|
|||||||
{getStatusChip(status[`php:${v.version}`] || { status: 'stopped' })}
|
{getStatusChip(status[`php:${v.version}`] || { status: 'stopped' })}
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>PHP {v.version}</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>PHP {v.version}</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {v.port || '?'}</Typography>
|
<Typography variant="body2" color="text.secondary">Port: {v.port || '?'}</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<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)' }}>
|
<IconButton onClick={() => handleOpenLogs(`php:${v.version}`)} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||||
<TerminalIcon fontSize="small" />
|
<TerminalIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -1310,8 +1347,9 @@ function App(): JSX.Element {
|
|||||||
{getStatusChip(status[`mariadb:${v.version}`] || { status: 'stopped' })}
|
{getStatusChip(status[`mariadb:${v.version}`] || { status: 'stopped' })}
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MariaDB {v.version}</Typography>
|
<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>
|
<Typography variant="body2" color="text.secondary">Port: {settings.mariaDbPorts?.[v.id] || v.port}</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<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 }}>
|
<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)' }}>
|
<IconButton onClick={() => handleOpenLogs(`mariadb:${v.version}`)} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||||
<TerminalIcon fontSize="small" />
|
<TerminalIcon fontSize="small" />
|
||||||
|
|||||||
Reference in New Issue
Block a user