feat: implement ProcessManager service with i18n support and cross-platform process lifecycle management

This commit is contained in:
Ümit Tunç
2026-04-02 00:11:06 +03:00
parent a8c3b4a824
commit 8c18c25ae9
6 changed files with 95 additions and 9 deletions
+7 -6
View File
@@ -3,6 +3,7 @@ import path from 'path'
import { app } from 'electron'
import fs from 'fs'
import { configService } from './ConfigService'
import { t } from '../utils/i18n'
export class ProcessManager {
private processes: Map<string, ChildProcess> = new Map()
@@ -24,7 +25,7 @@ export class ProcessManager {
// Initialize with default logs
['nginx', 'apache', 'php', 'mariadb'].forEach(s => {
if (!this.logs.has(s)) {
this.logs.set(s, [`[${new Date().toLocaleTimeString()}] Sistem hazır. İşlem bekleniyor...`])
this.logs.set(s, [`[${new Date().toLocaleTimeString()}] ${t('common.ready')} ${t('common.waiting_process')}`])
}
})
} catch (e) {
@@ -103,12 +104,12 @@ export class ProcessManager {
}
if (!fs.existsSync(binPath)) {
this.addLog(name, `Executable not found: ${binPath}`)
this.addLog(name, `${t('common.error')}: ${t('common.executable_not_found')} (${binPath})`)
return false
}
try {
this.addLog(name, `Starting service with: ${binPath} ${args.join(' ')}`)
this.addLog(name, `${t('common.starting')}... (${binPath})`)
const serviceEnv = { ...process.env }
const binDirPath = path.dirname(binPath)
@@ -127,7 +128,7 @@ export class ProcessManager {
})
console.log(`[PROCESS] Started ${name} with PID ${child.pid}`)
this.addLog(name, `Process started (PID: ${child.pid})`)
this.addLog(name, `${t('common.running')} (PID: ${child.pid})`)
child.stdout?.on('data', (data) => this.addLog(name, data.toString()))
child.stderr?.on('data', (data) => this.addLog(name, data.toString()))
@@ -138,7 +139,7 @@ export class ProcessManager {
})
child.on('exit', (code) => {
this.addLog(name, `Exited with code ${code}`)
this.addLog(name, `${t('common.stopped')} (Code: ${code})`)
if (code !== 0) {
this.checkErrorLogs(name)
}
@@ -157,7 +158,7 @@ export class ProcessManager {
async stopService(name: string): Promise<boolean> {
const child = this.processes.get(name)
if (child) {
this.addLog(name, 'Stopping service...')
this.addLog(name, `${t('common.stopping')}...`)
try {
if (process.platform === 'win32') {
child.kill()
+65
View File
@@ -0,0 +1,65 @@
import fs from 'fs'
import path from 'path'
import { app } from 'electron'
import { configService } from '../services/ConfigService'
class MainI18n {
private locales: Record<string, any> = {}
constructor() {
this.loadLocales()
}
private loadLocales() {
try {
// In development, locales are in src/renderer/src/locales
// In production, they should be in a resources folder
const isDev = !app.isPackaged
let localesPath = ''
if (isDev) {
localesPath = path.join(app.getAppPath(), 'src/renderer/src/locales')
} else {
// Adjust for production path
localesPath = path.join(process.resourcesPath, 'locales')
}
if (fs.existsSync(localesPath)) {
const files = fs.readdirSync(localesPath)
files.forEach(file => {
if (file.endsWith('.json')) {
const lang = file.replace('.json', '')
const content = fs.readFileSync(path.join(localesPath, file), 'utf-8')
this.locales[lang] = JSON.parse(content)
}
})
}
} catch (e) {
console.error('Failed to load main locales:', e)
}
}
t(key: string, variables?: Record<string, string>): string {
const lang = configService.getSettings().language || 'tr'
const parts = key.split('.')
let result = this.locales[lang] || this.locales['en'] || {}
for (const part of parts) {
result = result?.[part]
if (!result) break
}
if (typeof result !== 'string') return key
if (variables) {
Object.entries(variables).forEach(([k, v]) => {
result = (result as string).replace(`{{${k}}}`, v)
})
}
return result
}
}
export const i18nMain = new MainI18n()
export const t = (key: string, variables?: Record<string, string>) => i18nMain.t(key, variables)
+4 -1
View File
@@ -463,8 +463,11 @@ function App(): JSX.Element {
if (s === 'starting') {
return <Chip label={t('common.starting')} color="warning" size="small" sx={{ fontWeight: 'bold' }} icon={<CircularProgress size={14} color="inherit" />} />
}
if (s === 'stopping') {
return <Chip label={t('common.stopping')} color="warning" size="small" sx={{ fontWeight: 'bold' }} icon={<CircularProgress size={14} color="inherit" />} />
}
const color = s === 'running' ? 'success' : s === 'stopped' ? 'error' : 'warning'
return <Chip label={s.toUpperCase()} color={color} size="small" sx={{ fontWeight: 'bold' }} />
return <Chip label={t(`common.${s}`).toUpperCase()} color={color} size="small" sx={{ fontWeight: 'bold' }} />
}
const handleAddProject = async () => {
+6
View File
@@ -24,6 +24,12 @@
"search": "Suchen",
"loading": "Laden...",
"starting": "Wird gestartet",
"running": "Läuft",
"stopped": "Gestoppt",
"stopping": "Wird gestoppt",
"ready": "Bereit.",
"waiting_process": "Warte auf Prozess...",
"executable_not_found": "Ausführbare Datei nicht gefunden",
"are_you_sure": "Sind Sie sicher?",
"yes_delete": "Ja, Löschen",
"go_to_fix": "Zur Korrektur gehen",
+6 -1
View File
@@ -24,6 +24,11 @@
"search": "Search",
"loading": "Loading...",
"starting": "Starting",
"running": "Running",
"stopped": "Stopped",
"ready": "Ready.",
"waiting_process": "Waiting for process...",
"executable_not_found": "Executable not found",
"are_you_sure": "Are you sure?",
"yes_delete": "Yes, delete!",
"go_to_fix": "Go to Fix",
@@ -44,7 +49,7 @@
"reset_btn": "Yes, Reset"
},
"dashboard": {
"title": "Trunçgil Multi-PHP Server",
"title": "Dashboard",
"status": "Server Status",
"services": "Services",
"active_version": "Active PHP Version",
+7 -1
View File
@@ -24,6 +24,12 @@
"search": "Ara",
"loading": "Yükleniyor...",
"starting": "Başlatılıyor",
"running": "Çalışıyor",
"stopped": "Durduruldu",
"stopping": "Durduruluyor",
"ready": "Hazır.",
"waiting_process": "İşlem bekleniyor...",
"executable_not_found": "Dosya bulunamadı",
"are_you_sure": "Emin misiniz?",
"yes_delete": "Evet, Sil",
"go_to_fix": "Sorunu Düzeltmeye Git",
@@ -43,7 +49,7 @@
"reset_btn": "Evet, Sıfırla"
},
"dashboard": {
"title": "Trunçgil Multi-PHP Server",
"title": "Özet",
"status": "Sunucu Durumu",
"services": "Servisler",
"active_version": "Aktif PHP Sürümü",