feat: implement dashboard UI components, IPC handlers, and service configuration templates
This commit is contained in:
@@ -258,6 +258,7 @@ async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ bi
|
||||
if (!normalizedPath.endsWith('/')) normalizedPath += '/'
|
||||
|
||||
const phpDir = path.join(configService.getBasePath(), 'bin', `php-${p.phpVersion}`)
|
||||
const phpIniDir = path.join(configService.getBasePath(), 'generated_configs', 'php', p.phpVersion)
|
||||
const phpCgiBinary = findExecutable(phpDir, process.platform === 'win32' ? 'php-cgi.exe' : 'php-cgi') || ''
|
||||
|
||||
// Auto-suffix hostname if it's a simple slug
|
||||
@@ -268,6 +269,7 @@ async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ bi
|
||||
PATH: normalizedPath,
|
||||
PHP_PORT: phpPort.toString(),
|
||||
PHP_DIR: phpDir.replace(/\\/g, '/'),
|
||||
PHP_INI_DIR: phpIniDir.replace(/\\/g, '/'),
|
||||
PHP_CGI_PATH: phpCgiBinary.replace(/\\/g, '/'),
|
||||
PHP_BIN_URL: `/php-bin-${p.host}`,
|
||||
PORT: settings.apachePort || '8080'
|
||||
@@ -279,6 +281,7 @@ async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ bi
|
||||
PATH: normalizedPath,
|
||||
PHP_PORT: phpPort.toString(),
|
||||
PHP_DIR: phpDir.replace(/\\/g, '/'),
|
||||
PHP_INI_DIR: phpIniDir.replace(/\\/g, '/'),
|
||||
PHP_CGI_PATH: phpCgiBinary.replace(/\\/g, '/'),
|
||||
PHP_BIN_URL: `/php-bin-alias-${p.host}`,
|
||||
PORT: settings.apachePort || '8080'
|
||||
|
||||
@@ -202,6 +202,9 @@ export default function DashboardDetails({
|
||||
return 'n/a';
|
||||
};
|
||||
const isRunning = currentStatus.status === 'running';
|
||||
const isStarting = currentStatus.status === 'starting';
|
||||
const isStopping = currentStatus.status === 'stopping';
|
||||
const isTransitioning = isStarting || isStopping;
|
||||
|
||||
const serviceProjects = projects.filter(p => {
|
||||
if (serviceId === 'nginx') return !p.serverType || p.serverType === 'nginx';
|
||||
@@ -244,11 +247,14 @@ export default function DashboardDetails({
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 800 }}>{serviceId.toUpperCase()}</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.4)' }}>
|
||||
{isRunning ? t('common.running', 'Çalışıyor') : t('common.stopped', 'Durduruldu')}
|
||||
{isStarting && <Box component="span" sx={{ color: 'warning.light' }}>{t('common.starting', 'Başlatılıyor...')}</Box>}
|
||||
{isStopping && <Box component="span" sx={{ color: 'warning.light' }}>{t('common.stopping', 'Durduruluyor...')}</Box>}
|
||||
{isRunning && <Box component="span" sx={{ color: 'success.light' }}>{t('common.running', 'Çalışıyor')}</Box>}
|
||||
{!isRunning && !isStarting && !isStopping && <Box component="span" sx={{ opacity: 0.5 }}>{t('common.stopped', 'Durduruldu')}</Box>}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack direction="row" spacing={1}>
|
||||
{isRunning && (
|
||||
{isRunning && !isTransitioning && (
|
||||
<IconButton onClick={() => onReloadService(serviceId)} sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main' }}>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
@@ -256,11 +262,16 @@ export default function DashboardDetails({
|
||||
<Button
|
||||
variant="contained"
|
||||
color={isRunning ? 'error' : 'success'}
|
||||
startIcon={isRunning ? <StopIcon /> : <StartIcon />}
|
||||
disabled={isTransitioning}
|
||||
startIcon={
|
||||
isTransitioning
|
||||
? <CircularProgress size={20} color="inherit" />
|
||||
: isRunning ? <StopIcon /> : <StartIcon />
|
||||
}
|
||||
onClick={() => onToggleService(serviceId)}
|
||||
sx={{ borderRadius: 2 }}
|
||||
sx={{ borderRadius: 2, minWidth: 100 }}
|
||||
>
|
||||
{isRunning ? t('common.stop', 'Durdur') : t('common.start', 'Başlat')}
|
||||
{isStarting ? t('common.starting', '...') : (isStopping ? t('common.stopping', '...') : (isRunning ? t('common.stop', 'Durdur') : t('common.start', 'Başlat')))}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -26,29 +26,51 @@ export default function DashboardManager({
|
||||
onSelectProject
|
||||
}: DashboardManagerProps) {
|
||||
const [selectedServiceId, setSelectedServiceId] = useState<string | null>('nginx');
|
||||
const [isToggling, setIsToggling] = useState<string | null>(null);
|
||||
const [toggledStates, setToggledStates] = useState<Record<string, string>>({});
|
||||
|
||||
const handleToggleService = async (service: string) => {
|
||||
if (!window.api) return;
|
||||
if (isToggling) return;
|
||||
|
||||
const currentStatus = status[service]?.status || 'stopped';
|
||||
const isRunning = currentStatus === 'running';
|
||||
|
||||
setIsToggling(service);
|
||||
// Optimistically set transition state if not already there
|
||||
setToggledStates(prev => ({ ...prev, [service]: isRunning ? 'stopping' : 'starting' }));
|
||||
|
||||
const result = isRunning
|
||||
? await window.api.stopService(service)
|
||||
: await window.api.startService(service);
|
||||
try {
|
||||
const result = isRunning
|
||||
? await window.api.stopService(service)
|
||||
: await window.api.startService(service);
|
||||
|
||||
if (result && !result.success) {
|
||||
Swal.fire({
|
||||
title: t('common.error'),
|
||||
text: result.message,
|
||||
icon: 'error',
|
||||
background: '#1e1e1e',
|
||||
color: '#fff'
|
||||
if (result && !result.success) {
|
||||
Swal.fire({
|
||||
title: t('common.error'),
|
||||
text: result.message,
|
||||
icon: 'error',
|
||||
background: '#1e1e1e',
|
||||
color: '#fff'
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsToggling(null);
|
||||
setToggledStates(prev => {
|
||||
const updated = { ...prev };
|
||||
delete updated[service];
|
||||
return updated;
|
||||
});
|
||||
onFetchStatus();
|
||||
}
|
||||
onFetchStatus();
|
||||
};
|
||||
|
||||
// Merge actual status with our local transition states
|
||||
const mergedStatus = { ...status };
|
||||
Object.keys(toggledStates).forEach(service => {
|
||||
mergedStatus[service] = { ...mergedStatus[service], status: toggledStates[service] };
|
||||
});
|
||||
|
||||
const handleReloadService = async (service: string) => {
|
||||
if (!window.api) return;
|
||||
const result = await window.api.invoke('services:reload', service);
|
||||
@@ -67,7 +89,7 @@ export default function DashboardManager({
|
||||
return (
|
||||
<Box sx={{ display: 'flex', height: 'calc(100vh - 120px)', bgcolor: 'background.default', overflow: 'hidden', borderRadius: 4, border: '1px solid rgba(255, 255, 255, 0.05)', boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}>
|
||||
<DashboardSidebar
|
||||
status={status}
|
||||
status={mergedStatus}
|
||||
phpVersions={phpVersions}
|
||||
mariaDbVersions={mariaDbVersions}
|
||||
selectedServiceId={selectedServiceId}
|
||||
@@ -76,7 +98,7 @@ export default function DashboardManager({
|
||||
/>
|
||||
<DashboardDetails
|
||||
serviceId={selectedServiceId}
|
||||
status={status}
|
||||
status={mergedStatus}
|
||||
settings={settings}
|
||||
projects={projects}
|
||||
t={t}
|
||||
|
||||
@@ -72,10 +72,17 @@ export default function DashboardSidebar({
|
||||
height: 10,
|
||||
borderRadius: '50%',
|
||||
bgcolor: isStarting ? 'warning.main' : (isRunning ? 'success.main' : 'error.main'),
|
||||
boxShadow: isRunning ? '0 0 8px rgba(76, 175, 80, 0.5)' : 'none',
|
||||
boxShadow: isRunning ? '0 0 8px rgba(76, 175, 80, 0.5)' : (isStarting ? '0 0 8px rgba(255, 177, 66, 0.5)' : 'none'),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
'@keyframes pulse': {
|
||||
'0%': { transform: 'scale(1)', opacity: 1 },
|
||||
'50%': { transform: 'scale(1.2)', opacity: 0.7 },
|
||||
'100%': { transform: 'scale(1)', opacity: 1 }
|
||||
},
|
||||
animation: isStarting ? 'pulse 1.5s infinite ease-in-out' : 'none'
|
||||
}}>
|
||||
{isStarting && <CircularProgress size={8} thickness={6} color="inherit" />}
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user