feat: implement dashboard UI components, IPC handlers, and service configuration templates

This commit is contained in:
Ümit Tunç
2026-04-03 07:06:39 +03:00
parent e7be5c1c19
commit ee4e3660f0
8 changed files with 436 additions and 21 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ Alias /{{HOST}} "{{PATH}}"
# --- KRİTİK EKLEME BURASI ---
# PHP'ye php.ini dosyasını {{PHP_DIR}} klasöründe aramasını söyler
SetEnv PHPRC "{{PHP_DIR}}"
SetEnv PHPRC "{{PHP_INI_DIR}}"
# ----------------------------
# Passing Authorization header to PHP
@@ -6,6 +6,9 @@ Alias /{{HOST}} "{{PATH}}"
AllowOverride All
Require all granted
# PHP'ye php.ini dosyasını generated_configs klasöründe aramasını söyler
SetEnv PHPRC "{{PHP_INI_DIR}}"
# Passing Authorization header to PHP
CGIPassAuth On
+56
View File
@@ -0,0 +1,56 @@
[PHP]
engine = On
short_open_tag = Off
precision = 14
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
unserialize_callback_func =
serialize_precision = -1
disable_functions =
disable_classes =
zend.enable_gc = On
expose_php = On
max_execution_time = 300
max_input_time = 120
memory_limit = 512M
error_reporting = {{ERROR_REPORTING}}asdasd
display_errors = {{DISPLAY_ERRORS}}
display_startup_errors = On
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
html_errors = On
variables_order = "GPCS"
request_order = "GP"
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 100M
auto_prepend_file =
auto_append_file =
default_mimetype = "text/html"
default_charset = "UTF-8"
doc_root =
user_dir =
enable_dl = Off
file_uploads = On
upload_max_filesize = 100M
max_file_uploads = 20
allow_url_fopen = On
allow_url_include = Off
default_socket_timeout = 60
session.save_path = "{{SESSION_DIR}}"
upload_tmp_dir = "{{TEMP_DIR}}"
sys_temp_dir = "{{TEMP_DIR}}"
extension_dir = "{{EXT_DIR}}"
fastcgi.impersonate = 1
cgi.force_redirect = 0
cgi.fix_pathinfo = 0
[ExtensionList]
{{EXTENSIONS}}
[Date]
date.timezone = UTC
+313
View File
@@ -1,4 +1,317 @@
[
{
"service": "php:5.6.40",
"action": "start",
"status": "success",
"id": "200rgoskm",
"timestamp": 1775189179480
},
{
"service": "php:7.4.33",
"action": "start",
"status": "success",
"id": "lb7cc11oe",
"timestamp": 1775189179463
},
{
"service": "php:8.2.30",
"action": "start",
"status": "success",
"id": "etjx8kz61",
"timestamp": 1775189179447
},
{
"service": "php:8.5.4",
"action": "start",
"status": "success",
"id": "jw31owy76",
"timestamp": 1775189179430
},
{
"service": "mariadb:10.11.8",
"action": "start",
"status": "success",
"id": "905twpheo",
"timestamp": 1775189179167
},
{
"service": "mariadb:11.4.2",
"action": "start",
"status": "success",
"id": "3cpe3886a",
"timestamp": 1775189179146
},
{
"service": "php:7.4.33",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "1puz4ewk4",
"timestamp": 1775189136125
},
{
"service": "php:7.4.33",
"action": "stop",
"status": "info",
"id": "cp1skav2y",
"timestamp": 1775189136113
},
{
"service": "php:7.4.33",
"action": "start",
"status": "success",
"id": "5ssshkepi",
"timestamp": 1775189128458
},
{
"service": "nginx",
"action": "start",
"status": "success",
"id": "2w0dgrom5",
"timestamp": 1775189121191
},
{
"service": "apache",
"action": "start",
"status": "success",
"id": "ce020djd5",
"timestamp": 1775189116024
},
{
"service": "mariadb:11.4.2",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "f22mfjz6j",
"timestamp": 1775189109611
},
{
"service": "mariadb:10.11.8",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "bj1yhdd0b",
"timestamp": 1775189109603
},
{
"service": "php:5.6.40",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "dwo60nc99",
"timestamp": 1775189109591
},
{
"service": "php:7.4.33",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "vc72hwpnw",
"timestamp": 1775189109584
},
{
"service": "php:8.2.30",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "dmk8j6iad",
"timestamp": 1775189109577
},
{
"service": "php:8.5.4",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "ck9ctfdg9",
"timestamp": 1775189109571
},
{
"service": "apache",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "f4qs2eaz5",
"timestamp": 1775189109550
},
{
"service": "nginx",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "m1fkgarlg",
"timestamp": 1775189109547
},
{
"service": "mariadb:11.4.2",
"action": "stop",
"status": "info",
"id": "1cjzxt5i8",
"timestamp": 1775189109537
},
{
"service": "mariadb:10.11.8",
"action": "stop",
"status": "info",
"id": "ss8vlatqg",
"timestamp": 1775189109529
},
{
"service": "php:5.6.40",
"action": "stop",
"status": "info",
"id": "38on99hkk",
"timestamp": 1775189109520
},
{
"service": "php:7.4.33",
"action": "stop",
"status": "info",
"id": "qinl5mvi9",
"timestamp": 1775189109513
},
{
"service": "php:8.2.30",
"action": "stop",
"status": "info",
"id": "tc2wax0xc",
"timestamp": 1775189109502
},
{
"service": "php:8.5.4",
"action": "stop",
"status": "info",
"id": "vlawi3k8e",
"timestamp": 1775189109494
},
{
"service": "apache",
"action": "stop",
"status": "info",
"id": "6ujdvt7j1",
"timestamp": 1775189109479
},
{
"service": "nginx",
"action": "stop",
"status": "info",
"id": "k7rezgoaj",
"timestamp": 1775189109477
},
{
"service": "apache",
"action": "start",
"status": "success",
"id": "70tu2y5as",
"timestamp": 1775189102787
},
{
"service": "apache",
"action": "exit",
"status": "failure",
"details": "Exit code: 1",
"id": "9lfnqbgwe",
"timestamp": 1775189101318
},
{
"service": "php:5.6.40",
"action": "start",
"status": "success",
"id": "wgqvhqwe2",
"timestamp": 1775189054380
},
{
"service": "php:7.4.33",
"action": "start",
"status": "success",
"id": "cz9laoefl",
"timestamp": 1775189054362
},
{
"service": "php:8.2.30",
"action": "start",
"status": "success",
"id": "81z3uz9zw",
"timestamp": 1775189054344
},
{
"service": "php:8.5.4",
"action": "start",
"status": "success",
"id": "0hegp84kv",
"timestamp": 1775189054324
},
{
"service": "mariadb:11.4.2",
"action": "start",
"status": "success",
"id": "r2fm405gd",
"timestamp": 1775189054117
},
{
"service": "mariadb:10.11.8",
"action": "start",
"status": "success",
"id": "knua7kh8u",
"timestamp": 1775189054097
},
{
"service": "apache",
"action": "start",
"status": "success",
"id": "l4paq57hz",
"timestamp": 1775189053345
},
{
"service": "nginx",
"action": "start",
"status": "success",
"id": "khg59iy1d",
"timestamp": 1775189053276
},
{
"service": "php:7.4.33",
"action": "start",
"status": "success",
"id": "hg9972rds",
"timestamp": 1775188622685
},
{
"service": "php:7.4.33",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "1knv5xq4h",
"timestamp": 1775188618390
},
{
"service": "php:7.4.33",
"action": "stop",
"status": "info",
"id": "7132k0rd3",
"timestamp": 1775188618378
},
{
"service": "php:7.4.33",
"action": "start",
"status": "success",
"id": "j8dmzg0qj",
"timestamp": 1775188599983
},
{
"service": "php:7.4.33",
"action": "exit",
"status": "failure",
"details": "Exit code: null",
"id": "o26b3cigi",
"timestamp": 1775188594701
},
{
"service": "php:7.4.33",
"action": "stop",
"status": "info",
"id": "ox74ttj4y",
"timestamp": 1775188594690
},
{
"service": "php:5.6.40",
"action": "start",
+3
View File
@@ -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>