feat: implement IPC service management handlers for Nginx, PHP, and MySQL and add multi-language support.

This commit is contained in:
Ümit Tunç
2026-03-28 21:47:45 +03:00
parent a411138732
commit 2a66537f86
7 changed files with 178 additions and 88 deletions
+23 -12
View File
@@ -115,32 +115,43 @@ export function registerIpcHandlers(): void {
return { success, message: success ? `${phpVersion || ''} PHP başlatıldı.` : `${phpVersion || ''} PHP devreye alınamadı.` }
}
if (serviceName === 'mysql') {
const dataDir = path.join(app.getPath('userData'), 'mysql_data')
const binPath = findExecutable(binDir, 'mysqld.exe')
if (serviceName.startsWith('mysql')) {
const versionMatch = serviceName.match(/^mysql:(.+)$/)
const mysqlVersion = versionMatch ? versionMatch[1] : null
const mysqlId = mysqlVersion ? `mysql-${mysqlVersion}` : null
const dataDir = mysqlId
? path.join(app.getPath('userData'), `mysql_data_${mysqlId}`)
: path.join(app.getPath('userData'), 'mysql_data')
const mysqlDir = mysqlId ? path.join(binDir, mysqlId) : binDir
const binPath = findExecutable(mysqlDir, 'mysqld.exe')
if (!binPath) return { success: false, message: 'MySQL executable bulunamadı. Lütfen indirin.' }
// Force kill any existing instances before starting
await processManager.forceKillAll('mysql')
// Force kill this specific version if running or generic mysql
// Actually processManager handles by serviceName
await processManager.forceKillAll(serviceName)
// Initialize if data directory is empty
if (!fs.existsSync(dataDir) || fs.readdirSync(dataDir).length === 0) {
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
console.log('Initializing MySQL data directory...')
console.log(`Initializing MySQL data directory for ${serviceName}...`)
await processManager.runOnce(binPath, ['--initialize-insecure', `--datadir=${dataDir.replace(/\\/g, '/')}`])
}
const logFile = path.join(app.getPath('userData'), 'logs', 'mysql_error.log')
const configPath = await configService.generateConfig('my.ini.template', 'my.ini', {
PORT: settings.mysqlPort,
const mysqlPort = mysqlId ? (settings.mysqlPorts?.[mysqlId] || '3306') : settings.mysqlPort
const logFile = path.join(app.getPath('userData'), 'logs', `${serviceName}_error.log`)
const configPath = await configService.generateConfig('my.ini.template', `my_${mysqlId || 'default'}.ini`, {
PORT: mysqlPort,
DATADIR: dataDir.replace(/\\/g, '/'),
SOCKET: '/tmp/mysql.sock',
SOCKET: `/tmp/${serviceName}.sock`,
LOG_FILE: logFile.replace(/\\/g, '/')
})
const success = await processManager.startService('mysql', binPath, ['--defaults-file=' + configPath], false)
return { success, message: success ? 'MySQL başlatıldı.' : 'MySQL devreye alınamadı (Hata loglarını kontrol edin).' }
const success = await processManager.startService(serviceName, binPath, ['--defaults-file=' + configPath], false)
return { success, message: success ? `${mysqlVersion || ''} MySQL başlatıldı.` : `${mysqlVersion || ''} MySQL devreye alınamadı.` }
}
return { success: false, message: 'Bilinmeyen servis.' }
+1
View File
@@ -10,6 +10,7 @@ export class ConfigService {
nginxPort: '80',
phpPort: '9001',
mysqlPort: '3306',
mysqlPorts: {} as Record<string, string>,
language: 'tr'
}
+6
View File
@@ -1,6 +1,7 @@
import fs from 'fs'
import path from 'path'
import { app } from 'electron'
import { configService } from './ConfigService'
export interface MySqlVersion {
id: string
@@ -9,6 +10,7 @@ export interface MySqlVersion {
url: string
status: 'available' | 'downloading' | 'installed'
progress: number
port: string
description?: string
}
@@ -55,6 +57,9 @@ export class MySqlManagerService {
// Working CDN Pattern: https://cdn.mysql.com/archives/mysql-MAJOR.MINOR/mysql-VERSION-winx64.zip
const url = `https://cdn.mysql.com/archives/mysql-${majorMinor}/mysql-${version}-winx64.zip`
const settings = configService.getSettings()
const customPort = settings.mysqlPorts?.[`mysql-${version}`] || '3306'
return {
id: `mysql-${version}`,
version,
@@ -62,6 +67,7 @@ export class MySqlManagerService {
url,
status: 'available',
progress: 0,
port: customPort,
description: v.status
}
})
+100 -43
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect, Fragment } from 'react'
import React, { useState, useEffect } from 'react'
import logo from './assets/logo.svg'
import {
Box,
@@ -79,15 +79,14 @@ declare global {
const drawerWidth = 240
interface ServiceStatus {
nginx: string
php: string
mysql: string
[key: string]: string
}
interface AppSettings {
nginxPort: string
phpPort: string
mysqlPort: string
mysqlPorts?: Record<string, string>
language: string
}
@@ -330,20 +329,25 @@ function App(): JSX.Element {
const handleStartAll = async () => {
if (!window.api) return
const criticalServices = ['nginx', 'mysql']
const criticalServices = ['nginx']
for (const s of criticalServices) {
if (status[s] === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
}
// Also starting each installed PHP version
const phpToStart = phpVersions.filter(v => v.status === 'installed').map(v => `php:${v.version}`)
for (const s of phpToStart) {
if ((status[s] || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
}
const mysqlToStart = mySqlVersions.filter(v => v.status === 'installed').map(v => `mysql:${v.version}`)
for (const s of mysqlToStart) {
if ((status[s] || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
}
await Promise.all([
...criticalServices.filter(s => status[s] !== 'running').map(s => window.api?.startService(s)),
...phpToStart.filter(s => (status[s] || 'stopped') !== 'running').map(s => window.api?.startService(s))
...phpToStart.filter(s => (status[s] || 'stopped') !== 'running').map(s => window.api?.startService(s)),
...mysqlToStart.filter(s => (status[s] || 'stopped') !== 'running').map(s => window.api?.startService(s))
])
fetchStatus()
@@ -352,12 +356,15 @@ function App(): JSX.Element {
const handleStopAll = async () => {
if (!window.api) return
const criticalServices = ['nginx', 'mysql']
const criticalServices = ['nginx']
const phpToStop = Object.keys(status).filter(s => s.startsWith('php:') && (status[s] === 'running' || status[s] === 'starting'))
const mysqlToStop = Object.keys(status).filter(s => s.startsWith('mysql:') && (status[s] === 'running' || status[s] === 'starting'))
await Promise.all([
...criticalServices.filter(s => status[s] === 'running' || status[s] === 'starting').map(s => window.api?.stopService(s)),
...phpToStop.map(s => window.api?.stopService(s))
...phpToStop.map(s => window.api?.stopService(s)),
...mysqlToStop.map(s => window.api?.stopService(s)),
window.api?.stopService('mysql') // Also stop legacy if any
])
fetchStatus()
@@ -610,6 +617,20 @@ function App(): JSX.Element {
secondary={v.status === 'downloading' ? `İndiriliyor... %${Math.round(v.progress)}` : v.status === 'installed' ? 'Kurulu' : 'İndirilebilir'}
sx={{ flexGrow: 1 }}
/>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{v.status === 'installed' && (
<TextField
label="Port"
value={settings.mysqlPorts?.[v.id] || '3306'}
onChange={(e) => {
const newPorts = { ...(settings.mysqlPorts || {}), [v.id]: e.target.value }
setSettings({ ...settings, mysqlPorts: newPorts })
}}
variant="outlined"
size="small"
sx={{ width: 80 }}
/>
)}
<Box sx={{ minWidth: 120, textAlign: 'right' }}>
{v.status === 'available' && (
<Button variant="outlined" size="small" startIcon={<DownloadIcon />} onClick={() => handleDownloadMySql(v.id)}>
@@ -625,6 +646,7 @@ function App(): JSX.Element {
<Chip label="HAZIR" color="success" variant="outlined" size="small" />
)}
</Box>
</Box>
</ListItem>
))}
</List>
@@ -715,64 +737,99 @@ function App(): JSX.Element {
<Typography variant="h5" gutterBottom sx={{ mb: 3, fontWeight: 'medium' }}>
Hızlı Bakış
</Typography>
<Paper elevation={4} sx={{ mb: 4, borderRadius: 3, overflow: 'hidden', bgcolor: 'rgba(255, 255, 255, 0.03)', backdropFilter: 'blur(10px)', border: '1px solid rgba(255, 255, 255, 0.05)' }}>
<List dense>
<ListItem sx={{ py: 1.5 }}>
<ListItemIcon><WebIcon color="primary" /></ListItemIcon>
<ListItemText primary="Nginx Web Sunucusu" secondary={`Port: ${settings.nginxPort}`} />
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<IconButton size="small" onClick={() => handleOpenLogs('nginx')} title="Logları Görüntüle">
<Grid container spacing={3} sx={{ mb: 4 }}>
{/* Nginx Box */}
<Grid item xs={12} sm={6} md={4}>
<Paper elevation={4} sx={{
p: 3, borderRadius: 3,
bgcolor: 'rgba(255, 255, 255, 0.03)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.05)',
transition: 'transform 0.2s',
'&:hover': { transform: 'scale(1.02)' }
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<WebIcon color="primary" sx={{ fontSize: 40 }} />
{getStatusChip(status.nginx)}
</Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Nginx</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.nginxPort}</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<IconButton onClick={() => handleOpenLogs('nginx')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
<TerminalIcon fontSize="small" />
</IconButton>
{getStatusChip(status.nginx)}
<Switch
checked={status.nginx === 'running' || status.nginx === 'starting'}
onChange={() => handleToggleService('nginx')}
disabled={status.nginx === 'starting'}
/>
</Box>
</ListItem>
</Paper>
</Grid>
{/* PHP Boxes */}
{installedPhpVersions.map(v => (
<Fragment key={v.id}>
<Divider variant="inset" />
<ListItem sx={{ py: 1.5 }}>
<ListItemIcon><PhpIcon color="primary" /></ListItemIcon>
<ListItemText
primary={`PHP ${v.version} FastCGI`}
secondary={`Port: ${v.port || '?'}`}
/>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<IconButton size="small" onClick={() => handleOpenLogs(`php:${v.version}`)} title="Logları Görüntüle">
<Grid item xs={12} sm={6} md={4} key={v.id}>
<Paper elevation={4} sx={{
p: 3, borderRadius: 3,
bgcolor: 'rgba(255, 255, 255, 0.03)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.05)',
transition: 'transform 0.2s',
'&:hover': { transform: 'scale(1.02)' }
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<PhpIcon color="primary" sx={{ fontSize: 40 }} />
{getStatusChip(status[`php:${v.version}`] || 'stopped')}
</Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>PHP {v.version}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {v.port || '?'}</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<IconButton onClick={() => handleOpenLogs(`php:${v.version}`)} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
<TerminalIcon fontSize="small" />
</IconButton>
{getStatusChip(status[`php:${v.version}`] || 'stopped')}
<Switch
checked={(status[`php:${v.version}`] || 'stopped') === 'running' || status[`php:${v.version}`] === 'starting'}
onChange={() => handleToggleService(`php:${v.version}`)}
disabled={status[`php:${v.version}`] === 'starting'}
/>
</Box>
</ListItem>
</Fragment>
</Paper>
</Grid>
))}
<Divider variant="inset" />
<ListItem sx={{ py: 1.5 }}>
<ListItemIcon><DbIcon color="primary" /></ListItemIcon>
<ListItemText primary="MySQL / MariaDB" secondary={`Port: ${settings.mysqlPort}`} />
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<IconButton size="small" onClick={() => handleOpenLogs('mysql')} title="Logları Görüntüle">
{/* MySQL Boxes */}
{mySqlVersions.filter(v => v.status === 'installed').map(v => (
<Grid item xs={12} sm={6} md={4} key={v.id}>
<Paper elevation={4} sx={{
p: 3, borderRadius: 3,
bgcolor: 'rgba(255, 255, 255, 0.03)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.05)',
transition: 'transform 0.2s',
'&:hover': { transform: 'scale(1.02)' }
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<DbIcon color="primary" sx={{ fontSize: 40 }} />
{getStatusChip(status[`mysql:${v.version}`] || 'stopped')}
</Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MySQL {v.version}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.mysqlPorts?.[v.id] || '3306'}</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<IconButton onClick={() => handleOpenLogs(`mysql:${v.version}`)} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
<TerminalIcon fontSize="small" />
</IconButton>
{getStatusChip(status.mysql)}
<Switch
checked={status.mysql === 'running' || status.mysql === 'starting'}
onChange={() => handleToggleService('mysql')}
disabled={status.mysql === 'starting'}
checked={(status[`mysql:${v.version}`] || 'stopped') === 'running' || status[`mysql:${v.version}`] === 'starting'}
onChange={() => handleToggleService(`mysql:${v.version}`)}
disabled={status[`mysql:${v.version}`] === 'starting'}
/>
</Box>
</ListItem>
</List>
</Paper>
</Grid>
))}
</Grid>
<Box sx={{ display: 'flex', gap: 2 }}>
<Button variant="contained" fullWidth startIcon={<StartIcon />} size="large" onClick={handleStartAll} sx={{ borderRadius: 2, padding: '12px', background: 'linear-gradient(45deg, #153E5E 30%, #4A7CA3 90%)', boxShadow: '0 3px 5px 2px rgba(21, 62, 94, .3)' }}>
+7 -2
View File
@@ -6,12 +6,17 @@
"settings": "Einstellungen",
"tools": "Werkzeuge",
"close": "Schließen",
"quit": "Beenden"
"quit": "Beenden",
"save": "Speichern",
"port": "Port"
},
"dashboard": {
"title": "Trunçgil Multi-PHP Server",
"status": "Serverstatus",
"services": "Dienste",
"active_version": "Aktive PHP-Version"
"active_version": "Aktive PHP-Version",
"quick_look": "Schnellansicht",
"start_all": "Alle starten",
"stop_all": "Alle stoppen"
}
}
+7 -2
View File
@@ -6,12 +6,17 @@
"settings": "Settings",
"tools": "Tools",
"close": "Close",
"quit": "Quit"
"quit": "Quit",
"save": "Save",
"port": "Port"
},
"dashboard": {
"title": "Trunçgil Multi-PHP Server",
"status": "Server Status",
"services": "Services",
"active_version": "Active PHP Version"
"active_version": "Active PHP Version",
"quick_look": "Quick Look",
"start_all": "Start All",
"stop_all": "Stop All"
}
}
+7 -2
View File
@@ -6,12 +6,17 @@
"settings": "Ayarlar",
"tools": "Araçlar",
"close": "Kapat",
"quit": "Çıkış"
"quit": "Çıkış",
"save": "Kaydet",
"port": "Port"
},
"dashboard": {
"title": "Trunçgil Multi-PHP Server",
"status": "Sunucu Durumu",
"services": "Servisler",
"active_version": "Aktif PHP Sürümü"
"active_version": "Aktif PHP Sürümü",
"quick_look": "Hızlı Bakış",
"start_all": "Tümünü Başlat",
"stop_all": "Tümünü Durdur"
}
}