feat: implement IPC service management handlers for Nginx, PHP, and MySQL and add multi-language support.
This commit is contained in:
+23
-12
@@ -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.' }
|
||||
|
||||
@@ -10,6 +10,7 @@ export class ConfigService {
|
||||
nginxPort: '80',
|
||||
phpPort: '9001',
|
||||
mysqlPort: '3306',
|
||||
mysqlPorts: {} as Record<string, string>,
|
||||
language: 'tr'
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
+127
-70
@@ -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,20 +617,35 @@ function App(): JSX.Element {
|
||||
secondary={v.status === 'downloading' ? `İndiriliyor... %${Math.round(v.progress)}` : v.status === 'installed' ? 'Kurulu' : 'İndirilebilir'}
|
||||
sx={{ flexGrow: 1 }}
|
||||
/>
|
||||
<Box sx={{ minWidth: 120, textAlign: 'right' }}>
|
||||
{v.status === 'available' && (
|
||||
<Button variant="outlined" size="small" startIcon={<DownloadIcon />} onClick={() => handleDownloadMySql(v.id)}>
|
||||
İNDİR
|
||||
</Button>
|
||||
)}
|
||||
{v.status === 'downloading' && (
|
||||
<Box sx={{ width: '100%', mt: 1 }}>
|
||||
<LinearProgress variant="determinate" value={v.progress} sx={{ height: 8, borderRadius: 5 }} />
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
{v.status === 'installed' && (
|
||||
<Chip label="HAZIR" color="success" variant="outlined" size="small" />
|
||||
<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)}>
|
||||
İNDİR
|
||||
</Button>
|
||||
)}
|
||||
{v.status === 'downloading' && (
|
||||
<Box sx={{ width: '100%', mt: 1 }}>
|
||||
<LinearProgress variant="determinate" value={v.progress} sx={{ height: 8, borderRadius: 5 }} />
|
||||
</Box>
|
||||
)}
|
||||
{v.status === 'installed' && (
|
||||
<Chip label="HAZIR" color="success" variant="outlined" size="small" />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</ListItem>
|
||||
))}
|
||||
@@ -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>
|
||||
{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 || '?'}`}
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
{/* PHP Boxes */}
|
||||
{installedPhpVersions.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 }}>
|
||||
<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>
|
||||
<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 sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<IconButton size="small" onClick={() => handleOpenLogs(`php:${v.version}`)} title="Logları Görüntüle">
|
||||
<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>
|
||||
))}
|
||||
<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">
|
||||
<TerminalIcon fontSize="small" />
|
||||
</IconButton>
|
||||
{getStatusChip(status.mysql)}
|
||||
<Switch
|
||||
checked={status.mysql === 'running' || status.mysql === 'starting'}
|
||||
onChange={() => handleToggleService('mysql')}
|
||||
disabled={status.mysql === 'starting'}
|
||||
/>
|
||||
</Box>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
{/* 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>
|
||||
<Switch
|
||||
checked={(status[`mysql:${v.version}`] || 'stopped') === 'running' || status[`mysql:${v.version}`] === 'starting'}
|
||||
onChange={() => handleToggleService(`mysql:${v.version}`)}
|
||||
disabled={status[`mysql:${v.version}`] === 'starting'}
|
||||
/>
|
||||
</Box>
|
||||
</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)' }}>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user