feat: implement main application UI with dashboard, service management, and project configuration panels

This commit is contained in:
Ümit Tunç
2026-04-02 00:25:20 +03:00
parent 90bb7bbdbd
commit 2e0ba0d35f
+66 -28
View File
@@ -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" />