feat: implement ProcessManager service with i18n support and cross-platform process lifecycle management
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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ü",
|
||||
|
||||
Reference in New Issue
Block a user