feat: add PHP INI editor component, extension manager UI, and Nginx IPC service handlers

This commit is contained in:
Ümit Tunç
2026-03-30 16:51:16 +03:00
parent f4cff4ac68
commit 8d687a159f
3 changed files with 159 additions and 26 deletions
+5 -2
View File
@@ -389,9 +389,12 @@ export function registerIpcHandlers(): void {
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 {
content: fs.readFileSync(iniPath, 'utf8'),
path: iniPath
}
}
return ''
return { content: '', path: iniPath }
})
ipcMain.handle('php:config:save', async (_event, { version, content }) => {
@@ -19,6 +19,7 @@ import {
} from '@mui/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 PhpIniCodeEditor from './PhpIniCodeEditor'
interface PhpExtension {
name: string
@@ -40,6 +41,8 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
const [rawConfig, setRawConfig] = useState('')
const [isRawEditorOpen, setIsRawEditorOpen] = useState(false)
const [isSavingRaw, setIsSavingRaw] = useState(false)
const [configPath, setConfigPath] = useState('')
const [rawSearchTerm, setRawSearchTerm] = useState('')
const fetchExtensions = async () => {
if (!phpVersion) return
@@ -58,7 +61,8 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
if (!phpVersion) return
try {
const data = await window.api.invoke('php:config:get', phpVersion)
setRawConfig(data)
setRawConfig(data.content)
setConfigPath(data.path)
setIsRawEditorOpen(true)
} catch (error) {
console.error('Failed to fetch raw config:', error)
@@ -259,30 +263,53 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
}
}}
>
<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
<DialogTitle sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, p: 2 }}>
<Box 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>
</Box>
<Typography
variant="caption"
sx={{
color: 'primary.main',
wordBreak: 'break-all',
cursor: 'pointer',
'&:hover': { textDecoration: 'underline', color: 'primary.light' }
}}
onClick={() => window.api.invoke('shell:open-path', configPath)}
title="Dosyayı sistemde aç"
>
{configPath}
</Typography>
</DialogTitle>
<Box sx={{ px: 2, pb: 2, bgcolor: '#1a1a1a' }}>
<TextField
fullWidth
size="small"
placeholder="Dosya içerisinde ara..."
value={rawSearchTerm}
onChange={(e) => setRawSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon sx={{ color: 'text.secondary', fontSize: '1.2rem' }} />
</InputAdornment>
),
sx: {
color: '#fff',
bgcolor: 'rgba(255,255,255,0.05)',
'& fieldset': { borderColor: 'rgba(255,255,255,0.1)' }
}
}}
/>
</Box>
<DialogContent sx={{ p: 0, minHeight: 400, borderTop: '1px solid rgba(255,255,255,0.1)' }}>
<PhpIniCodeEditor
value={rawConfig}
onChange={setRawConfig}
searchTerm={rawSearchTerm}
/>
</DialogContent>
<DialogActions sx={{ p: 2, bgcolor: 'rgba(0,0,0,0.2)' }}>
@@ -0,0 +1,103 @@
import React from 'react'
import Editor from 'react-simple-code-editor'
import Prism from 'prismjs'
import 'prismjs/components/prism-ini'
import 'prismjs/themes/prism-tomorrow.css'
interface PhpIniCodeEditorProps {
value: string
onChange: (value: string) => void
searchTerm?: string
}
const PhpIniCodeEditor: React.FC<PhpIniCodeEditorProps> = ({ value, onChange, searchTerm }) => {
const editorRef = React.useRef<HTMLDivElement>(null)
const highlightWithSearch = (code: string) => {
// 1. Get Prism highlighted HTML
let highlighted = Prism.highlight(code, Prism.languages.ini, 'ini')
// 2. If search term exists, wrap it in <mark>
// Use a regex that avoids matching inside existing HTML tags
if (searchTerm && searchTerm.trim().length > 1) {
const escapedSearch = searchTerm.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
// This regex matches the search term only if it's not inside a tag (negative lookahead for > before <)
const regex = new RegExp(`(${escapedSearch})(?![^<]*>)`, 'gi')
highlighted = highlighted.replace(regex, '<mark class="search-match">$1</mark>')
}
return highlighted
}
// Scroll to first match
React.useEffect(() => {
if (searchTerm && searchTerm.trim().length > 1 && editorRef.current) {
const index = value.toLowerCase().indexOf(searchTerm.toLowerCase().trim())
if (index !== -1) {
const container = editorRef.current
const linesBefore = value.substring(0, index).split('\n').length
const lineHeight = 21 // Approx for 14px font
// Scroll the container, not the textarea
container.scrollTop = (linesBefore - 3) * lineHeight
}
}
}, [searchTerm])
return (
<div
ref={editorRef}
style={{
borderRadius: '4px',
overflow: 'hidden',
border: '1px solid rgba(255,255,255,0.1)',
backgroundColor: '#1a1a1a',
fontFamily: '"Fira code", "Fira Mono", monospace',
fontSize: '14px',
height: '450px',
display: 'flex',
flexDirection: 'column',
scrollBehavior: 'smooth'
}}
>
<Editor
key={searchTerm} // Force re-render/re-highlight when searchTerm changes
value={value}
onValueChange={code => onChange(code)}
highlight={highlightWithSearch}
padding={15}
style={{
fontFamily: '"Fira code", "Fira Mono", monospace',
fontSize: 14,
minHeight: '100%',
outline: 'none'
}}
textareaClassName="ini-editor-textarea"
preClassName="ini-editor-pre"
/>
<style>{`
.ini-editor-textarea {
outline: none !important;
caret-color: #fff !important;
}
.ini-editor-pre {
pointer-events: none;
}
.token.comment { color: #6a9955; }
.token.section { color: #569cd6; font-weight: bold; }
.token.attr-name { color: #9cdcfe; }
.token.attr-value { color: #ce9178; }
.token.punctuation { color: #d4d4d4; }
.token.important { color: #569cd6; }
.search-match {
background: #ffcf00 !important;
color: #000 !important;
border-radius: 2px;
box-shadow: 0 0 8px rgba(255, 207, 0, 0.6);
font-weight: bold;
}
`}</style>
</div>
)
}
export default PhpIniCodeEditor