feat: implement initial UI dashboard and process management services for multi-PHP environment

This commit is contained in:
Ümit Tunç
2026-03-30 07:50:09 +03:00
parent 98295e38ef
commit 2766b0ed39
3 changed files with 127 additions and 44 deletions
+72 -27
View File
@@ -28,29 +28,36 @@ function findExecutable(dir: string, name: string): string | null {
return null
}
async function reloadNginx(): Promise<{ success: boolean; message: string }> {
try {
const binDir = path.join(configService.getBasePath(), 'bin')
const nginxDir = path.join(binDir, 'nginx')
const nginxVersions = fs.readdirSync(nginxDir).filter(f => fs.statSync(path.join(nginxDir, f)).isDirectory())
const activeVersion = nginxVersions[0]
if (!activeVersion) throw new Error('Nginx sürümü bulunamadı.')
const nginxPrefix = path.join(nginxDir, activeVersion)
const binPath = path.join(nginxPrefix, 'nginx.exe')
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
const result = await processManager.runOnce(binPath, ['-p', nginxPrefix, '-c', configPath, '-s', 'reload'], 'nginx')
if (result.success) {
console.log('Nginx reload successful')
return { success: true, message: 'Nginx reloaded' }
} else {
console.error('Nginx reload failed:', result.output)
return { success: false, message: result.output || 'Nginx reload failed' }
}
} catch (error: any) {
return { success: false, message: `Reload hatası: ${error.message}` }
}
}
export function registerIpcHandlers(): void {
// Service management
ipcMain.handle('services:reload', async (_event, serviceName: string) => {
if (serviceName !== 'nginx') return { success: false, message: 'Yalnızca Nginx için reload desteklenmektedir.' }
try {
// Re-generate config
const binDir = path.join(configService.getBasePath(), 'bin')
const nginxDir = path.join(binDir, 'nginx')
const nginxVersions = fs.readdirSync(nginxDir).filter(f => fs.statSync(path.join(nginxDir, f)).isDirectory())
const activeVersion = nginxVersions[0] // Simplify: use first installed
if (!activeVersion) throw new Error('Nginx sürümü bulunamadı.')
const nginxPrefix = path.join(nginxDir, activeVersion)
const binPath = path.join(nginxPrefix, 'nginx.exe')
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
// For reload, we need the same prefix and config
const success = await processManager.runOnce(binPath, ['-p', nginxPrefix, '-c', configPath, '-s', 'reload'], 'nginx')
return { success, message: success ? 'Nginx yapılandırması yeniden yüklendi.' : 'Nginx reload hatası (Nginx çalışmıyor olabilir).' }
} catch (error: any) {
return { success: false, message: `Reload hatası: ${error.message}` }
}
return await reloadNginx()
})
ipcMain.handle('services:start', async (_event, serviceName: string) => {
@@ -116,8 +123,9 @@ export function registerIpcHandlers(): void {
// Test config first with prefix
const testResult = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx')
if (!testResult) {
return { success: false, message: 'Nginx konfigürasyon hatası! Log sekmesini kontrol edin.' }
if (!testResult.success) {
console.error('Nginx config test failed before reload:', testResult.output)
return { success: false, message: `Nginx config error: ${testResult.output}` }
}
const success = await processManager.startService('nginx', binPath, ['-p', nginxPrefix, '-c', configPath], false)
@@ -416,8 +424,7 @@ export function registerIpcHandlers(): void {
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
fs.writeFileSync(configPath, content)
// Reload Nginx after save
await ipcMain.emit('services:reload', null, 'nginx')
return { success: true }
return await reloadNginx()
})
ipcMain.handle('nginx:config:test', async () => {
@@ -431,8 +438,8 @@ export function registerIpcHandlers(): void {
const binPath = path.join(nginxPrefix, 'nginx.exe')
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
const success = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx')
return { success, message: success ? 'Configuration test successful' : 'Configuration test failed' }
const result = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx')
return { success: result.success, message: result.success ? 'Configuration test successful' : result.output }
} catch (error: any) {
return { success: false, message: error.message }
}
@@ -447,12 +454,50 @@ export function registerIpcHandlers(): void {
return ''
})
ipcMain.handle('project:nginx:test', async (_event, host, content) => {
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`)
let originalContent = ''
if (fs.existsSync(configPath)) {
originalContent = fs.readFileSync(configPath, 'utf8')
}
try {
// 1. Save new content temporarily
fs.writeFileSync(configPath, content)
// 2. Run global test
const binDir = path.join(configService.getBasePath(), 'bin', 'nginx')
const nginxVersions = fs.readdirSync(binDir).filter(f => fs.statSync(path.join(binDir, f)).isDirectory())
const activeVersion = nginxVersions[0]
if (!activeVersion) throw new Error('Nginx sürümü bulunamadı.')
const nginxPrefix = path.join(binDir, activeVersion)
const binPath = path.join(nginxPrefix, 'nginx.exe')
const globalConfigPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
const result = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', globalConfigPath], 'nginx')
// 3. Restore original content regardless of test result
// (The user must click "Save" to keep it)
if (originalContent) {
fs.writeFileSync(configPath, originalContent)
} else {
fs.unlinkSync(configPath)
}
return { success: result.success, message: result.success ? 'Configuration test successful' : result.output }
} catch (error: any) {
// Restore on error too
if (originalContent) fs.writeFileSync(configPath, originalContent)
return { success: false, message: error.message }
}
})
ipcMain.handle('project:nginx:save', async (_event, host, content) => {
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`)
fs.writeFileSync(configPath, content)
// Reload Nginx after save
await ipcMain.emit('services:reload', null, 'nginx')
return { success: true }
return await reloadNginx()
})
// phpMyAdmin
+13 -13
View File
@@ -64,35 +64,35 @@ export class ProcessManager {
return this.logs.get(name) || []
}
async runOnce(binPath: string, args: string[], name?: string): Promise<boolean> {
async runOnce(binPath: string, args: string[], name?: string): Promise<{ success: boolean; output: string }> {
return new Promise((resolve) => {
const child = spawn(binPath, args, { stdio: 'pipe', shell: false, windowsHide: true })
let output = ''
child.stdout?.on('data', (data) => {
const out = data.toString()
output += out
console.log(`[INIT:${name || '?'}] ${out}`)
if (name) this.addLog(name, out)
})
child.on('error', (err) => {
console.error(`[INIT ERROR:${name || '?'}] ${err.message}`)
if (name) this.addLog(name, `RunOnce Error: ${err.message}`)
resolve(false)
})
child.stderr?.on('data', (data) => {
const err = data.toString()
output += err
console.error(`[INIT ERROR:${name || '?'}] ${err}`)
if (name) this.addLog(name, err)
})
child.on('close', (code) => {
resolve(code === 0)
})
child.on('error', (err) => {
console.error('RunOnce error:', err)
if (name) this.addLog(name, `RunOnce Error: ${err.message}`)
resolve(false)
const msg = `RunOnce Error: ${err.message}`
output += msg
console.error(`[INIT ERROR:${name || '?'}] ${msg}`)
if (name) this.addLog(name, msg)
resolve({ success: false, output })
})
child.on('close', (code) => {
resolve({ success: code === 0, output: output.trim() })
})
})
}
+42 -4
View File
@@ -12,12 +12,13 @@ import {
AppBar,
Toolbar,
IconButton,
Divider,
GlobalStyles,
Tooltip,
Container,
Paper,
Chip,
Switch,
Divider,
Button,
Snackbar,
Alert,
@@ -37,8 +38,7 @@ import {
CircularProgress,
Stack,
Grid,
Avatar,
GlobalStyles
Avatar
} from '@mui/material'
import {
Dashboard as DashboardIcon,
@@ -484,7 +484,10 @@ function App(): JSX.Element {
text: result.message,
icon: result.success ? 'success' : 'error',
background: '#1e1e1e',
color: '#fff'
color: '#fff',
customClass: {
htmlContainer: 'nginx-error-container'
}
})
}
}
@@ -569,6 +572,16 @@ function App(): JSX.Element {
<GlobalStyles styles={{
'.swal2-container': {
zIndex: '9999 !important'
},
'.nginx-error-container': {
textAlign: 'left !important',
fontFamily: 'monospace !important',
fontSize: '0.8rem !important',
whiteSpace: 'pre-wrap !important',
backgroundColor: 'rgba(0,0,0,0.2) !important',
padding: '10px !important',
borderRadius: '5px !important',
border: '1px solid rgba(255,255,255,0.1) !important'
}
}} />
<Box sx={{ display: 'flex', minHeight: '100vh', backgroundImage: 'radial-gradient(circle at 50% 0%, #153E5E 0%, #121212 100%)', bgcolor: 'transparent' }}>
@@ -1465,6 +1478,31 @@ function App(): JSX.Element {
/>
</DialogContent>
<DialogActions sx={{ p: 2, gap: 1 }}>
<Button
onClick={async () => {
if (window.api && window.api.invoke) {
// We need to test the global config because this project is part of it
// But we should ideally save the project config first (to the temp or real file)
// For now, testing global config is the most reliable
const result = await window.api.invoke('nginx:config:test')
Swal.fire({
title: result.success ? 'Başarılı' : 'Hata',
text: result.message,
icon: result.success ? 'success' : 'error',
background: '#1e1e1e',
color: '#fff',
customClass: {
htmlContainer: 'nginx-error-container'
}
})
}
}}
variant="outlined"
startIcon={<RefreshIcon />}
sx={{ color: '#fff', borderColor: 'rgba(255,255,255,0.3)' }}
>
Yapılandırmayı Test Et
</Button>
<Button onClick={() => setIsProjectNginxConfigOpen(false)} sx={{ color: 'rgba(255,255,255,0.6)' }}>
İptal
</Button>