feat: implement IPC handlers for Nginx and Apache service management and project configuration generation
This commit is contained in:
@@ -39,3 +39,4 @@ bin/
|
|||||||
*.exe
|
*.exe
|
||||||
*.dll
|
*.dll
|
||||||
projects.json
|
projects.json
|
||||||
|
www/
|
||||||
|
|||||||
+77
-16
@@ -290,10 +290,16 @@ export function registerIpcHandlers(): void {
|
|||||||
if (fs.existsSync(localIniPath)) {
|
if (fs.existsSync(localIniPath)) {
|
||||||
const localIniContent = fs.readFileSync(localIniPath, 'utf8')
|
const localIniContent = fs.readFileSync(localIniPath, 'utf8')
|
||||||
|
|
||||||
// 1. Get extensions
|
// 1. Get extensions (and deduplicate them)
|
||||||
const extMatches = localIniContent.match(/^\s*extension\s*=.*$/gm)
|
const extMatches = localIniContent.match(/^\s*extension\s*=.*$/gm)
|
||||||
if (extMatches) {
|
if (extMatches) {
|
||||||
extensionsList = extMatches.join('\n')
|
const uniqueExts = new Set<string>()
|
||||||
|
extMatches.forEach(match => {
|
||||||
|
// Extract just the dll name or extension name to prevent duplicates
|
||||||
|
const extValue = match.split('=')[1].trim()
|
||||||
|
uniqueExts.add(extValue)
|
||||||
|
})
|
||||||
|
extensionsList = Array.from(uniqueExts).map(ext => `extension=${ext}`).join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Get error settings
|
// 2. Get error settings
|
||||||
@@ -658,38 +664,62 @@ export function registerIpcHandlers(): void {
|
|||||||
|
|
||||||
ipcMain.handle('projects:add', async (_event, project) => {
|
ipcMain.handle('projects:add', async (_event, project) => {
|
||||||
const result = await projectService.addProject(project)
|
const result = await projectService.addProject(project)
|
||||||
// Trigger Nginx restart to generate initial config and apply
|
if (project.serverType === 'apache') {
|
||||||
await restartNginx()
|
await restartApache()
|
||||||
|
} else {
|
||||||
|
await restartNginx()
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('projects:remove', async (_event, id) => {
|
ipcMain.handle('projects:remove', async (_event, id) => {
|
||||||
const result = projectService.removeProject(id)
|
|
||||||
// Trigger Nginx restart to remove config from glob inclusion indirectly
|
|
||||||
// Actually we might want to delete the .conf file too
|
|
||||||
const projects = await projectService.getProjects()
|
const projects = await projectService.getProjects()
|
||||||
const projectToRemove = projects.find(p => p.id === id)
|
const projectToRemove = projects.find(p => p.id === id)
|
||||||
|
const result = projectService.removeProject(id)
|
||||||
|
|
||||||
if (projectToRemove) {
|
if (projectToRemove) {
|
||||||
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${projectToRemove.host}.conf`)
|
if (projectToRemove.serverType === 'apache') {
|
||||||
if (fs.existsSync(configPath)) fs.unlinkSync(configPath)
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects_apache', `${projectToRemove.host}.conf`)
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
try { fs.unlinkSync(configPath) } catch (e) { console.error('Error deleting apache config:', e) }
|
||||||
|
}
|
||||||
|
await restartApache()
|
||||||
|
} else {
|
||||||
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${projectToRemove.host}.conf`)
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
try { fs.unlinkSync(configPath) } catch (e) { console.error('Error deleting nginx config:', e) }
|
||||||
|
}
|
||||||
|
await restartNginx()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await restartNginx()
|
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
ipcMain.handle('projects:update', async (_event, project) => {
|
ipcMain.handle('projects:update', async (_event, project) => {
|
||||||
// Clean up old config file if host changed
|
// Clean up old config file if host or serverType changed
|
||||||
const projects = projectService.getProjects()
|
const projects = await projectService.getProjects()
|
||||||
const oldProject = projects.find(p => p.id === project.id)
|
const oldProject = projects.find(p => p.id === project.id)
|
||||||
if (oldProject && oldProject.host !== project.host) {
|
if (oldProject && (oldProject.host !== project.host || oldProject.serverType !== project.serverType)) {
|
||||||
const oldConfigPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${oldProject.host}.conf`)
|
const oldTypeDir = oldProject.serverType === 'apache' ? 'projects_apache' : 'projects'
|
||||||
|
const oldConfigPath = path.join(configService.getBasePath(), 'generated_configs', oldTypeDir, `${oldProject.host}.conf`)
|
||||||
if (fs.existsSync(oldConfigPath)) {
|
if (fs.existsSync(oldConfigPath)) {
|
||||||
try { fs.unlinkSync(oldConfigPath) } catch (e) { console.error('Error deleting old config:', e) }
|
try { fs.unlinkSync(oldConfigPath) } catch (e) { console.error('Error deleting old config:', e) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = projectService.updateProject(project)
|
const result = projectService.updateProject(project)
|
||||||
// Trigger Nginx restart
|
|
||||||
await restartNginx()
|
if (project.serverType === 'apache') {
|
||||||
|
await restartApache()
|
||||||
|
} else {
|
||||||
|
await restartNginx()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If serverType changed, trigger restart for the old server as well
|
||||||
|
if (oldProject && oldProject.serverType !== project.serverType) {
|
||||||
|
if (oldProject.serverType === 'apache') await restartApache()
|
||||||
|
else await restartNginx()
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -737,6 +767,37 @@ export function registerIpcHandlers(): void {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Apache Config Management
|
||||||
|
ipcMain.handle('apache:config:get', async () => {
|
||||||
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'httpd.conf')
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
return fs.readFileSync(configPath, 'utf8')
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('apache:config:save', async (_event, content) => {
|
||||||
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'httpd.conf')
|
||||||
|
fs.writeFileSync(configPath, content)
|
||||||
|
return await restartApache()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('apache:config:test', async () => {
|
||||||
|
try {
|
||||||
|
const binDir = path.join(configService.getBasePath(), 'bin', 'apache')
|
||||||
|
const isWindows = process.platform === 'win32';
|
||||||
|
const binPath = findExecutable(binDir, isWindows ? 'httpd.exe' : 'httpd')
|
||||||
|
if (!binPath) throw new Error('Apache executable bulunamadı.')
|
||||||
|
|
||||||
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'httpd.conf')
|
||||||
|
|
||||||
|
const result = await processManager.runOnce(binPath, ['-t', '-f', configPath], 'apache')
|
||||||
|
return { success: result.success, message: result.success ? 'Configuration test successful' : result.output }
|
||||||
|
} catch (error: any) {
|
||||||
|
return { success: false, message: error.message }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Project Nginx Config
|
// Project Nginx Config
|
||||||
ipcMain.handle('project:nginx:get', async (_event, host) => {
|
ipcMain.handle('project:nginx:get', async (_event, host) => {
|
||||||
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`)
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`)
|
||||||
|
|||||||
@@ -219,9 +219,10 @@ export class PhpManagerService {
|
|||||||
// Keep the same format as found if possible, but for enabled we enforce a clean one
|
// Keep the same format as found if possible, but for enabled we enforce a clean one
|
||||||
// For Windows, extension=php_name.dll is safest even in newer versions,
|
// For Windows, extension=php_name.dll is safest even in newer versions,
|
||||||
// but extension=name is cleaner. We'll use a slightly smarter approach:
|
// but extension=name is cleaner. We'll use a slightly smarter approach:
|
||||||
let extValue = extName
|
let cleanExt = extName.replace(/^php_/i, '').replace(/\.dll$/i, '')
|
||||||
|
let extValue = cleanExt
|
||||||
if (version.startsWith('5.') || version.startsWith('7.')) {
|
if (version.startsWith('5.') || version.startsWith('7.')) {
|
||||||
extValue = `php_${extName}.dll`
|
extValue = `php_${cleanExt}.dll`
|
||||||
}
|
}
|
||||||
return enabled ? `extension=${extValue}` : `;extension=${extValue}`
|
return enabled ? `extension=${extValue}` : `;extension=${extValue}`
|
||||||
}
|
}
|
||||||
@@ -229,9 +230,10 @@ export class PhpManagerService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!found && enabled) {
|
if (!found && enabled) {
|
||||||
let extValue = extName
|
let cleanExt = extName.replace(/^php_/i, '').replace(/\.dll$/i, '')
|
||||||
|
let extValue = cleanExt
|
||||||
if (version.startsWith('5.') || version.startsWith('7.')) {
|
if (version.startsWith('5.') || version.startsWith('7.')) {
|
||||||
extValue = `php_${extName}.dll`
|
extValue = `php_${cleanExt}.dll`
|
||||||
}
|
}
|
||||||
const newLine = `extension=${extValue}`
|
const newLine = `extension=${extValue}`
|
||||||
const extSectionIndex = newLines.findIndex(l => l.trim().toLowerCase() === '; dynamic extensions')
|
const extSectionIndex = newLines.findIndex(l => l.trim().toLowerCase() === '; dynamic extensions')
|
||||||
|
|||||||
+104
-1
@@ -75,6 +75,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import MariaDbImportWizard from './components/MariaDbImportWizard'
|
import MariaDbImportWizard from './components/MariaDbImportWizard'
|
||||||
import MariaDbExportWizard from './components/MariaDbExportWizard'
|
import MariaDbExportWizard from './components/MariaDbExportWizard'
|
||||||
import NginxCodeEditor from './components/NginxCodeEditor'
|
import NginxCodeEditor from './components/NginxCodeEditor'
|
||||||
|
import ApacheCodeEditor from './components/ApacheCodeEditor'
|
||||||
import PhpExtensionManager from './components/PhpExtensionManager'
|
import PhpExtensionManager from './components/PhpExtensionManager'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -145,6 +146,8 @@ function App(): JSX.Element {
|
|||||||
const [isMariaDbExportWizardOpen, setIsMariaDbExportWizardOpen] = useState(false)
|
const [isMariaDbExportWizardOpen, setIsMariaDbExportWizardOpen] = useState(false)
|
||||||
const [isNginxConfigOpen, setIsNginxConfigOpen] = useState(false)
|
const [isNginxConfigOpen, setIsNginxConfigOpen] = useState(false)
|
||||||
const [nginxConfig, setNginxConfig] = useState('')
|
const [nginxConfig, setNginxConfig] = useState('')
|
||||||
|
const [isApacheConfigOpen, setIsApacheConfigOpen] = useState(false)
|
||||||
|
const [apacheConfig, setApacheConfig] = useState('')
|
||||||
const [isProjectNginxConfigOpen, setIsProjectNginxConfigOpen] = useState(false)
|
const [isProjectNginxConfigOpen, setIsProjectNginxConfigOpen] = useState(false)
|
||||||
const [projectNginxConfig, setProjectNginxConfig] = useState('')
|
const [projectNginxConfig, setProjectNginxConfig] = useState('')
|
||||||
const [selectedProjectHost, setSelectedProjectHost] = useState('')
|
const [selectedProjectHost, setSelectedProjectHost] = useState('')
|
||||||
@@ -515,6 +518,46 @@ function App(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpenApacheConfig = async () => {
|
||||||
|
if (window.api && window.api.invoke) {
|
||||||
|
const config = await window.api.invoke('apache:config:get')
|
||||||
|
setApacheConfig(config)
|
||||||
|
setIsApacheConfigOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveApacheConfig = async () => {
|
||||||
|
if (window.api && window.api.invoke) {
|
||||||
|
const result = await window.api.invoke('apache:config:save', apacheConfig)
|
||||||
|
if (result.success) {
|
||||||
|
setNotification({ open: true, message: 'Apache yapılandırması kaydedildi ve yeniden yüklendi.', severity: 'success' })
|
||||||
|
setIsApacheConfigOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTestApacheConfig = async () => {
|
||||||
|
if (window.api && window.api.invoke) {
|
||||||
|
const result = await window.api.invoke('apache:config:test')
|
||||||
|
const formattedMessage = result.message
|
||||||
|
.replace(/failed/g, '<span style="color: #ff5252;">failed</span>')
|
||||||
|
.replace(/successful/g, '<span style="color: #33d9b2;">successful</span>')
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: result.success ? 'Başarılı' : 'Yapılandırma Hatası',
|
||||||
|
html: `
|
||||||
|
<div class="nginx-error-container">
|
||||||
|
${formattedMessage}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
icon: result.success ? 'success' : 'error',
|
||||||
|
background: '#1e1e1e',
|
||||||
|
color: '#fff',
|
||||||
|
width: '600px'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleTestNginxConfig = async () => {
|
const handleTestNginxConfig = async () => {
|
||||||
if (window.api && window.api.invoke) {
|
if (window.api && window.api.invoke) {
|
||||||
const result = await window.api.invoke('nginx:config:test')
|
const result = await window.api.invoke('nginx:config:test')
|
||||||
@@ -926,7 +969,12 @@ function App(): JSX.Element {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{v.status === 'installed' && (
|
{v.status === 'installed' && (
|
||||||
<Chip label="HAZIR" color="success" variant="outlined" size="small" />
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||||
|
<Button variant="outlined" size="small" startIcon={<SettingsIcon />} onClick={handleOpenApacheConfig}>
|
||||||
|
YAPILANDIRMA
|
||||||
|
</Button>
|
||||||
|
<Chip label="HAZIR" color="success" variant="outlined" size="small" />
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@@ -1103,6 +1151,14 @@ function App(): JSX.Element {
|
|||||||
<IconButton onClick={() => handleOpenLogs('apache')} title="Hata Logları" size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
<IconButton onClick={() => handleOpenLogs('apache')} title="Hata Logları" size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||||
<TerminalIcon fontSize="small" />
|
<TerminalIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
onClick={handleOpenApacheConfig}
|
||||||
|
title="Apache Yapılandırması"
|
||||||
|
size="small"
|
||||||
|
sx={{ bgcolor: 'rgba(255,255,255,0.05)', '&:hover': { color: 'primary.main' } }}
|
||||||
|
>
|
||||||
|
<SettingsIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
<Switch
|
<Switch
|
||||||
checked={status.apache === 'running' || status.apache === 'starting'}
|
checked={status.apache === 'running' || status.apache === 'starting'}
|
||||||
@@ -1780,6 +1836,53 @@ function App(): JSX.Element {
|
|||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<Dialog
|
||||||
|
open={isApacheConfigOpen}
|
||||||
|
onClose={() => setIsApacheConfigOpen(false)}
|
||||||
|
maxWidth="md"
|
||||||
|
fullWidth
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
bgcolor: '#1e1e1e',
|
||||||
|
color: '#fff',
|
||||||
|
borderRadius: 3,
|
||||||
|
border: '1px solid rgba(255,255,255,0.1)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<Typography variant="h6">Apache (httpd.conf) Yapılandırması</Typography>
|
||||||
|
<IconButton onClick={() => setIsApacheConfigOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers sx={{ borderColor: 'rgba(255,255,255,0.1)' }}>
|
||||||
|
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', mb: 2, display: 'block' }}>
|
||||||
|
Düzenlediğiniz bu dosya Apache sunucusunun ana (global) yapılandırma dosyasıdır.
|
||||||
|
</Typography>
|
||||||
|
<ApacheCodeEditor
|
||||||
|
value={apacheConfig}
|
||||||
|
onChange={(val) => setApacheConfig(val)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={{ p: 2, gap: 1 }}>
|
||||||
|
<Button
|
||||||
|
onClick={handleTestApacheConfig}
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<RefreshIcon />}
|
||||||
|
sx={{ color: '#fff', borderColor: 'rgba(255,255,255,0.3)' }}
|
||||||
|
>
|
||||||
|
Yapılandırmayı Test Et
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setIsApacheConfigOpen(false)} sx={{ color: 'rgba(255,255,255,0.6)' }}>
|
||||||
|
İptal
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSaveApacheConfig} variant="contained" color="primary" startIcon={<SaveIcon />}>
|
||||||
|
Kaydet ve Yeniden Yükle
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<PhpExtensionManager
|
<PhpExtensionManager
|
||||||
open={isPhpExtensionsOpen}
|
open={isPhpExtensionsOpen}
|
||||||
onClose={() => setIsPhpExtensionsOpen(false)}
|
onClose={() => setIsPhpExtensionsOpen(false)}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Editor from 'react-simple-code-editor'
|
||||||
|
import Prism from 'prismjs'
|
||||||
|
import 'prismjs/components/prism-apacheconf'
|
||||||
|
import 'prismjs/themes/prism-tomorrow.css'
|
||||||
|
|
||||||
|
interface ApacheCodeEditorProps {
|
||||||
|
value: string
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApacheCodeEditor: React.FC<ApacheCodeEditorProps> = ({ value, onChange }) => {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
borderRadius: '4px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
backgroundColor: '#1e1e1e',
|
||||||
|
fontFamily: '"Fira code", "Fira Mono", monospace',
|
||||||
|
fontSize: '14px',
|
||||||
|
minHeight: '300px'
|
||||||
|
}}>
|
||||||
|
<Editor
|
||||||
|
value={value}
|
||||||
|
onValueChange={code => onChange(code)}
|
||||||
|
highlight={code => Prism.highlight(code, Prism.languages.apacheconf, 'apacheconf')}
|
||||||
|
padding={15}
|
||||||
|
style={{
|
||||||
|
fontFamily: '"Fira code", "Fira Mono", monospace',
|
||||||
|
fontSize: 14,
|
||||||
|
minHeight: '400px',
|
||||||
|
outline: 'none'
|
||||||
|
}}
|
||||||
|
textareaClassName="apache-editor-textarea"
|
||||||
|
preClassName="apache-editor-pre"
|
||||||
|
/>
|
||||||
|
<style>{`
|
||||||
|
.apache-editor-textarea {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
.apache-editor-pre {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.token.comment { color: #6a9955; }
|
||||||
|
.token.directive { color: #569cd6; font-weight: bold; }
|
||||||
|
.token.variable { color: #9cdcfe; }
|
||||||
|
.token.string { color: #ce9178; }
|
||||||
|
.token.number { color: #b5cea8; }
|
||||||
|
.token.boolean { color: #569cd6; }
|
||||||
|
.token.operator { color: #d4d4d4; }
|
||||||
|
.token.punctuation { color: #d4d4d4; }
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApacheCodeEditor
|
||||||
Reference in New Issue
Block a user