feat: implement Nginx and PHP service management IPC handlers with dynamic configuration generation

This commit is contained in:
Ümit Tunç
2026-03-30 16:51:11 +03:00
parent 532e1a5407
commit f4cff4ac68
3 changed files with 239 additions and 111 deletions
+1 -11
View File
@@ -47,17 +47,7 @@ sys_temp_dir = "{{TEMP_DIR}}"
extension_dir = "{{EXT_DIR}}" extension_dir = "{{EXT_DIR}}"
[ExtensionList] [ExtensionList]
extension=curl {{EXTENSIONS}}
extension=fileinfo
extension={{GD_EXT}}
extension=gettext
extension=mbstring
extension=exif
extension=mysqli
extension=openssl
extension=pdo_mysql
extension=pdo_sqlite
extension=sqlite3
[Date] [Date]
date.timezone = UTC date.timezone = UTC
+39 -1
View File
@@ -187,11 +187,26 @@ export function registerIpcHandlers(): void {
if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true }) if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true })
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }) if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true })
// --- 1. Get extensions from local php.ini ---
let extensionsList = ''
const localIniPath = path.join(phpDir, 'php.ini')
if (fs.existsSync(localIniPath)) {
const localIniContent = fs.readFileSync(localIniPath, 'utf8')
const extMatches = localIniContent.match(/^\s*extension\s*=.*$/gm)
if (extMatches) {
extensionsList = extMatches.join('\n')
}
} else {
// Fallback to minimal set if missing
extensionsList = 'extension=curl\nextension=mbstring\nextension=openssl\nextension=mysqli\nextension=pdo_mysql'
}
const configPath = await configService.generateConfig('php.ini.template', `php_${phpVersion || 'default'}.ini`, { const configPath = await configService.generateConfig('php.ini.template', `php_${phpVersion || 'default'}.ini`, {
EXT_DIR: extDir, EXT_DIR: extDir,
GD_EXT: gdExtName, GD_EXT: gdExtName,
SESSION_DIR: sessionDir, SESSION_DIR: sessionDir,
TEMP_DIR: tempDir TEMP_DIR: tempDir,
EXTENSIONS: extensionsList
}) })
const success = await processManager.startService(serviceName, binPath, ['-b', `127.0.0.1:${phpPort}`, '-c', configPath], false) const success = await processManager.startService(serviceName, binPath, ['-b', `127.0.0.1:${phpPort}`, '-c', configPath], false)
@@ -370,6 +385,29 @@ export function registerIpcHandlers(): void {
return result return result
}) })
ipcMain.handle('php:config:get', async (_event, version: string) => {
const phpDir = path.join(configService.getBasePath(), 'bin', `php-${version}`)
const iniPath = path.join(phpDir, 'php.ini')
if (fs.existsSync(iniPath)) {
return fs.readFileSync(iniPath, 'utf8')
}
return ''
})
ipcMain.handle('php:config:save', async (_event, { version, content }) => {
const phpDir = path.join(configService.getBasePath(), 'bin', `php-${version}`)
const iniPath = path.join(phpDir, 'php.ini')
fs.writeFileSync(iniPath, content)
// Return a result that indicates a restart is needed
const serviceName = `php:${version}`
const isRunning = processManager.isServiceRunning(serviceName)
if (isRunning) {
await processManager.stopService(serviceName)
}
return { success: true, message: `php.ini saved.${isRunning ? ' Current PHP service stopped. Please restart to apply.' : ''}` }
})
// MariaDB Versions // MariaDB Versions
ipcMain.handle('mariadb:versions', async () => { ipcMain.handle('mariadb:versions', async () => {
return await mariaDbManagerService.getVersions() return await mariaDbManagerService.getVersions()
@@ -15,10 +15,9 @@ import {
Box, Box,
Typography, Typography,
Chip, Chip,
IconButton, IconButton
Alert
} from '@mui/material' } from '@mui/material'
import { Search as SearchIcon, Close as CloseIcon, Refresh as RefreshIcon } from '@mui/icons-material' import { Search as SearchIcon, Close as CloseIcon, Refresh as RefreshIcon, EditNote as RawIcon, Save as SaveIcon } from '@mui/icons-material'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
interface PhpExtension { interface PhpExtension {
@@ -38,6 +37,9 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState('')
const [saving, setSaving] = useState<string | null>(null) const [saving, setSaving] = useState<string | null>(null)
const [rawConfig, setRawConfig] = useState('')
const [isRawEditorOpen, setIsRawEditorOpen] = useState(false)
const [isSavingRaw, setIsSavingRaw] = useState(false)
const fetchExtensions = async () => { const fetchExtensions = async () => {
if (!phpVersion) return if (!phpVersion) return
@@ -52,6 +54,39 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
} }
} }
const fetchRawConfig = async () => {
if (!phpVersion) return
try {
const data = await window.api.invoke('php:config:get', phpVersion)
setRawConfig(data)
setIsRawEditorOpen(true)
} catch (error) {
console.error('Failed to fetch raw config:', error)
}
}
const handleSaveRaw = async () => {
setIsSavingRaw(true)
try {
const result = await window.api.invoke('php:config:save', { version: phpVersion, content: rawConfig })
if (result.success) {
Swal.fire({
title: 'Başarılı',
text: result.message,
icon: 'success',
background: '#1e1e1e',
color: '#fff'
})
setIsRawEditorOpen(false)
fetchExtensions() // Refresh extension list too
}
} catch (error: any) {
Swal.fire({ title: 'Hata', text: error.message, icon: 'error', background: '#1e1e1e', color: '#fff' })
} finally {
setIsSavingRaw(false)
}
}
useEffect(() => { useEffect(() => {
if (open && phpVersion) { if (open && phpVersion) {
fetchExtensions() fetchExtensions()
@@ -101,6 +136,7 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
) )
return ( return (
<>
<Dialog <Dialog
open={open} open={open}
onClose={onClose} onClose={onClose}
@@ -126,7 +162,7 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent dividers sx={{ borderColor: 'rgba(255,255,255,0.1)', p: 0 }}> <DialogContent dividers sx={{ borderColor: 'rgba(255,255,255,0.1)', p: 0 }}>
<Box sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.03)' }}> <Box sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.03)', display: 'flex', gap: 1 }}>
<TextField <TextField
fullWidth fullWidth
size="small" size="small"
@@ -142,6 +178,14 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)' } sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)' }
}} }}
/> />
<Button
variant="outlined"
startIcon={<RawIcon />}
onClick={fetchRawConfig}
sx={{ whiteSpace: 'nowrap', borderColor: 'rgba(255,255,255,0.2)', color: '#fff' }}
>
RAW EDİTÖR
</Button>
</Box> </Box>
{loading ? ( {loading ? (
@@ -201,6 +245,62 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
<Dialog
open={isRawEditorOpen}
onClose={() => setIsRawEditorOpen(false)}
maxWidth="md"
fullWidth
PaperProps={{
sx: {
bgcolor: '#1a1a1a',
color: '#fff',
borderRadius: 2,
}
}}
>
<DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6">php.ini Düzenle ({phpVersion})</Typography>
<IconButton onClick={() => setIsRawEditorOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent sx={{ p: 0 }}>
<TextField
multiline
fullWidth
rows={20}
value={rawConfig}
onChange={(e) => setRawConfig(e.target.value)}
variant="filled"
InputProps={{
sx: {
fontFamily: 'monospace',
fontSize: '0.85rem',
color: '#d4d4d4',
bgcolor: '#1e1e1e',
'& textarea': { p: 2 }
},
disableUnderline: true
}}
/>
</DialogContent>
<DialogActions sx={{ p: 2, bgcolor: 'rgba(0,0,0,0.2)' }}>
<Button onClick={() => setIsRawEditorOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}>
İPTAL
</Button>
<Button
onClick={handleSaveRaw}
variant="contained"
color="primary"
startIcon={isSavingRaw ? <CircularProgress size={18} color="inherit" /> : <SaveIcon />}
disabled={isSavingRaw}
>
{isSavingRaw ? 'KAYDEDİLİYOR...' : 'KAYDET VE SUNUCUYU DURDUR'}
</Button>
</DialogActions>
</Dialog>
</>
) )
} }