diff --git a/config/apache_project.conf.template b/config/apache_project.conf.template
index 7c781d4..85fb296 100644
--- a/config/apache_project.conf.template
+++ b/config/apache_project.conf.template
@@ -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
diff --git a/config/apache_project_alias.conf.template b/config/apache_project_alias.conf.template
index 527efed..86182ac 100644
--- a/config/apache_project_alias.conf.template
+++ b/config/apache_project_alias.conf.template
@@ -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
diff --git a/config/php.ini.template.bak b/config/php.ini.template.bak
new file mode 100644
index 0000000..bc950d1
--- /dev/null
+++ b/config/php.ini.template.bak
@@ -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
diff --git a/service_events.json b/service_events.json
index 2752594..8f0988e 100644
--- a/service_events.json
+++ b/service_events.json
@@ -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",
diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts
index 009955d..6572bd1 100644
--- a/src/main/ipc/index.ts
+++ b/src/main/ipc/index.ts
@@ -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'
diff --git a/src/renderer/src/components/Dashboard/DashboardDetails.tsx b/src/renderer/src/components/Dashboard/DashboardDetails.tsx
index 3bb84c6..bfea0d8 100644
--- a/src/renderer/src/components/Dashboard/DashboardDetails.tsx
+++ b/src/renderer/src/components/Dashboard/DashboardDetails.tsx
@@ -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({
{serviceId.toUpperCase()}
- {isRunning ? t('common.running', 'Çalışıyor') : t('common.stopped', 'Durduruldu')}
+ {isStarting && {t('common.starting', 'Başlatılıyor...')}}
+ {isStopping && {t('common.stopping', 'Durduruluyor...')}}
+ {isRunning && {t('common.running', 'Çalışıyor')}}
+ {!isRunning && !isStarting && !isStopping && {t('common.stopped', 'Durduruldu')}}
- {isRunning && (
+ {isRunning && !isTransitioning && (
onReloadService(serviceId)} sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main' }}>
@@ -256,11 +262,16 @@ export default function DashboardDetails({
: }
+ disabled={isTransitioning}
+ startIcon={
+ isTransitioning
+ ?
+ : isRunning ? :
+ }
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')))}
diff --git a/src/renderer/src/components/Dashboard/DashboardManager.tsx b/src/renderer/src/components/Dashboard/DashboardManager.tsx
index a72d6fe..4349687 100644
--- a/src/renderer/src/components/Dashboard/DashboardManager.tsx
+++ b/src/renderer/src/components/Dashboard/DashboardManager.tsx
@@ -26,29 +26,51 @@ export default function DashboardManager({
onSelectProject
}: DashboardManagerProps) {
const [selectedServiceId, setSelectedServiceId] = useState('nginx');
+ const [isToggling, setIsToggling] = useState(null);
+ const [toggledStates, setToggledStates] = useState>({});
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 (
{isStarting && }