From 4184d137e6e8d58bfff1631f099e3ce9b55d97a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Thu, 2 Apr 2026 20:31:22 +0300 Subject: [PATCH] feat: implement project management system with Nginx configuration templates and automated host file updates --- config/apache_project.conf.template | 65 +- config/apache_project_alias.conf.template | 32 + config/httpd.conf.template | 5 +- config/nginx.conf.template | 8 +- config/nginx_project.conf.template | 35 +- config/nginx_project_location.conf.template | 17 + service_events.json | 1653 +++++++++++++++++ settings.json | 2 +- src/main/ipc/index.ts | 128 +- src/main/services/HostsService.ts | 73 + src/main/services/PhpManagerService.ts | 9 + src/main/services/ProjectService.ts | 12 +- src/renderer/src/App.tsx | 29 +- .../ProjectManager/ProjectDetails.tsx | 158 +- .../ProjectManager/ProjectManager.tsx | 10 +- .../ProjectManager/ProjectResourceMonitor.tsx | 117 +- .../ProjectManager/ProjectSidebar.tsx | 4 +- 17 files changed, 2181 insertions(+), 176 deletions(-) create mode 100644 config/apache_project_alias.conf.template create mode 100644 config/nginx_project_location.conf.template create mode 100644 src/main/services/HostsService.ts diff --git a/config/apache_project.conf.template b/config/apache_project.conf.template index 3cbb832..aaad8f8 100644 --- a/config/apache_project.conf.template +++ b/config/apache_project.conf.template @@ -1,38 +1,33 @@ -# Apache Project Configuration (CGI Mode) -# Accessed via: http://localhost:{{PORT}}/{{HOST}}/ + + ServerName {{HOST}} + DocumentRoot "{{PATH}}" -# 1. Alias definition (restores localhost/projectname access) -Alias /{{HOST}} "{{PATH}}" + + Options Indexes FollowSymLinks ExecCGI + AllowOverride All + Require all granted + + # Passing Authorization header to PHP + CGIPassAuth On + + # Directory index handling + DirectoryIndex index.php index.html + + # PHP Handling via mod_actions and CGI + + SetHandler application/x-httpd-php-{{HOST}} + + + # Action maps the handler to the binary via the ScriptAlias URL + Action application/x-httpd-php-{{HOST}} "{{PHP_BIN_URL}}/php-cgi.exe" + -# 2. ScriptAlias definition for the project's PHP CGI binary -# This maps a virtual URL path to the actual PHP folder. -ScriptAlias {{PHP_BIN_URL}}/ "{{PHP_DIR}}/" - -# 3. Directory settings for the project - - Options Indexes FollowSymLinks ExecCGI - AllowOverride All - Require all granted + # ScriptAlias definition for the project's PHP CGI binary + ScriptAlias {{PHP_BIN_URL}}/ "{{PHP_DIR}}/" - # Passing Authorization header to PHP - CGIPassAuth On - - # Directory index handling - DirectoryIndex index.php index.html - - # PHP Handling via mod_actions and CGI - - SetHandler application/x-httpd-php-{{HOST}} - - - # Action maps the handler to the binary via the ScriptAlias URL - # This is more robust on Windows than using absolute local paths. - Action application/x-httpd-php-{{HOST}} "{{PHP_BIN_URL}}/php-cgi.exe" - - -# 4. Permissions for the PHP binary directory - - AllowOverride None - Options None - Require all granted - \ No newline at end of file + + AllowOverride None + Options None + Require all granted + + \ No newline at end of file diff --git a/config/apache_project_alias.conf.template b/config/apache_project_alias.conf.template new file mode 100644 index 0000000..527efed --- /dev/null +++ b/config/apache_project_alias.conf.template @@ -0,0 +1,32 @@ +# Subdirectory Alias for {{HOST}} +Alias /{{HOST}} "{{PATH}}" + + + Options Indexes FollowSymLinks ExecCGI + AllowOverride All + Require all granted + + # Passing Authorization header to PHP + CGIPassAuth On + + # Directory index handling + DirectoryIndex index.php index.html + + # PHP Handling via mod_actions and CGI + + SetHandler application/x-httpd-php-alias-{{HOST}} + + + # Action maps the handler to the binary via the ScriptAlias URL + # We use a unique handler name to avoid conflict with VirtualHost config + Action application/x-httpd-php-alias-{{HOST}} "{{PHP_BIN_URL}}/php-cgi.exe" + + +# ScriptAlias definition for the project's PHP CGI binary +ScriptAlias {{PHP_BIN_URL}}/ "{{PHP_DIR}}/" + + + AllowOverride None + Options None + Require all granted + diff --git a/config/httpd.conf.template b/config/httpd.conf.template index f807e18..c7940f4 100644 --- a/config/httpd.conf.template +++ b/config/httpd.conf.template @@ -83,7 +83,10 @@ Alias /phpmyadmin "{{PHPMYADMIN_DIR}}" CGIPassAuth On -# Project configurations +# Project configurations (Subdirectory Access) +{{PROJECT_ALIASES}} + +# Project configurations (Virtual Host Access) {{PROJECTS}} # Global PHP handler (Manual Nginx-Style SCRIPT_FILENAME) diff --git a/config/nginx.conf.template b/config/nginx.conf.template index 386a13a..1e0becd 100644 --- a/config/nginx.conf.template +++ b/config/nginx.conf.template @@ -9,12 +9,14 @@ http { default_type application/octet-stream; sendfile on; keepalive_timeout 65; + server_names_hash_bucket_size 64; server { listen {{LISTEN_ADDRESS}}:{{PORT}}; server_name localhost; root "{{ROOT}}"; index index.php index.html index.htm; + autoindex on; location / { # Try global root first, fallback to @project_fallback if not found @@ -35,8 +37,10 @@ http { } return 404; } + + # Subdirectory access for projects (legacy support) + {{PROJECT_LOCATIONS}} - {{PROJECTS}} location /phpmyadmin { alias "{{PHPMYADMIN_DIR}}"; @@ -58,4 +62,6 @@ http { fastcgi_param SCRIPT_FILENAME $request_filename; } } + + {{PROJECTS}} } diff --git a/config/nginx_project.conf.template b/config/nginx_project.conf.template index 1311849..dc60f93 100644 --- a/config/nginx_project.conf.template +++ b/config/nginx_project.conf.template @@ -1,31 +1,24 @@ -location ^~ /{{HOST}}/ { - alias "{{PATH}}"; +server { + listen {{PORT}}; + server_name {{HOST}}; + root "{{PATH}}"; index index.php index.html; autoindex on; - - # Use a named location for reliable alias fallback - try_files $uri $uri/ @{{HOST}}; - # PHP handling nested INSIDE the alias location to maintain context + # Support for clean URLs + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # PHP handling location ~ \.php$ { fastcgi_pass 127.0.0.1:{{PHP_PORT}}; fastcgi_index index.php; - include "{{CONF_DIR}}/fastcgi_params"; - - # Normal files matched correctly by URI don't suffer the try_files bug - fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } -} -# The named location handles requests that don't match physical files -location @{{HOST}} { - # Directly execute PHP without URI mutation to preserve framework routing paths - fastcgi_pass 127.0.0.1:{{PHP_PORT}}; - include "{{CONF_DIR}}/fastcgi_params"; - - # Hardcode the essential execution parameters - fastcgi_param SCRIPT_FILENAME "{{PATH}}index.php"; - fastcgi_param SCRIPT_NAME "/{{HOST}}/index.php"; - fastcgi_param DOCUMENT_ROOT "{{PATH}}"; + # Custom log files for the project + # access_log "{{LOG_DIR}}/{{HOST}}_access.log"; + # error_log "{{LOG_DIR}}/{{HOST}}_error.log"; } diff --git a/config/nginx_project_location.conf.template b/config/nginx_project_location.conf.template new file mode 100644 index 0000000..894eee5 --- /dev/null +++ b/config/nginx_project_location.conf.template @@ -0,0 +1,17 @@ +location ^~ /{{HOST}}/ { + alias "{{PATH}}"; + index index.php index.html; + autoindex on; + + if (!-e $request_filename) { + rewrite ^/{{HOST}}/(.*)$ /{{HOST}}/index.php?$1 last; + } + + location ~ \.php$ { + # Using $request_filename to match the alias path correctly + fastcgi_pass 127.0.0.1:{{PHP_PORT}}; + fastcgi_index index.php; + include "{{CONF_DIR}}/fastcgi_params"; + fastcgi_param SCRIPT_FILENAME $request_filename; + } +} diff --git a/service_events.json b/service_events.json index 0fbd3c9..8c8d5fa 100644 --- a/service_events.json +++ b/service_events.json @@ -1,4 +1,1657 @@ [ + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "x9hvv7hsb", + "timestamp": 1775150982761 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "v4ipyg2i4", + "timestamp": 1775150981476 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "g92zn6zie", + "timestamp": 1775150981155 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "vy58xvhc1", + "timestamp": 1775150979799 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "mgee4y1sc", + "timestamp": 1775150648837 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "t8knokmhz", + "timestamp": 1775150647450 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "6z5sa3xgr", + "timestamp": 1775150624672 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "h2ksioetl", + "timestamp": 1775150624643 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "jgghsoxiz", + "timestamp": 1775150619025 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "6l0dmc7wm", + "timestamp": 1775150617630 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "751akrtrj", + "timestamp": 1775150617228 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "zj21cgh19", + "timestamp": 1775150605139 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "igr1oplt7", + "timestamp": 1775150603787 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "xpautv5vl", + "timestamp": 1775150557540 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "f8lxlllqb", + "timestamp": 1775150549382 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "1qj5mer0v", + "timestamp": 1775150548357 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "oabjf0kmp", + "timestamp": 1775150539513 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "cdqeyl8hb", + "timestamp": 1775150539498 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "1dze9cw1u", + "timestamp": 1775150539159 + }, + { + "service": "php:8.5.4", + "action": "stop", + "status": "info", + "id": "ad1fefv4o", + "timestamp": 1775150539145 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "ql4gzicux", + "timestamp": 1775150538791 + }, + { + "service": "nginx", + "action": "stop", + "status": "info", + "id": "dx8v5zdrv", + "timestamp": 1775150538786 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "h29e59m7n", + "timestamp": 1775150451034 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "hiot2wp8k", + "timestamp": 1775150441780 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "bulq7bi3a", + "timestamp": 1775150441743 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "rne4abiyv", + "timestamp": 1775150422032 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "k5yg7epmg", + "timestamp": 1775150421994 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "8gfojjrsj", + "timestamp": 1775150417309 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "ymqzjsiek", + "timestamp": 1775150417263 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "hqo3ku3t9", + "timestamp": 1775150404864 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "8ew1w9buv", + "timestamp": 1775150404846 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "bqwlyk3g6", + "timestamp": 1775150404828 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "o2uqetljl", + "timestamp": 1775150404811 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "33doizwg6", + "timestamp": 1775150404599 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "7cdxzt48p", + "timestamp": 1775150404578 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "pwh2trf2p", + "timestamp": 1775150403826 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "589qt6skk", + "timestamp": 1775150403819 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "m89coborm", + "timestamp": 1775150403765 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "scuy98pqd", + "timestamp": 1775144396353 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "ngriq552n", + "timestamp": 1775144395325 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "90ivxjxp7", + "timestamp": 1775144390098 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "ewt9obb9p", + "timestamp": 1775144390088 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "z2ytaa9z2", + "timestamp": 1775144389800 + }, + { + "service": "php:7.4.33", + "action": "stop", + "status": "info", + "id": "ks4vjl0a8", + "timestamp": 1775144389788 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "otecei6vx", + "timestamp": 1775144389492 + }, + { + "service": "apache", + "action": "stop", + "status": "info", + "id": "zqg6cv3gm", + "timestamp": 1775144389485 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "72u53glzq", + "timestamp": 1775144370843 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "cqbvd5ex6", + "timestamp": 1775144369526 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "mglm8ddoj", + "timestamp": 1775144369213 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "so2zrdeff", + "timestamp": 1775144367875 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "6sk4bsx1z", + "timestamp": 1775144338063 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "cawnpv1bt", + "timestamp": 1775144336760 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "cmiiuyys3", + "timestamp": 1775144336453 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "ikbkgh6jm", + "timestamp": 1775144335172 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "pqxdm20c9", + "timestamp": 1775144323509 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "ysuye4pqj", + "timestamp": 1775144323480 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "8hrfyvdkj", + "timestamp": 1775144323458 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "japix37kq", + "timestamp": 1775144323441 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "lbawdrnfj", + "timestamp": 1775144323192 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "5w48592ov", + "timestamp": 1775144323175 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "9ytif9zck", + "timestamp": 1775144322476 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "4fgo16wcu", + "timestamp": 1775144322396 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "zcq3mllla", + "timestamp": 1775144321231 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "bfayna5kl", + "timestamp": 1775144321215 + }, + { + "service": "php:5.6.40", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "kyiq1bq1v", + "timestamp": 1775144321206 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "w16aahco6", + "timestamp": 1775144321199 + }, + { + "service": "php:8.2.30", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "v0538f9pc", + "timestamp": 1775144321191 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "fo92zhlw5", + "timestamp": 1775144321183 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "0ba7vtelc", + "timestamp": 1775144321145 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "fohbng7fg", + "timestamp": 1775144321144 + }, + { + "service": "mariadb:10.11.8", + "action": "stop", + "status": "info", + "id": "mbnmye7uu", + "timestamp": 1775144321135 + }, + { + "service": "mariadb:11.4.2", + "action": "stop", + "status": "info", + "id": "cu4e7t4b3", + "timestamp": 1775144321121 + }, + { + "service": "php:5.6.40", + "action": "stop", + "status": "info", + "id": "wepe2jsur", + "timestamp": 1775144321108 + }, + { + "service": "php:7.4.33", + "action": "stop", + "status": "info", + "id": "55y40xqzl", + "timestamp": 1775144321096 + }, + { + "service": "php:8.2.30", + "action": "stop", + "status": "info", + "id": "kb3q8v1j1", + "timestamp": 1775144321083 + }, + { + "service": "php:8.5.4", + "action": "stop", + "status": "info", + "id": "oqx1wr67q", + "timestamp": 1775144321068 + }, + { + "service": "apache", + "action": "stop", + "status": "info", + "id": "w6gbjp5ln", + "timestamp": 1775144321054 + }, + { + "service": "nginx", + "action": "stop", + "status": "info", + "id": "flhslsx8m", + "timestamp": 1775144321047 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "je15n99xp", + "timestamp": 1775144291473 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "mzg4zspgw", + "timestamp": 1775144291453 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "q8u9qvlbv", + "timestamp": 1775144291432 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "f1p0gil1s", + "timestamp": 1775144291406 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "x28rxovfd", + "timestamp": 1775144291122 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "qnk49zop6", + "timestamp": 1775144291102 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "smhza5ym3", + "timestamp": 1775144290439 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "5g8fifvcr", + "timestamp": 1775144290367 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "64rk9a8jl", + "timestamp": 1775144234681 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "o8qxofvhm", + "timestamp": 1775144234661 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "audz6van5", + "timestamp": 1775144234641 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "62m0s8z80", + "timestamp": 1775144234623 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "uhh9nlsrw", + "timestamp": 1775144234367 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "uhp60xly4", + "timestamp": 1775144234343 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "6xskm29tx", + "timestamp": 1775144233649 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "lj7koig8f", + "timestamp": 1775144233579 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "hligqdqbg", + "timestamp": 1775143682791 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "69pbd55hh", + "timestamp": 1775143682765 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "uolp904tg", + "timestamp": 1775143682745 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "sltc3b8i6", + "timestamp": 1775143682727 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "1on6t391k", + "timestamp": 1775143682356 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "5wg4nf0nl", + "timestamp": 1775143682338 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "y3c7yro4y", + "timestamp": 1775143681744 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "b9xo3i1ox", + "timestamp": 1775143681683 + }, + { + "service": "php:5.6.40", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "aakeq52dp", + "timestamp": 1775143568597 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "rixx6genu", + "timestamp": 1775143568590 + }, + { + "service": "php:8.2.30", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "r38zl4zuu", + "timestamp": 1775143568582 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "grxpsvjhg", + "timestamp": 1775143568574 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "lg6o4ibbf", + "timestamp": 1775143568566 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "vswdvrzt4", + "timestamp": 1775143568559 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "h9dnmkhgj", + "timestamp": 1775143568521 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "cdt1dk7oj", + "timestamp": 1775143568519 + }, + { + "service": "php:5.6.40", + "action": "stop", + "status": "info", + "id": "kcp2j9esp", + "timestamp": 1775143568502 + }, + { + "service": "php:7.4.33", + "action": "stop", + "status": "info", + "id": "f7sqck0l8", + "timestamp": 1775143568491 + }, + { + "service": "php:8.2.30", + "action": "stop", + "status": "info", + "id": "bv0eokrdn", + "timestamp": 1775143568477 + }, + { + "service": "php:8.5.4", + "action": "stop", + "status": "info", + "id": "6bx01cztj", + "timestamp": 1775143568465 + }, + { + "service": "mariadb:10.11.8", + "action": "stop", + "status": "info", + "id": "y68edblfs", + "timestamp": 1775143568452 + }, + { + "service": "mariadb:11.4.2", + "action": "stop", + "status": "info", + "id": "p8jhnv2ya", + "timestamp": 1775143568442 + }, + { + "service": "apache", + "action": "stop", + "status": "info", + "id": "rhsnvp7ly", + "timestamp": 1775143568427 + }, + { + "service": "nginx", + "action": "stop", + "status": "info", + "id": "fahkm2kyw", + "timestamp": 1775143568421 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "z0ry0k0k7", + "timestamp": 1775143501975 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "61tszw0xn", + "timestamp": 1775143501949 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "bia7p9f9v", + "timestamp": 1775143501931 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "k6lcmiyl5", + "timestamp": 1775143501912 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "gnk65u5ap", + "timestamp": 1775143501647 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "98t47ve63", + "timestamp": 1775143501606 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "vnmqn0rrq", + "timestamp": 1775143500920 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "341eo8e00", + "timestamp": 1775143500866 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "vjpzdhxwg", + "timestamp": 1775143433818 + }, + { + "service": "php:5.6.40", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "pc1vvmqto", + "timestamp": 1775143433793 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "4cph2zy9r", + "timestamp": 1775143433785 + }, + { + "service": "php:8.2.30", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "6fcacmn91", + "timestamp": 1775143433774 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "66gmzyj2b", + "timestamp": 1775143433764 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "xv5p9bdsn", + "timestamp": 1775143433757 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "rayaa4o0h", + "timestamp": 1775143433717 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "tu0rebj5r", + "timestamp": 1775143433715 + }, + { + "service": "php:5.6.40", + "action": "stop", + "status": "info", + "id": "afjy6gk5a", + "timestamp": 1775143433687 + }, + { + "service": "php:7.4.33", + "action": "stop", + "status": "info", + "id": "rlvru3y4u", + "timestamp": 1775143433669 + }, + { + "service": "php:8.2.30", + "action": "stop", + "status": "info", + "id": "kzkyyigtb", + "timestamp": 1775143433652 + }, + { + "service": "php:8.5.4", + "action": "stop", + "status": "info", + "id": "flijmjctw", + "timestamp": 1775143433633 + }, + { + "service": "mariadb:10.11.8", + "action": "stop", + "status": "info", + "id": "h1zul1uti", + "timestamp": 1775143433612 + }, + { + "service": "mariadb:11.4.2", + "action": "stop", + "status": "info", + "id": "4iy7wdckh", + "timestamp": 1775143433599 + }, + { + "service": "apache", + "action": "stop", + "status": "info", + "id": "65kqv87pb", + "timestamp": 1775143433584 + }, + { + "service": "nginx", + "action": "stop", + "status": "info", + "id": "j25ifpr5s", + "timestamp": 1775143433578 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "9up5u5ywc", + "timestamp": 1775143193051 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "kuqgzsj0b", + "timestamp": 1775143193031 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "00sjap3r2", + "timestamp": 1775143193007 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "je65w6t91", + "timestamp": 1775143192979 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "7y3b8ant5", + "timestamp": 1775143192795 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "gzyh7shwf", + "timestamp": 1775143192771 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "gh7ngadcy", + "timestamp": 1775143191998 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "jvnhlgrej", + "timestamp": 1775143191937 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "6qwfc6xxe", + "timestamp": 1775143002954 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "68bv8y8uc", + "timestamp": 1775143001572 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "era4415c5", + "timestamp": 1775142913122 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "ylnxpe014", + "timestamp": 1775142913100 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "04d0v9wr4", + "timestamp": 1775142913078 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "magvgbbsw", + "timestamp": 1775142912815 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "wt4whxsj0", + "timestamp": 1775142912792 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "80emjncsz", + "timestamp": 1775142912095 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "6b1jpu41d", + "timestamp": 1775142912021 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "sijsuavf4", + "timestamp": 1775142869149 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "tbga3ugmp", + "timestamp": 1775142869120 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "mdo9t7pvn", + "timestamp": 1775142847681 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "ts6kfp9dr", + "timestamp": 1775142847648 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "vl9jmbalt", + "timestamp": 1775142837332 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "s8tjspdk5", + "timestamp": 1775142836335 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "2ufvwxt8u", + "timestamp": 1775142836304 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "penppitxf", + "timestamp": 1775142834342 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "htcsimfit", + "timestamp": 1775142834306 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "z76k0bw25", + "timestamp": 1775142789972 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "baes7toxm", + "timestamp": 1775142789936 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "022oillmk", + "timestamp": 1775142780726 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "l88y6d0v2", + "timestamp": 1775142779708 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "zmujdd0pg", + "timestamp": 1775142779677 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "5nj4pt44o", + "timestamp": 1775142775865 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "6nhqa75ti", + "timestamp": 1775142775829 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "1rzo1kmu5", + "timestamp": 1775142748429 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "6l9vyd62l", + "timestamp": 1775142692713 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "nx53rk1x4", + "timestamp": 1775142692680 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "3rrl38cuc", + "timestamp": 1775142684006 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "qgm12pq29", + "timestamp": 1775142683973 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "j6uk4e2f8", + "timestamp": 1775142613402 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "5k8onfmkz", + "timestamp": 1775142613368 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "vb5elfiji", + "timestamp": 1775142536182 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "87zxbn8td", + "timestamp": 1775142536150 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "xlr096zx9", + "timestamp": 1775142518127 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "kn1jgqpdw", + "timestamp": 1775142518090 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "lcii7xp4g", + "timestamp": 1775142496016 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "3o3q506oe", + "timestamp": 1775142495986 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "rg9lrvp9s", + "timestamp": 1775142494090 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "x4anort95", + "timestamp": 1775142494051 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "tpypbu4a2", + "timestamp": 1775142463063 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "cibkw9iht", + "timestamp": 1775142461628 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "4vjm17x7m", + "timestamp": 1775142346271 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "a2lgfzufq", + "timestamp": 1775142346240 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "adxmi5o2h", + "timestamp": 1775142345209 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "6dkhjh8hj", + "timestamp": 1775142345173 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "yq2h3d1pe", + "timestamp": 1775142343008 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "0vvot89i7", + "timestamp": 1775142342969 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "umytdimhg", + "timestamp": 1775142333906 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "tggk9txe0", + "timestamp": 1775142333872 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "nicwfu4qq", + "timestamp": 1775142332841 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "mrvde3fi3", + "timestamp": 1775142332805 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "husmdcfzd", + "timestamp": 1775142316335 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "2c15mlt5r", + "timestamp": 1775142316297 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "jlvnl4ibm", + "timestamp": 1775142307210 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "dosb7oy4w", + "timestamp": 1775142307177 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "00aj71xlj", + "timestamp": 1775142303019 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "3k615afiz", + "timestamp": 1775142302985 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "syy1zj7yy", + "timestamp": 1775142289974 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "u4z4rrc2h", + "timestamp": 1775142289944 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "g5y31kff0", + "timestamp": 1775142289923 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "iffrkdzg2", + "timestamp": 1775142289901 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "1o9qrkuv7", + "timestamp": 1775142289792 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "j80370nza", + "timestamp": 1775142289771 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "u40buu1dw", + "timestamp": 1775142288919 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "8uq76phpb", + "timestamp": 1775142288903 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "attwi93sv", + "timestamp": 1775142288840 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "p1ipjfvmt", + "timestamp": 1775141316766 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "qhsh2ol3o", + "timestamp": 1775141316746 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "vbu4gxs6w", + "timestamp": 1775141316726 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "33ox6fh1v", + "timestamp": 1775141316704 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "odzgjwnbi", + "timestamp": 1775141316402 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "5oyqe4gkz", + "timestamp": 1775141316340 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "ecuuyspno", + "timestamp": 1775141315714 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "qg9upkvg2", + "timestamp": 1775141315654 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "o6j9i3dev", + "timestamp": 1775141081322 + }, { "service": "apache", "action": "start", diff --git a/settings.json b/settings.json index 9ca7e43..28d22f4 100644 --- a/settings.json +++ b/settings.json @@ -1,5 +1,5 @@ { - "nginxPort": "80", + "nginxPort": "8081", "apachePort": "8080", "serverType": "nginx", "phpPort": "9001", diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index ffec901..e908206 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -1,4 +1,5 @@ import { ipcMain, app, dialog, shell } from 'electron' +import { exec } from 'child_process' import fs from 'fs' import { processManager } from '../services/ProcessManager' import { configService } from '../services/ConfigService' @@ -15,6 +16,7 @@ import { mariaDbOperationService } from '../services/MariaDbOperationService' import { cliAliasService } from '../services/CliAliasService' import { cliBinService } from '../services/CliBinService' import { serviceEventLogger } from '../services/ServiceEventLogger' +import { hostsService } from '../services/HostsService' function findExecutable(dir: string, name: string): string | null { if (!fs.existsSync(dir)) return null @@ -44,13 +46,14 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin const isWindows = process.platform === 'win32'; const binPath = path.join(nginxPrefix, isWindows ? 'nginx.exe' : 'nginx') const confDir = path.join(nginxPrefix, 'conf') - const logDir = path.join(configService.getBasePath(), 'logs') const rootPath = path.join(configService.getBasePath(), 'www') if (!fs.existsSync(rootPath)) fs.mkdirSync(rootPath, { recursive: true }) // Ensure directories exist const projectsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects') + const locationsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects_locations') if (!fs.existsSync(projectsConfDir)) fs.mkdirSync(projectsConfDir, { recursive: true }) + if (!fs.existsSync(locationsConfDir)) fs.mkdirSync(locationsConfDir, { recursive: true }) const tempDirs = ['temp', 'temp/client_body_temp', 'temp/proxy_temp', 'temp/fastcgi_temp', 'temp/uwsgi_temp', 'temp/scgi_temp', 'logs'] for (const dir of tempDirs) { @@ -59,8 +62,15 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin } // Generate project configs - const allProjects = await projectService.getProjects() - const projects = allProjects.filter(p => !p.serverType || p.serverType === 'nginx') + const projects = projectService.getProjects().filter(p => !p.serverType || p.serverType === 'nginx') + + if (forceProjects) { + const files = fs.readdirSync(projectsConfDir) + for (const file of files) { + if (file.endsWith('.conf')) fs.unlinkSync(path.join(projectsConfDir, file)) + } + } + for (const p of projects) { const projectConfPath = path.join(projectsConfDir, `${p.host}.conf`) let shouldGenerate = forceProjects || !fs.existsSync(projectConfPath) @@ -82,11 +92,24 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin normalizedPath += '/' } + // Auto-suffix hostname if it's a simple slug + const finalHost = p.host.includes('.') ? p.host : `${p.host}.local` + await configService.generateConfig('nginx_project.conf.template', `projects/${p.host}.conf`, { - HOST: p.host, + HOST: finalHost, PATH: normalizedPath, PHP_PORT: phpPort.toString(), - CONF_DIR: confDir.replace(/\\/g, '/') + CONF_DIR: confDir.replace(/\\/g, '/'), + PORT: settings.nginxPort + }) + + // Also generate the subdirectory/alias-style config for localhost fallback + await configService.generateConfig('nginx_project_location.conf.template', `projects_locations/${p.host}.conf`, { + HOST: p.host, // Use simple host slug for location path + PATH: normalizedPath, + PHP_PORT: phpPort.toString(), + CONF_DIR: confDir.replace(/\\/g, '/'), + PORT: settings.nginxPort }) } } @@ -95,15 +118,20 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin ? `include "${projectsConfDir.replace(/\\/g, '/')}/*.conf";` : '' + const locationsInclusion = projects.length > 0 + ? `include "${locationsConfDir.replace(/\\/g, '/')}/*.conf";` + : '' + const configPath = await configService.generateConfig('nginx.conf.template', 'nginx.conf', { - PORT: settings.nginxPort, + PORT: settings.nginxPort.toString(), + ROOT: rootPath.replace(/\\/g, '/'), LISTEN_ADDRESS: settings.allowRemoteAccess ? '0.0.0.0' : '127.0.0.1', - PHP_PORT: settings.phpPort, - ROOT: rootPath, - LOG_DIR: logDir.replace(/\\/g, '/'), + PHP_PORT: settings.phpPort.toString(), + LOG_DIR: path.join(nginxPrefix, 'logs').replace(/\\/g, '/'), CONF_DIR: confDir.replace(/\\/g, '/'), - PHPMYADMIN_DIR: phpMyAdminService.getPath().replace(/\\/g, '/'), - PROJECTS: projectsInclusion + PHPMYADMIN_DIR: path.join(configService.getBasePath(), 'bin', 'phpmyadmin').replace(/\\/g, '/'), + PROJECTS: projectsInclusion, + PROJECT_LOCATIONS: locationsInclusion }) return { binPath, nginxPrefix, configPath } @@ -111,7 +139,7 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin async function restartNginx(): Promise<{ success: boolean; message: string }> { try { - const { binPath, nginxPrefix, configPath } = await rebuildNginxConfig(false) + const { binPath, nginxPrefix, configPath } = await rebuildNginxConfig(true) // Force kill Nginx to ensure it picks up new config on Windows await processManager.forceKillAll('nginx') @@ -156,8 +184,6 @@ async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ bi const binPath = findExecutable(apacheDir, isWindows ? 'httpd.exe' : 'httpd') if (!binPath) throw new Error('Apache executable bulunamadı.') - const apacheRoot = path.dirname(path.dirname(binPath)) // bin'in üstü - const logDir = path.join(configService.getBasePath(), 'logs') const rootPath = path.join(configService.getBasePath(), 'www') if (!fs.existsSync(rootPath)) fs.mkdirSync(rootPath, { recursive: true }) @@ -175,11 +201,19 @@ async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ bi // Ensure directories exist const projectsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects_apache') + const aliasesConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects_aliases_apache') if (!fs.existsSync(projectsConfDir)) fs.mkdirSync(projectsConfDir, { recursive: true }) + if (!fs.existsSync(aliasesConfDir)) fs.mkdirSync(aliasesConfDir, { recursive: true }) - // Generate project configs - const allProjects = await projectService.getProjects() - const projects = allProjects.filter(p => p.serverType === 'apache') + if (forceProjects) { + const files = fs.readdirSync(projectsConfDir) + for (const file of files) { + if (file.endsWith('.conf')) fs.unlinkSync(path.join(projectsConfDir, file)) + } + } + + // Ensure projects exist + const projects = projectService.getProjects().filter(p => p.serverType === 'apache') for (const p of projects) { const projectConfPath = path.join(projectsConfDir, `${p.host}.conf`) let shouldGenerate = forceProjects || !fs.existsSync(projectConfPath) @@ -200,13 +234,28 @@ async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ bi const phpDir = path.join(configService.getBasePath(), 'bin', `php-${p.phpVersion}`) const phpCgiBinary = findExecutable(phpDir, process.platform === 'win32' ? 'php-cgi.exe' : 'php-cgi') || '' + // Auto-suffix hostname if it's a simple slug + const finalHost = p.host.includes('.') ? p.host : `${p.host}.local` + await configService.generateConfig('apache_project.conf.template', `projects_apache/${p.host}.conf`, { + HOST: finalHost, + PATH: normalizedPath, + PHP_PORT: phpPort.toString(), + PHP_DIR: phpDir.replace(/\\/g, '/'), + PHP_CGI_PATH: phpCgiBinary.replace(/\\/g, '/'), + PHP_BIN_URL: `/php-bin-${p.host}`, + PORT: settings.apachePort || '8080' + }) + + // Also generate the subdirectory/alias-style config for localhost fallback + await configService.generateConfig('apache_project_alias.conf.template', `projects_aliases_apache/${p.host}.conf`, { HOST: p.host, PATH: normalizedPath, PHP_PORT: phpPort.toString(), PHP_DIR: phpDir.replace(/\\/g, '/'), PHP_CGI_PATH: phpCgiBinary.replace(/\\/g, '/'), - PHP_BIN_URL: `/php-bin-${p.host}` + PHP_BIN_URL: `/php-bin-alias-${p.host}`, + PORT: settings.apachePort || '8080' }) } } @@ -216,15 +265,20 @@ async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ bi ? `Include "${projectsConfDir.replace(/\\/g, '/')}/*.conf"` : '' + const aliasesInclusion = projects.length > 0 + ? `Include "${aliasesConfDir.replace(/\\/g, '/')}/*.conf"` + : '' + await configService.generateConfig('httpd.conf.template', 'httpd.conf', { - PORT: settings.apachePort || '8080', + PORT: settings.apachePort.toString(), LISTEN_ADDRESS: settings.allowRemoteAccess ? '0.0.0.0' : '127.0.0.1', - PHP_PORT: settings.phpPort, ROOT: rootPath.replace(/\\/g, '/'), - SRVROOT: apacheRoot.replace(/\\/g, '/'), - LOG_DIR: logDir.replace(/\\/g, '/'), - PHPMYADMIN_DIR: phpMyAdminService.getPath().replace(/\\/g, '/'), - PROJECTS: projectsInclusion + PHP_PORT: settings.phpPort.toString(), + LOG_DIR: path.join(configService.getBasePath(), 'logs').replace(/\\/g, '/'), + SRVROOT: path.join(configService.getBasePath(), 'bin', 'apache', 'Apache24').replace(/\\/g, '/'), + PHPMYADMIN_DIR: path.join(configService.getBasePath(), 'bin', 'phpmyadmin').replace(/\\/g, '/'), + PROJECTS: projectsInclusion, + PROJECT_ALIASES: aliasesInclusion }) } @@ -233,7 +287,7 @@ async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ bi async function restartApache(): Promise<{ success: boolean; message: string }> { try { - const { binPath, configPath } = await rebuildApacheConfig(false) + const { binPath, configPath } = await rebuildApacheConfig(true) const settings = configService.getSettings() await processManager.forceKillAll('apache') @@ -796,10 +850,32 @@ export function registerIpcHandlers(): void { return result.filePath }) - ipcMain.handle('shell:open-path', async (_event, pathStr: string) => { + ipcMain.handle('open-path', async (_event, pathStr: string) => { return await shell.openPath(pathStr) }) + ipcMain.handle('open-vscode', async (_event, pathStr: string) => { + return new Promise((resolve) => { + exec(`code "${pathStr}"`, (error) => { + if (error) { + console.error('Failed to open VS Code:', error) + resolve({ success: false, message: error.message }) + } else { + resolve({ success: true }) + } + }) + }) + }) + + // Hosts file management + ipcMain.handle('hosts:check', async (_event, hostname: string) => { + return await hostsService.checkEntry(hostname) + }) + + ipcMain.handle('hosts:add', async (_event, hostname: string) => { + return await hostsService.addEntry(hostname) + }) + ipcMain.handle('binaries:download', async (_event, { name, url }) => { const result = await downloadService.downloadAndExtract(url, name, (p) => { // TODO: Emit progress to renderer via webContents.send diff --git a/src/main/services/HostsService.ts b/src/main/services/HostsService.ts new file mode 100644 index 0000000..d45c9f7 --- /dev/null +++ b/src/main/services/HostsService.ts @@ -0,0 +1,73 @@ +import fs from 'fs' +import path from 'path' +import { exec } from 'child_process' + +export class HostsService { + private hostsPath: string + + constructor() { + // Windows: C:\Windows\System32\drivers\etc\hosts + // Linux/macOS: /etc/hosts + this.hostsPath = process.platform === 'win32' + ? path.join(process.env.windir || 'C:\\Windows', 'System32', 'drivers', 'etc', 'hosts') + : '/etc/hosts' + } + + async checkEntry(hostname: string): Promise { + try { + if (!fs.existsSync(this.hostsPath)) return false + const content = fs.readFileSync(this.hostsPath, 'utf8') + + // Look for 127.0.0.1 or ::1 + const regex = new RegExp(`(^|\\s)(127\\.0\\.0\\.1|::1)\\s+${hostname.replace(/\./g, '\\.')}(\\s|$)`, 'm') + return regex.test(content) + } catch (error) { + console.error('Failed to read hosts file:', error) + return false + } + } + + async addEntry(hostname: string): Promise<{ success: boolean; message: string }> { + if (await this.checkEntry(hostname)) { + return { success: true, message: 'Entry already exists' } + } + + return new Promise((resolve) => { + if (process.platform === 'win32') { + // Windows: Use PowerShell with -Verb RunAs for UAC prompt + // Using `n (newline) to ensure we don't append to the last line's end + const cmd = `powershell -Command "Start-Process powershell -ArgumentList '-Command Add-Content -Path $env:windir\\System32\\drivers\\etc\\hosts -Value \"\`n127.0.0.1 ${hostname}\" -Force' -Verb RunAs"` + + exec(cmd, (error) => { + if (error) { + console.error('Failed to update hosts file:', error) + resolve({ success: false, message: 'Yönetici izni (UAC) reddedildi veya bir hata oluştu.' }) + } else { + // Success doesn't mean the entry was actually added (could have been cancelled in UAC) + // So we wait a bit and re-check + setTimeout(async () => { + const verified = await this.checkEntry(hostname) + if (verified) { + resolve({ success: true, message: 'Hosts dosyası başarıyla güncellendi.' }) + } else { + resolve({ success: false, message: 'Kayıt eklenemedi. Lütfen yönetici iznini onaylayın.' }) + } + }, 2000) + } + }) + } else { + // Linux/macOS: Use sudo + const cmd = `echo "127.0.0.1 ${hostname}" | sudo tee -a ${this.hostsPath}` + exec(cmd, (error) => { + if (error) { + resolve({ success: false, message: error.message }) + } else { + resolve({ success: true, message: 'Hosts file updated.' }) + } + }) + } + }) + } +} + +export const hostsService = new HostsService() diff --git a/src/main/services/PhpManagerService.ts b/src/main/services/PhpManagerService.ts index a5eca3c..85909f3 100644 --- a/src/main/services/PhpManagerService.ts +++ b/src/main/services/PhpManagerService.ts @@ -268,11 +268,15 @@ export class PhpManagerService { const iniPath = path.join(phpDir, 'php.ini') if (!fs.existsSync(iniPath)) return false + const sessionsDir = path.join(configService.getBasePath(), 'sessions').replace(/\\/g, '/') + if (!fs.existsSync(sessionsDir)) fs.mkdirSync(sessionsDir, { recursive: true }) + let content = fs.readFileSync(iniPath, 'utf8') const lines = content.split(/\r?\n/) let forceRedirectSet = false let fixPathinfoSet = false + let sessionPathSet = false const newLines = lines.map(line => { const trimmed = line.trim() @@ -284,11 +288,16 @@ export class PhpManagerService { fixPathinfoSet = true return 'cgi.fix_pathinfo = 1' } + if (trimmed.startsWith('session.save_path') || trimmed.startsWith(';session.save_path')) { + sessionPathSet = true + return `session.save_path = "${sessionsDir}"` + } return line }) if (!forceRedirectSet) newLines.push('cgi.force_redirect = 0') if (!fixPathinfoSet) newLines.push('cgi.fix_pathinfo = 1') + if (!sessionPathSet) newLines.push(`session.save_path = "${sessionsDir}"`) fs.writeFileSync(iniPath, newLines.join('\n'), 'utf8') return true diff --git a/src/main/services/ProjectService.ts b/src/main/services/ProjectService.ts index 2bd365c..3a010f0 100644 --- a/src/main/services/ProjectService.ts +++ b/src/main/services/ProjectService.ts @@ -34,7 +34,17 @@ export class ProjectService { } private saveProjects() { - fs.writeFileSync(this.projectsFile, JSON.stringify(this.projects, null, 2)) + const tempFile = `${this.projectsFile}.tmp` + try { + fs.writeFileSync(tempFile, JSON.stringify(this.projects, null, 2)) + if (fs.existsSync(tempFile)) { + fs.renameSync(tempFile, this.projectsFile) + } + } catch (e) { + console.error('Failed to save projects safely:', e) + // Fallback to direct write if rename fails + fs.writeFileSync(this.projectsFile, JSON.stringify(this.projects, null, 2)) + } } getProjects(): Project[] { diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index e190334..3786169 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -350,10 +350,6 @@ function App(): JSX.Element { const interval = setInterval(() => { fetchStatus() - // fetchProjects() // Reduced frequency for these - // fetchPhpVersions() - // fetchMariaDbVersions() - // fetchNginxVersions() }, 3000) let logInterval: NodeJS.Timeout | null = null @@ -1135,8 +1131,6 @@ function App(): JSX.Element { {activeTab === 'user_guide' && } {activeTab === 'settings' && ( - - setSettingsTab(v)} textColor="primary" indicatorColor="primary" variant="scrollable" scrollButtons="auto"> } iconPosition="start" /> @@ -1537,8 +1531,6 @@ function App(): JSX.Element { {activeTab === 'dashboard' && ( <> - - setDashboardTab(v)} textColor="primary" indicatorColor="primary"> @@ -1986,11 +1978,28 @@ function App(): JSX.Element { } fetchStatus(); }} - onOpenSite={(p: any) => { + onOpenSite={(p: any, isHostMapped: boolean) => { const port = p.serverType === 'apache' ? settings.apachePort : settings.nginxPort - window.open(`http://localhost:${port}/${p.host}/`, '_blank') + if (isHostMapped) { + const finalHost = p.host && p.host.includes('.') ? p.host : `${p.host}.local` + window.open(`http://${finalHost}:${port}/`, '_blank') + } else { + window.open(`http://localhost:${port}/${p.host}/`, '_blank') + } }} onOpenFolder={(path: string) => window.api.invoke('open-path', path)} + onOpenVSCode={(path: string) => window.api.invoke('open-vscode', path)} + onAddHost={async (hostname: string) => { + const result = await window.api.invoke('hosts:add', hostname); + if (result.success) { + // Refresh current project to check mapping again + const currentId = selectedProjectId; + setSelectedProjectId(null); + setTimeout(() => setSelectedProjectId(currentId), 10); + } else { + alert(result.message); + } + }} onManageDb={() => setActiveTab('db_manager')} /> )} diff --git a/src/renderer/src/components/ProjectManager/ProjectDetails.tsx b/src/renderer/src/components/ProjectManager/ProjectDetails.tsx index b66e758..8c8bc14 100644 --- a/src/renderer/src/components/ProjectManager/ProjectDetails.tsx +++ b/src/renderer/src/components/ProjectManager/ProjectDetails.tsx @@ -10,8 +10,10 @@ import { Chip, Divider, Stack, - Tooltip + Tooltip, + CircularProgress } from '@mui/material' +import { useState, useEffect } from 'react' import { Folder as ProjectIcon, FolderOpen as FolderOpenIcon, @@ -21,7 +23,8 @@ import { Edit as EditIcon, Delete as DeleteIcon, Tune as TuningIcon, - OpenInNew as OpenIcon + OpenInNew as OpenIcon, + Code as CodeIcon } from '@mui/icons-material' import ProjectResourceMonitor from './ProjectResourceMonitor' @@ -35,9 +38,13 @@ interface ProjectDetailsProps { onEdit: () => void; onDelete: () => void; onToggleStatus: () => void; - onOpenSite: () => void; + onOpenSite: (useHost: boolean) => void; onOpenFolder: () => void; + onOpenVSCode: () => void; onManageDb: () => void; + onAddHost: (hostname: string) => void; + isTransitioning?: boolean; + setTransitioning?: (v: boolean) => void; } export default function ProjectDetails({ @@ -52,8 +59,20 @@ export default function ProjectDetails({ onToggleStatus, onOpenSite, onOpenFolder, - onManageDb + onOpenVSCode, + onManageDb, + onAddHost, + isTransitioning = false, + setTransitioning }: ProjectDetailsProps) { + const [isHostMapped, setIsHostMapped] = useState(true); + + useEffect(() => { + if (p?.host) { + const finalHost = p.host.includes('.') ? p.host : `${p.host}.local`; + window.api.invoke('hosts:check', finalHost).then(setIsHostMapped); + } + }, [p]); if (!p) { return ( @@ -70,12 +89,12 @@ export default function ProjectDetails({ return ( - - - - - - + + + + + + {p.name} @@ -86,35 +105,96 @@ export default function ProjectDetails({ - - - - - - - - - - - - - - + + + + + + {!isHostMapped && ( + + )} + + + + + + + + + + + diff --git a/src/renderer/src/components/ProjectManager/ProjectManager.tsx b/src/renderer/src/components/ProjectManager/ProjectManager.tsx index b36e972..700ed61 100644 --- a/src/renderer/src/components/ProjectManager/ProjectManager.tsx +++ b/src/renderer/src/components/ProjectManager/ProjectManager.tsx @@ -17,8 +17,10 @@ interface ProjectManagerProps { onEditProject: (p: any) => void; onRemoveProject: (id: string) => void; onToggleStatus: (p: any) => void; - onOpenSite: (p: any) => void; + onOpenSite: (p: any, useHost: boolean) => void; onOpenFolder: (path: string) => void; + onOpenVSCode: (path: string) => void; + onAddHost: (hostname: string) => void; onManageDb: () => void; } @@ -39,6 +41,8 @@ export default function ProjectManager({ onToggleStatus, onOpenSite, onOpenFolder, + onOpenVSCode, + onAddHost, onManageDb }: ProjectManagerProps) { @@ -74,8 +78,10 @@ export default function ProjectManager({ onEdit={() => onEditProject(p)} onDelete={() => onRemoveProject(p.id)} onToggleStatus={() => onToggleStatus(p)} - onOpenSite={() => onOpenSite(p)} + onOpenSite={(useHost) => onOpenSite(p, useHost)} onOpenFolder={() => onOpenFolder(p.path)} + onOpenVSCode={() => onOpenVSCode(p.path)} + onAddHost={onAddHost} onManageDb={onManageDb} /> diff --git a/src/renderer/src/components/ProjectManager/ProjectResourceMonitor.tsx b/src/renderer/src/components/ProjectManager/ProjectResourceMonitor.tsx index f98ea8b..0dd5cdd 100644 --- a/src/renderer/src/components/ProjectManager/ProjectResourceMonitor.tsx +++ b/src/renderer/src/components/ProjectManager/ProjectResourceMonitor.tsx @@ -1,4 +1,5 @@ -import { Box, Typography, LinearProgress } from '@mui/material' +import { Box, Typography } from '@mui/material' +import { Gauge, gaugeClasses } from '@mui/x-charts/Gauge' interface ServiceInfo { status: string; @@ -13,7 +14,7 @@ interface ProjectResourceMonitorProps { } const formatMemory = (bytes?: number) => { - if (!bytes) return '' + if (!bytes) return '0 MB' const mb = bytes / (1024 * 1024) if (mb > 1024) return `${(mb / 1024).toFixed(1)} GB` return `${Math.round(mb)} MB` @@ -25,54 +26,96 @@ export default function ProjectResourceMonitor({ serverStats, phpStats, serverTy if (!hasServerStat && !hasPhpStat) return null; - const renderMiniStats = (stats?: ServiceInfo, label: string = 'Service') => { + const renderServiceCard = (stats?: ServiceInfo, label: string = 'Service') => { if (!stats) return null; - const cpu = stats.cpu || 0; + const cpu = Math.max(0, Math.min(100, Math.round(stats.cpu || 0))); const memory = stats.memory || 0; const ramMB = Math.round(memory / (1024 * 1024)); const ramPercent = Math.min((ramMB / 1024) * 100, 100); + + const getColor = (val: number) => { + if (val < 30) return '#33d9b2'; + if (val < 70) return '#ffb142'; + return '#ff5252'; + } return ( - - - {label.toUpperCase()} - - {cpu}% - {formatMemory(memory)} + + + {label} + + + + {/* CPU Gauge */} + + + ({ + [`& .${gaugeClasses.valueText}`]: { fontSize: 14, fontWeight: 800, fill: getColor(cpu) }, + [`& .${gaugeClasses.valueArc}`]: { fill: getColor(cpu) }, + [`& .${gaugeClasses.referenceArc}`]: { fill: 'rgba(255,255,255,0.05)' }, + })} + text={({ value }) => `${value}%`} + /> + + CPU + + + {/* RAM Gauge */} + + + ({ + [`& .${gaugeClasses.valueText}`]: { fontSize: 12, fontWeight: 800, fill: 'secondary.light' }, + [`& .${gaugeClasses.valueArc}`]: { fill: 'secondary.main' }, + [`& .${gaugeClasses.referenceArc}`]: { fill: 'rgba(255,255,255,0.05)' }, + })} + text={() => formatMemory(memory)} + /> + + RAM - - - - ); }; return ( - - {renderMiniStats(serverStats, serverType)} - {renderMiniStats(phpStats, 'PHP')} + + {renderServiceCard(serverStats, serverType)} + {renderServiceCard(phpStats, 'PHP')} ); } diff --git a/src/renderer/src/components/ProjectManager/ProjectSidebar.tsx b/src/renderer/src/components/ProjectManager/ProjectSidebar.tsx index 5b3b23b..d30e64f 100644 --- a/src/renderer/src/components/ProjectManager/ProjectSidebar.tsx +++ b/src/renderer/src/components/ProjectManager/ProjectSidebar.tsx @@ -10,7 +10,7 @@ import { } from '@mui/material' import { Search as SearchIcon, - Folder as ProjectIcon + Add as AddIcon } from '@mui/icons-material' interface ProjectSidebarProps { @@ -66,7 +66,7 @@ export default function ProjectSidebar({ - +