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
|
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 {
|
export function registerIpcHandlers(): void {
|
||||||
// Service management
|
// Service management
|
||||||
ipcMain.handle('services:reload', async (_event, serviceName: string) => {
|
ipcMain.handle('services:reload', async (_event, serviceName: string) => {
|
||||||
if (serviceName !== 'nginx') return { success: false, message: 'Yalnızca Nginx için reload desteklenmektedir.' }
|
if (serviceName !== 'nginx') return { success: false, message: 'Yalnızca Nginx için reload desteklenmektedir.' }
|
||||||
|
return await reloadNginx()
|
||||||
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}` }
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('services:start', async (_event, serviceName: string) => {
|
ipcMain.handle('services:start', async (_event, serviceName: string) => {
|
||||||
@@ -116,8 +123,9 @@ export function registerIpcHandlers(): void {
|
|||||||
|
|
||||||
// Test config first with prefix
|
// Test config first with prefix
|
||||||
const testResult = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx')
|
const testResult = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx')
|
||||||
if (!testResult) {
|
if (!testResult.success) {
|
||||||
return { success: false, message: 'Nginx konfigürasyon hatası! Log sekmesini kontrol edin.' }
|
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)
|
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')
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
|
||||||
fs.writeFileSync(configPath, content)
|
fs.writeFileSync(configPath, content)
|
||||||
// Reload Nginx after save
|
// Reload Nginx after save
|
||||||
await ipcMain.emit('services:reload', null, 'nginx')
|
return await reloadNginx()
|
||||||
return { success: true }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('nginx:config:test', async () => {
|
ipcMain.handle('nginx:config:test', async () => {
|
||||||
@@ -431,8 +438,8 @@ export function registerIpcHandlers(): void {
|
|||||||
const binPath = path.join(nginxPrefix, 'nginx.exe')
|
const binPath = path.join(nginxPrefix, 'nginx.exe')
|
||||||
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
|
||||||
|
|
||||||
const success = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx')
|
const result = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx')
|
||||||
return { success, message: success ? 'Configuration test successful' : 'Configuration test failed' }
|
return { success: result.success, message: result.success ? 'Configuration test successful' : result.output }
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return { success: false, message: error.message }
|
return { success: false, message: error.message }
|
||||||
}
|
}
|
||||||
@@ -447,12 +454,50 @@ export function registerIpcHandlers(): void {
|
|||||||
return ''
|
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) => {
|
ipcMain.handle('project:nginx:save', async (_event, host, content) => {
|
||||||
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`)
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`)
|
||||||
fs.writeFileSync(configPath, content)
|
fs.writeFileSync(configPath, content)
|
||||||
// Reload Nginx after save
|
// Reload Nginx after save
|
||||||
await ipcMain.emit('services:reload', null, 'nginx')
|
return await reloadNginx()
|
||||||
return { success: true }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// phpMyAdmin
|
// phpMyAdmin
|
||||||
|
|||||||
@@ -64,35 +64,35 @@ export class ProcessManager {
|
|||||||
return this.logs.get(name) || []
|
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) => {
|
return new Promise((resolve) => {
|
||||||
const child = spawn(binPath, args, { stdio: 'pipe', shell: false, windowsHide: true })
|
const child = spawn(binPath, args, { stdio: 'pipe', shell: false, windowsHide: true })
|
||||||
|
let output = ''
|
||||||
|
|
||||||
child.stdout?.on('data', (data) => {
|
child.stdout?.on('data', (data) => {
|
||||||
const out = data.toString()
|
const out = data.toString()
|
||||||
|
output += out
|
||||||
console.log(`[INIT:${name || '?'}] ${out}`)
|
console.log(`[INIT:${name || '?'}] ${out}`)
|
||||||
if (name) this.addLog(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) => {
|
child.stderr?.on('data', (data) => {
|
||||||
const err = data.toString()
|
const err = data.toString()
|
||||||
|
output += err
|
||||||
console.error(`[INIT ERROR:${name || '?'}] ${err}`)
|
console.error(`[INIT ERROR:${name || '?'}] ${err}`)
|
||||||
if (name) this.addLog(name, err)
|
if (name) this.addLog(name, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
child.on('close', (code) => {
|
|
||||||
resolve(code === 0)
|
|
||||||
})
|
|
||||||
child.on('error', (err) => {
|
child.on('error', (err) => {
|
||||||
console.error('RunOnce error:', err)
|
const msg = `RunOnce Error: ${err.message}`
|
||||||
if (name) this.addLog(name, `RunOnce Error: ${err.message}`)
|
output += msg
|
||||||
resolve(false)
|
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,
|
AppBar,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Divider,
|
||||||
|
GlobalStyles,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Container,
|
Container,
|
||||||
Paper,
|
Paper,
|
||||||
Chip,
|
Chip,
|
||||||
Switch,
|
Switch,
|
||||||
Divider,
|
|
||||||
Button,
|
Button,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
Alert,
|
Alert,
|
||||||
@@ -37,8 +38,7 @@ import {
|
|||||||
CircularProgress,
|
CircularProgress,
|
||||||
Stack,
|
Stack,
|
||||||
Grid,
|
Grid,
|
||||||
Avatar,
|
Avatar
|
||||||
GlobalStyles
|
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import {
|
import {
|
||||||
Dashboard as DashboardIcon,
|
Dashboard as DashboardIcon,
|
||||||
@@ -484,7 +484,10 @@ function App(): JSX.Element {
|
|||||||
text: result.message,
|
text: result.message,
|
||||||
icon: result.success ? 'success' : 'error',
|
icon: result.success ? 'success' : 'error',
|
||||||
background: '#1e1e1e',
|
background: '#1e1e1e',
|
||||||
color: '#fff'
|
color: '#fff',
|
||||||
|
customClass: {
|
||||||
|
htmlContainer: 'nginx-error-container'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -569,6 +572,16 @@ function App(): JSX.Element {
|
|||||||
<GlobalStyles styles={{
|
<GlobalStyles styles={{
|
||||||
'.swal2-container': {
|
'.swal2-container': {
|
||||||
zIndex: '9999 !important'
|
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' }}>
|
<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>
|
</DialogContent>
|
||||||
<DialogActions sx={{ p: 2, gap: 1 }}>
|
<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)' }}>
|
<Button onClick={() => setIsProjectNginxConfigOpen(false)} sx={{ color: 'rgba(255,255,255,0.6)' }}>
|
||||||
İptal
|
İptal
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user