feat: implement process management service and initial dashboard UI for MultiPHP

This commit is contained in:
Ümit Tunç
2026-03-28 21:21:06 +03:00
parent f5d0c42888
commit 4de00919f0
4 changed files with 81 additions and 51 deletions
+13 -6
View File
@@ -95,17 +95,24 @@ export function registerIpcHandlers(): void {
return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx devreye alınamadı (Hata loglarını kontrol edin).' }
}
if (serviceName === 'php') {
const binPath = findExecutable(binDir, 'php-cgi.exe')
if (!binPath) return { success: false, message: 'PHP executable bulunamadı. Lütfen en az bir sürüm indirin.' }
if (serviceName.startsWith('php')) {
const versionMatch = serviceName.match(/^php:(.+)$/)
const phpVersion = versionMatch ? versionMatch[1] : null
const phpId = phpVersion ? `php-${phpVersion}` : null
const phpDir = phpId ? path.join(binDir, phpId) : binDir
const binPath = findExecutable(phpDir, 'php-cgi.exe')
if (!binPath) return { success: false, message: `${phpVersion || ''} PHP executable bulunamadı. Lütfen indirin.` }
const phpPort = phpVersion ? portMappingService.getPhpPort(phpVersion) : settings.phpPort
const extDir = path.join(path.dirname(binPath), 'ext')
const configPath = await configService.generateConfig('php.ini.template', 'php.ini', {
const configPath = await configService.generateConfig('php.ini.template', `php_${phpVersion || 'default'}.ini`, {
EXT_DIR: extDir
})
const success = await processManager.startService('php', binPath, ['-b', `127.0.0.1:${settings.phpPort}`, '-c', configPath], false)
return { success, message: success ? 'PHP başlatıldı.' : 'PHP devreye alınamadı (Hata loglarını kontrol edin).' }
const success = await processManager.startService(serviceName, binPath, ['-b', `127.0.0.1:${phpPort}`, '-c', configPath], false)
return { success, message: success ? `${phpVersion || ''} PHP başlatıldı.` : `${phpVersion || ''} PHP devreye alınamadı.` }
}
if (serviceName === 'mysql') {
+7 -1
View File
@@ -2,6 +2,7 @@ import fs from 'fs'
import path from 'path'
import { app } from 'electron'
import axios from 'axios'
import { portMappingService } from './PortMappingService'
export interface PhpVersion {
id: string
@@ -9,6 +10,7 @@ export interface PhpVersion {
url: string
status: 'available' | 'downloading' | 'installed'
progress: number
port?: number
}
export class PhpManagerService {
@@ -136,7 +138,11 @@ export class PhpManagerService {
await this.fetchAvailableVersions()
}
this.refreshInstalledStatus()
return this.versions
// Add ports for all versions
return this.versions.map(v => ({
...v,
port: portMappingService.getPhpPort(v.version)
}))
}
updateProgress(id: string, progress: number, status: PhpVersion['status'] = 'downloading') {
+7 -4
View File
@@ -170,11 +170,14 @@ export class ProcessManager {
}
getStatuses(): Record<string, string> {
return {
nginx: this.isServiceRunning('nginx') ? 'running' : 'stopped',
php: this.isServiceRunning('php') ? 'running' : 'stopped',
mysql: this.isServiceRunning('mysql') ? 'running' : 'stopped'
const statuses: Record<string, string> = {}
for (const name of Array.from(this.processes.keys())) {
statuses[name] = 'running'
}
// Ensure defaults are present if stopped
if (!statuses['nginx']) statuses['nginx'] = 'stopped'
if (!statuses['mysql']) statuses['mysql'] = 'stopped'
return statuses
}
private async checkErrorLogs(name: string) {
+54 -40
View File
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'
import React, { useState, useEffect, Fragment } from 'react'
import {
Box,
Typography,
@@ -243,11 +243,11 @@ function App(): JSX.Element {
const interval = setInterval(() => {
fetchStatus()
fetchProjects()
fetchPhpVersions()
fetchMySqlVersions()
fetchNginxVersions()
}, 5000)
// fetchProjects() // Reduced frequency for these
// fetchPhpVersions()
// fetchMySqlVersions()
// fetchNginxVersions()
}, 3000)
let logInterval: NodeJS.Timeout | null = null
if (logDialogOpen && selectedService) {
@@ -284,15 +284,15 @@ function App(): JSX.Element {
}
}
const handleToggleService = async (service: keyof ServiceStatus) => {
const handleToggleService = async (service: string) => {
if (!window.api) return
const isStarting = status[service] === 'stopped'
const isStarting = (status[service] || 'stopped') === 'stopped'
if (isStarting) {
setStatus(prev => ({ ...prev, [service]: 'starting' }))
}
const result = status[service] === 'running'
const result = (status[service] || 'stopped') === 'running'
? await window.api.stopService(service)
: await window.api.startService(service)
@@ -328,29 +328,36 @@ function App(): JSX.Element {
const handleStartAll = async () => {
if (!window.api) return
const services: (keyof ServiceStatus)[] = ['nginx', 'php', 'mysql']
for (const s of services) {
if (status[s] === 'stopped') {
setStatus(prev => ({ ...prev, [s]: 'starting' }))
}
const criticalServices = ['nginx', 'mysql']
for (const s of criticalServices) {
if (status[s] === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
}
for (const s of services) {
if (status[s] !== 'running') {
await window.api.startService(s)
}
// 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' }))
}
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))
])
fetchStatus()
setNotification({ open: true, message: 'Tüm servisler başlatıldı.', severity: 'success' })
}
const handleStopAll = async () => {
if (!window.api) return
const services: (keyof ServiceStatus)[] = ['nginx', 'php', 'mysql']
for (const s of services) {
if (status[s] === 'running') {
await window.api.stopService(s)
}
}
const criticalServices = ['nginx', 'mysql']
const phpToStop = Object.keys(status).filter(s => s.startsWith('php:') && (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))
])
fetchStatus()
setNotification({ open: true, message: 'Tüm servisler durduruldu.', severity: 'success' })
}
@@ -753,22 +760,29 @@ function App(): JSX.Element {
/>
</Box>
</ListItem>
<Divider variant="inset" />
<ListItem sx={{ py: 1.5 }}>
<ListItemIcon><PhpIcon color="primary" /></ListItemIcon>
<ListItemText primary="PHP FastCGI" secondary={`Port: ${settings.phpPort}`} />
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<IconButton size="small" onClick={() => handleOpenLogs('php')} title="Logları Görüntüle">
<TerminalIcon fontSize="small" />
</IconButton>
{getStatusChip(status.php)}
<Switch
checked={status.php === 'running' || status.php === 'starting'}
onChange={() => handleToggleService('php')}
disabled={status.php === '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 || '?'}`}
/>
<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>