feat: implement Nginx and PHP service management IPC handlers and add sweetalert2 dependency

This commit is contained in:
Ümit Tunç
2026-03-30 07:06:43 +03:00
parent 862364d4d8
commit bd566eea1c
6 changed files with 126 additions and 15 deletions
+1 -1
View File
@@ -1 +1 @@
43936 32612
+12 -1
View File
@@ -20,7 +20,8 @@
"i18next": "^23.1.0", "i18next": "^23.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^13.0.0" "react-i18next": "^13.0.0",
"sweetalert2": "^11.26.24"
}, },
"devDependencies": { "devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2", "@electron-toolkit/eslint-config": "^1.0.2",
@@ -8940,6 +8941,16 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/synckit": {
"version": "0.11.12", "version": "0.11.12",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+2 -1
View File
@@ -32,7 +32,8 @@
"i18next": "^23.1.0", "i18next": "^23.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^13.0.0" "react-i18next": "^13.0.0",
"sweetalert2": "^11.26.24"
}, },
"devDependencies": { "devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2", "@electron-toolkit/eslint-config": "^1.0.2",
+21 -3
View File
@@ -120,7 +120,13 @@ export function registerIpcHandlers(): void {
} }
const success = await processManager.startService('nginx', binPath, ['-p', nginxPrefix, '-c', configPath], false) 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')) { 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) 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ı.` } 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.' } 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 // Force kill this specific version if running or generic mysql
await processManager.forceKillAll(serviceName) await processManager.forceKillAll(serviceName)
@@ -214,8 +228,12 @@ export function registerIpcHandlers(): void {
// Quick health check for MySQL // Quick health check for MySQL
await new Promise(r => setTimeout(r, 1500)) await new Promise(r => setTimeout(r, 1500))
if (!processManager.isServiceRunning(serviceName)) { 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ı.` } return { success, message: success ? `${mysqlVersion || ''} MySQL başlatıldı.` : `${mysqlVersion || ''} MySQL devreye alınamadı.` }
+45
View File
@@ -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<string[]> {
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 { stopAll(): void {
for (const name of this.processes.keys()) { for (const name of this.processes.keys()) {
this.stopService(name) this.stopService(name)
+45 -9
View File
@@ -69,6 +69,7 @@ import {
Handyman as ToolsIcon, Handyman as ToolsIcon,
Restore as ResetIcon Restore as ResetIcon
} from '@mui/icons-material' } from '@mui/icons-material'
import Swal from 'sweetalert2'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
declare global { declare global {
@@ -302,11 +303,26 @@ function App(): JSX.Element {
// Refresh status after operation // Refresh status after operation
fetchStatus() fetchStatus()
setNotification({ if (!result.success) {
open: true, Swal.fire({
message: result.message, title: 'Hata!',
severity: result.success ? 'success' : 'error' 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) => { const handleOpenLogs = async (service: string) => {
@@ -316,10 +332,30 @@ function App(): JSX.Element {
} }
const handleResetData = async (service: string) => { 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 confirmResult = await Swal.fire({
const result = await window.api.invoke('services:reset-data', service) title: 'Emin misiniz?',
alert(result.message) text: `${service} verilerini sıfırlamak istiyor musunuz? Bu işlem tüm verileri siler ve temiz kurulum yapar.`,
fetchStatus() 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 () => { const handleSaveSettings = async () => {