feat: implement initial UI dashboard and process management services for multi-PHP environment
This commit is contained in:
+72
-27
@@ -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
|
||||
|
||||
@@ -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() })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user