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 { app } from 'electron'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { configService } from './ConfigService'
|
import { configService } from './ConfigService'
|
||||||
|
import { t } from '../utils/i18n'
|
||||||
|
|
||||||
export class ProcessManager {
|
export class ProcessManager {
|
||||||
private processes: Map<string, ChildProcess> = new Map()
|
private processes: Map<string, ChildProcess> = new Map()
|
||||||
@@ -24,7 +25,7 @@ export class ProcessManager {
|
|||||||
// Initialize with default logs
|
// Initialize with default logs
|
||||||
['nginx', 'apache', 'php', 'mariadb'].forEach(s => {
|
['nginx', 'apache', 'php', 'mariadb'].forEach(s => {
|
||||||
if (!this.logs.has(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) {
|
} catch (e) {
|
||||||
@@ -103,12 +104,12 @@ export class ProcessManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(binPath)) {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.addLog(name, `Starting service with: ${binPath} ${args.join(' ')}`)
|
this.addLog(name, `${t('common.starting')}... (${binPath})`)
|
||||||
|
|
||||||
const serviceEnv = { ...process.env }
|
const serviceEnv = { ...process.env }
|
||||||
const binDirPath = path.dirname(binPath)
|
const binDirPath = path.dirname(binPath)
|
||||||
@@ -127,7 +128,7 @@ export class ProcessManager {
|
|||||||
})
|
})
|
||||||
|
|
||||||
console.log(`[PROCESS] Started ${name} with PID ${child.pid}`)
|
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.stdout?.on('data', (data) => this.addLog(name, data.toString()))
|
||||||
child.stderr?.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) => {
|
child.on('exit', (code) => {
|
||||||
this.addLog(name, `Exited with code ${code}`)
|
this.addLog(name, `${t('common.stopped')} (Code: ${code})`)
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
this.checkErrorLogs(name)
|
this.checkErrorLogs(name)
|
||||||
}
|
}
|
||||||
@@ -157,7 +158,7 @@ export class ProcessManager {
|
|||||||
async stopService(name: string): Promise<boolean> {
|
async stopService(name: string): Promise<boolean> {
|
||||||
const child = this.processes.get(name)
|
const child = this.processes.get(name)
|
||||||
if (child) {
|
if (child) {
|
||||||
this.addLog(name, 'Stopping service...')
|
this.addLog(name, `${t('common.stopping')}...`)
|
||||||
try {
|
try {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
child.kill()
|
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') {
|
if (s === 'starting') {
|
||||||
return <Chip label={t('common.starting')} color="warning" size="small" sx={{ fontWeight: 'bold' }} icon={<CircularProgress size={14} color="inherit" />} />
|
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'
|
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 () => {
|
const handleAddProject = async () => {
|
||||||
|
|||||||
@@ -24,6 +24,12 @@
|
|||||||
"search": "Suchen",
|
"search": "Suchen",
|
||||||
"loading": "Laden...",
|
"loading": "Laden...",
|
||||||
"starting": "Wird gestartet",
|
"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?",
|
"are_you_sure": "Sind Sie sicher?",
|
||||||
"yes_delete": "Ja, Löschen",
|
"yes_delete": "Ja, Löschen",
|
||||||
"go_to_fix": "Zur Korrektur gehen",
|
"go_to_fix": "Zur Korrektur gehen",
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
"search": "Search",
|
"search": "Search",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"starting": "Starting",
|
"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?",
|
"are_you_sure": "Are you sure?",
|
||||||
"yes_delete": "Yes, delete!",
|
"yes_delete": "Yes, delete!",
|
||||||
"go_to_fix": "Go to Fix",
|
"go_to_fix": "Go to Fix",
|
||||||
@@ -44,7 +49,7 @@
|
|||||||
"reset_btn": "Yes, Reset"
|
"reset_btn": "Yes, Reset"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Trunçgil Multi-PHP Server",
|
"title": "Dashboard",
|
||||||
"status": "Server Status",
|
"status": "Server Status",
|
||||||
"services": "Services",
|
"services": "Services",
|
||||||
"active_version": "Active PHP Version",
|
"active_version": "Active PHP Version",
|
||||||
|
|||||||
@@ -24,6 +24,12 @@
|
|||||||
"search": "Ara",
|
"search": "Ara",
|
||||||
"loading": "Yükleniyor...",
|
"loading": "Yükleniyor...",
|
||||||
"starting": "Başlatılıyor",
|
"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?",
|
"are_you_sure": "Emin misiniz?",
|
||||||
"yes_delete": "Evet, Sil",
|
"yes_delete": "Evet, Sil",
|
||||||
"go_to_fix": "Sorunu Düzeltmeye Git",
|
"go_to_fix": "Sorunu Düzeltmeye Git",
|
||||||
@@ -43,7 +49,7 @@
|
|||||||
"reset_btn": "Evet, Sıfırla"
|
"reset_btn": "Evet, Sıfırla"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Trunçgil Multi-PHP Server",
|
"title": "Özet",
|
||||||
"status": "Sunucu Durumu",
|
"status": "Sunucu Durumu",
|
||||||
"services": "Servisler",
|
"services": "Servisler",
|
||||||
"active_version": "Aktif PHP Sürümü",
|
"active_version": "Aktif PHP Sürümü",
|
||||||
|
|||||||
Reference in New Issue
Block a user