From bd566eea1cadbebdbcabc8ff1fff40acff0c192f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Mon, 30 Mar 2026 07:06:43 +0300 Subject: [PATCH] feat: implement Nginx and PHP service management IPC handlers and add sweetalert2 dependency --- logs/nginx.pid | 2 +- package-lock.json | 13 ++++++- package.json | 3 +- src/main/ipc/index.ts | 24 +++++++++++-- src/main/services/ProcessManager.ts | 45 ++++++++++++++++++++++++ src/renderer/src/App.tsx | 54 ++++++++++++++++++++++++----- 6 files changed, 126 insertions(+), 15 deletions(-) diff --git a/logs/nginx.pid b/logs/nginx.pid index 5e4f92a..361935a 100644 --- a/logs/nginx.pid +++ b/logs/nginx.pid @@ -1 +1 @@ -43936 +32612 diff --git a/package-lock.json b/package-lock.json index 2985967..a93e91f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,8 @@ "i18next": "^23.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-i18next": "^13.0.0" + "react-i18next": "^13.0.0", + "sweetalert2": "^11.26.24" }, "devDependencies": { "@electron-toolkit/eslint-config": "^1.0.2", @@ -8940,6 +8941,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sweetalert2": { + "version": "11.26.24", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.24.tgz", + "integrity": "sha512-SLgukW4wicewpW5VOukSXY5Z6DL/z7HCOK2ODSjmQPiSphCN8gJAmh9npoceXOtBRNoDN0xIz+zHYthtfiHmjg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, "node_modules/synckit": { "version": "0.11.12", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", diff --git a/package.json b/package.json index 4d532b8..9fba9b7 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "i18next": "^23.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-i18next": "^13.0.0" + "react-i18next": "^13.0.0", + "sweetalert2": "^11.26.24" }, "devDependencies": { "@electron-toolkit/eslint-config": "^1.0.2", diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 6003a1f..0298267 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -120,7 +120,13 @@ export function registerIpcHandlers(): void { } const success = await processManager.startService('nginx', binPath, ['-p', nginxPrefix, '-c', configPath], false) - return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx devreye alınamadı (Hata loglarını kontrol edin).' } + + if (!success) { + const errorLogs = await processManager.getLastErrorLogs('nginx') + return { success: false, message: `Nginx başlatılamadı.\n\n${errorLogs.join('\n')}` } + } + + return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx devreye alınamadı.' } } if (serviceName.startsWith('php')) { @@ -151,6 +157,12 @@ export function registerIpcHandlers(): void { }) const success = await processManager.startService(serviceName, binPath, ['-b', `127.0.0.1:${phpPort}`, '-c', configPath], false) + + if (!success) { + const errorLogs = await processManager.getLastErrorLogs(serviceName) + return { success: false, message: `${phpVersion || ''} PHP başlatılamadı.\n\n${errorLogs.join('\n')}` } + } + return { success, message: success ? `${phpVersion || ''} PHP başlatıldı.` : `${phpVersion || ''} PHP devreye alınamadı.` } } @@ -168,7 +180,9 @@ export function registerIpcHandlers(): void { if (!binPath) return { success: false, message: 'MySQL executable bulunamadı. Lütfen indirin.' } - const mysqlPort = mysqlId ? (settings.mysqlPorts?.[mysqlId] || '3306') : settings.mysqlPort + const mysqlVersions = await mySqlManagerService.getVersions() + const mysqlInfo = mysqlVersions.find(v => v.id === mysqlId) + const mysqlPort = mysqlInfo?.port || (mysqlId ? '3306' : settings.mysqlPort) // Force kill this specific version if running or generic mysql await processManager.forceKillAll(serviceName) @@ -214,8 +228,12 @@ export function registerIpcHandlers(): void { // Quick health check for MySQL await new Promise(r => setTimeout(r, 1500)) if (!processManager.isServiceRunning(serviceName)) { - return { success: false, message: `${mysqlVersion || ''} MySQL başlatıldı ancak çalışma hatası verdi. Lütfen logları ve port çakışmalarını kontrol edin.` } + const errorLogs = await processManager.getLastErrorLogs(serviceName) + return { success: false, message: `${mysqlVersion || ''} MySQL başlatıldı ancak çalışma hatası verdi (ExitCode: 1).\n\n${errorLogs.length > 0 ? 'Önemli Hata Logları:\n' + errorLogs.join('\n') : 'Lütfen log sekmesini kontrol edin.'}` } } + } else { + const errorLogs = await processManager.getLastErrorLogs(serviceName) + return { success: false, message: `${mysqlVersion || ''} MySQL başlatılamadı.\n\n${errorLogs.join('\n')}` } } return { success, message: success ? `${mysqlVersion || ''} MySQL başlatıldı.` : `${mysqlVersion || ''} MySQL devreye alınamadı.` } diff --git a/src/main/services/ProcessManager.ts b/src/main/services/ProcessManager.ts index cc6b8ae..ef56874 100644 --- a/src/main/services/ProcessManager.ts +++ b/src/main/services/ProcessManager.ts @@ -318,6 +318,51 @@ export class ProcessManager { } } + getLastLogs(name: string, count: number = 10): string[] { + const serviceLogs = this.logs.get(name) || [] + return serviceLogs.slice(-count) + } + + async getLastErrorLogs(name: string, count: number = 10): Promise { + this.initLogs() + const foundLogs: string[] = [] + + // 1. Check in-memory logs for [CORE ERROR] or other error markers + const memLogs = this.logs.get(name) || [] + const errorMarkers = ['ERROR', 'failed', 'Fatal', 'error'] + const recentErrors = memLogs.filter(line => + errorMarkers.some(m => line.toLowerCase().includes(m.toLowerCase())) + ).slice(-count) + + foundLogs.push(...recentErrors) + + // 2. Check physical log files if memory logs are insufficient + const sanitizedName = name.replace(/:/g, '_') + const possibleFiles = [ + path.join(this.logDir, `${sanitizedName}.log`), + path.join(this.logDir, `${sanitizedName}_error.log`), + path.join(this.logDir, `mysql_error.log`), + path.join(this.logDir, `nginx_error.log`) + ] + + for (const logPath of possibleFiles) { + if (fs.existsSync(logPath)) { + try { + const content = fs.readFileSync(logPath, 'utf8') + const lines = content.split(/\r?\n/).filter(l => l.trim()) + const relevantLines = lines.slice(-count) + relevantLines.forEach(l => { + if (!foundLogs.includes(l)) foundLogs.push(l) + }) + } catch (e) { + // Ignore errors + } + } + } + + return Array.from(new Set(foundLogs)).slice(-count) + } + stopAll(): void { for (const name of this.processes.keys()) { this.stopService(name) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index c3f0c28..581a3a1 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -69,6 +69,7 @@ import { Handyman as ToolsIcon, Restore as ResetIcon } from '@mui/icons-material' +import Swal from 'sweetalert2' import { useTranslation } from 'react-i18next' declare global { @@ -302,11 +303,26 @@ function App(): JSX.Element { // Refresh status after operation fetchStatus() - setNotification({ - open: true, - message: result.message, - severity: result.success ? 'success' : 'error' - }) + if (!result.success) { + Swal.fire({ + title: 'Hata!', + text: result.message, + icon: 'error', + confirmButtonText: 'Tamam', + background: '#1e1e1e', + color: '#fff', + confirmButtonColor: '#153E5E', + customClass: { + popup: 'swal2-dark-popup' + } + }) + } else { + setNotification({ + open: true, + message: result.message, + severity: 'success' + }) + } } const handleOpenLogs = async (service: string) => { @@ -316,10 +332,30 @@ function App(): JSX.Element { } const handleResetData = async (service: string) => { - if (!window.confirm(`${service} verilerini sıfırlamak istiyor musunuz? Bu işlem tüm verileri siler ve temiz kurulum yapar.`)) return - const result = await window.api.invoke('services:reset-data', service) - alert(result.message) - fetchStatus() + const confirmResult = await Swal.fire({ + title: 'Emin misiniz?', + text: `${service} verilerini sıfırlamak istiyor musunuz? Bu işlem tüm verileri siler ve temiz kurulum yapar.`, + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Evet, Sıfırla', + cancelButtonText: 'Vazgeç', + background: '#1e1e1e', + color: '#fff', + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6' + }) + + if (confirmResult.isConfirmed) { + const result = await window.api.invoke('services:reset-data', service) + Swal.fire({ + title: result.success ? 'Başarılı!' : 'Hata!', + text: result.message, + icon: result.success ? 'success' : 'error', + background: '#1e1e1e', + color: '#fff' + }) + fetchStatus() + } } const handleSaveSettings = async () => {