diff --git a/config/mariadb.ini.template b/config/mariadb.ini.template index 2928386..6caeb3a 100644 --- a/config/mariadb.ini.template +++ b/config/mariadb.ini.template @@ -16,6 +16,7 @@ innodb_doublewrite=0 innodb_lock_wait_timeout=600 innodb_strict_mode=0 innodb_default_row_format=DYNAMIC +{{EXTRA_CONFIG}} [client] port={{PORT}} diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 00bfc1d..79fc7c7 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -484,11 +484,13 @@ export function registerIpcHandlers(): void { const sanitizedServiceName = serviceName.replace(/:/g, '_') const logFile = path.join(configService.getBasePath(), 'logs', `${sanitizedServiceName}_error.log`) + const tuning = settings.mariaDbTuning?.[dbId] || '' const configPath = await configService.generateConfig('mariadb.ini.template', `mariadb_${dbId || 'default'}.ini`, { PORT: dbPort, DATADIR: dataDir.replace(/\\/g, '/'), SOCKET: `/tmp/${sanitizedServiceName}.sock`, - LOG_FILE: logFile.replace(/\\/g, '/') + LOG_FILE: logFile.replace(/\\/g, '/'), + EXTRA_CONFIG: tuning }) const success = await processManager.startService(serviceName, binPath, ['--defaults-file=' + configPath], false) @@ -627,6 +629,56 @@ export function registerIpcHandlers(): void { return { success: false, message: `MariaDB hatası: ${error.message}` } } }) + + ipcMain.handle('mariadb:config:get', async (_event, versionId: string) => { + const settings = configService.getSettings() + const tuning = settings.mariaDbTuning?.[versionId] || '' + return { content: tuning } + }) + + ipcMain.handle('mariadb:config:save', async (_event, { versionId, content }) => { + const settings = configService.getSettings() + const mariaDbTuning = settings.mariaDbTuning || {} + mariaDbTuning[versionId] = content + configService.updateSettings({ ...settings, mariaDbTuning }) + + const serviceName = `mariadb:${versionId.replace('mariadb-', '')}` + if (processManager.isServiceRunning(serviceName)) { + await processManager.stopService(serviceName) + } + + return { success: true, message: 'Tuning configuration saved.' } + }) + + ipcMain.handle('php:versions:all', async () => { + return phpManagerService.getVersions() + }) + + ipcMain.handle('php:config:get', async (_event, version: string) => { + const phpIniPath = path.join(configService.getBasePath(), 'generated_configs', `php_${version}.ini`) + if (fs.existsSync(phpIniPath)) { + return { content: fs.readFileSync(phpIniPath, 'utf8') } + } + return { content: '' } + }) + + ipcMain.handle('php:config:save', async (_event, { version, content }) => { + const phpIniPath = path.join(configService.getBasePath(), 'generated_configs', `php_${version}.ini`) + // Add manual edit marker to prevent overwriting + let finalContent = content + if (!finalContent.includes('; MANUAL_EDIT')) { + finalContent = `; MANUAL_EDIT\n${finalContent}` + } + fs.writeFileSync(phpIniPath, finalContent, 'utf8') + + // Restart PHP if running + const serviceName = `php:${version}` + if (processManager.isServiceRunning(serviceName)) { + await processManager.stopService(serviceName) + // It will be started again later or manually + } + return { success: true } + }) // Apache Versions ipcMain.handle('apache:versions', async () => { diff --git a/src/main/services/ConfigService.ts b/src/main/services/ConfigService.ts index 1c2623d..5e408d8 100644 --- a/src/main/services/ConfigService.ts +++ b/src/main/services/ConfigService.ts @@ -17,7 +17,8 @@ export class ConfigService { language: 'tr', cliEnvEnabled: true, defaultPhpVersion: '', - defaultMariaDbVersion: '' + defaultMariaDbVersion: '', + mariaDbTuning: {} as Record } constructor() { diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 2b1b9e6..7ffc6b4 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -78,7 +78,8 @@ import { Launch as LaunchIcon, Handyman as ToolsIcon, Restore as ResetIcon, - Search as SearchIcon + Search as SearchIcon, + Tune as TuningIcon } from '@mui/icons-material' import Swal from 'sweetalert2' import { useTranslation } from 'react-i18next' @@ -88,6 +89,7 @@ import MariaDbExportWizard from './components/MariaDbExportWizard' import NginxCodeEditor from './components/NginxCodeEditor' import ApacheCodeEditor from './components/ApacheCodeEditor' import PhpExtensionManager from './components/PhpExtensionManager' +import ServerTuning from './components/ServerTuning' declare global { interface Window { @@ -1105,6 +1107,12 @@ function App(): JSX.Element { + + setActiveTab('tuning')}> + + + + setActiveTab('tools')}> @@ -1133,6 +1141,7 @@ function App(): JSX.Element { + {activeTab === 'tuning' && } {activeTab === 'settings' && ( diff --git a/src/renderer/src/components/ServerTuning.tsx b/src/renderer/src/components/ServerTuning.tsx new file mode 100644 index 0000000..3f5aba8 --- /dev/null +++ b/src/renderer/src/components/ServerTuning.tsx @@ -0,0 +1,426 @@ +import { useState, useEffect } from 'react' +import { + Box, + Typography, + Grid, + Paper, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Slider, + TextField, + Stack, + IconButton, + Tooltip, + Alert, + CircularProgress, + Card, + CardContent, + CardActionArea, + Chip +} from '@mui/material' +import { + Tune as TuneIcon, + Close as CloseIcon, + Save as SaveIcon, + Code as PhpIcon, + Storage as DbIcon, + InfoOutlined as InfoIcon +} from '@mui/icons-material' +import { useTranslation } from 'react-i18next' + +interface TuningParam { + key: string + label: string + description: string + min: number + max: number + step: number + unit: string + type: 'php' | 'mariadb' + transform?: (val: number) => string + parse?: (val: string) => number +} + +const PHP_PARAMS: TuningParam[] = [ + { + key: 'memory_limit', + label: 'memory_limit', + description: 'tuning.parameters.memory_limit_desc', + min: 64, + max: 4096, + step: 64, + unit: 'M', + type: 'php', + transform: (v) => `${v}M`, + parse: (v) => parseInt(v.replace('M', '')) || 128 + }, + { + key: 'max_execution_time', + label: 'max_execution_time', + description: 'tuning.parameters.max_execution_time_desc', + min: 30, + max: 3600, + step: 30, + unit: 's', + type: 'php', + transform: (v) => `${v}`, + parse: (v) => parseInt(v) || 30 + }, + { + key: 'post_max_size', + label: 'post_max_size', + description: 'tuning.parameters.post_max_size_desc', + min: 8, + max: 2048, + step: 8, + unit: 'M', + type: 'php', + transform: (v) => `${v}M`, + parse: (v) => parseInt(v.replace('M', '')) || 8 + }, + { + key: 'upload_max_filesize', + label: 'upload_max_filesize', + description: 'tuning.parameters.upload_max_filesize_desc', + min: 2, + max: 2048, + step: 2, + unit: 'M', + type: 'php', + transform: (v) => `${v}M`, + parse: (v) => parseInt(v.replace('M', '')) || 2 + }, + { + key: 'max_input_vars', + label: 'max_input_vars', + description: 'tuning.parameters.max_input_vars_desc', + min: 1000, + max: 100000, + step: 1000, + unit: '', + type: 'php', + transform: (v) => `${v}`, + parse: (v) => parseInt(v) || 1000 + } +] + +const MARIADB_PARAMS: TuningParam[] = [ + { + key: 'max_connections', + label: 'max_connections', + description: 'tuning.parameters.max_connections_desc', + min: 10, + max: 5000, + step: 10, + unit: '', + type: 'mariadb', + transform: (v) => `${v}`, + parse: (v) => parseInt(v) || 151 + }, + { + key: 'innodb_buffer_pool_size', + label: 'innodb_buffer_pool_size', + description: 'tuning.parameters.innodb_buffer_pool_size_desc', + min: 128, + max: 16384, + step: 128, + unit: 'M', + type: 'mariadb', + transform: (v) => `${v}M`, + parse: (v) => { + if (v.endsWith('G')) return parseInt(v) * 1024 + return parseInt(v) || 128 + } + }, + { + key: 'key_buffer_size', + label: 'key_buffer_size', + description: 'tuning.parameters.key_buffer_size_desc', + min: 8, + max: 1024, + step: 8, + unit: 'M', + type: 'mariadb', + transform: (v) => `${v}M`, + parse: (v) => parseInt(v) || 16 + }, + { + key: 'max_allowed_packet', + label: 'max_allowed_packet', + description: 'tuning.parameters.max_allowed_packet_desc', + min: 1, + max: 1024, + step: 1, + unit: 'M', + type: 'mariadb', + transform: (v) => `${v}M`, + parse: (v) => { + if (v.endsWith('G')) return parseInt(v) * 1024 + return parseInt(v) || 16 + } + } +] + +export default function ServerTuning() { + const { t } = useTranslation() + const [phpVersions, setPhpVersions] = useState([]) + const [mariaDbVersions, setMariaDbVersions] = useState([]) + const [loading, setLoading] = useState(true) + const [selectedVersion, setSelectedVersion] = useState(null) + const [configContent, setConfigContent] = useState('') + const [isModalOpen, setIsModalOpen] = useState(false) + const [saving, setSaving] = useState(false) + const [tuningValues, setTuningValues] = useState>({}) + + const fetchVersions = async () => { + setLoading(true) + if (window.api) { + const phps = await window.api.getPhpVersions() + const dbs = await window.api.getMariaDbVersions() + setPhpVersions(phps.filter((v: any) => v.status === 'installed')) + setMariaDbVersions(dbs.filter((v: any) => v.status === 'installed')) + } + setLoading(false) + } + + useEffect(() => { + fetchVersions() + }, []) + + const handleOpenTuning = async (version: any, type: 'php' | 'mariadb') => { + setSelectedVersion({ ...version, type }) + let content = '' + if (type === 'php') { + const res = await window.api.invoke('php:config:get', version.version) + content = res.content + } else { + const res = await window.api.invoke('mariadb:config:get', version.id) + content = res.content + } + setConfigContent(content) + + // Parse current values + const params = type === 'php' ? PHP_PARAMS : MARIADB_PARAMS + const values: Record = {} + params.forEach(p => { + const regex = type === 'php' + ? new RegExp(`^\\s*${p.key}\\s*=\\s*(.+)$`, 'm') + : new RegExp(`^\\s*${p.key}\\s*=\\s*(.+)$`, 'm') + + const match = content.match(regex) + if (match && p.parse) { + values[p.key] = p.parse(match[1].trim()) + } else { + values[p.key] = p.min // Default to min if not found + } + }) + setTuningValues(values) + setIsModalOpen(true) + } + + const handleSave = async () => { + setSaving(true) + let updatedContent = configContent + const params = selectedVersion.type === 'php' ? PHP_PARAMS : MARIADB_PARAMS + + params.forEach(p => { + const newValue = p.transform ? p.transform(tuningValues[p.key]) : tuningValues[p.key].toString() + const regex = new RegExp(`^(\\s*${p.key}\\s*=\\s*)(.+)$`, 'm') + + if (updatedContent.match(regex)) { + updatedContent = updatedContent.replace(regex, `$1${newValue}`) + } else { + updatedContent += `\n${p.key}=${newValue}` + } + }) + + if (selectedVersion.type === 'php') { + await window.api.invoke('php:config:save', { version: selectedVersion.version, content: updatedContent }) + } else { + await window.api.invoke('mariadb:config:save', { versionId: selectedVersion.id, content: updatedContent }) + } + + setSaving(false) + setIsModalOpen(false) + } + + if (loading) { + return ( + + + + ) + } + + return ( + + + + {t('tuning.title')} + + + {t('tuning.desc')} + + + + + {/* PHP Section */} + + + + + + + PHP Tuning + + + + + {phpVersions.map(v => ( + + handleOpenTuning(v, 'php')}> + + + PHP {v.version} + php.ini optimization + + + + + + ))} + + + + + {/* MariaDB Section */} + + + + + + + MariaDB Tuning + + + + + {mariaDbVersions.map(v => ( + + handleOpenTuning(v, 'mariadb')}> + + + MariaDB {v.version} + Performance variables + + + + + + ))} + + + + + + {/* Tuning Modal */} + !saving && setIsModalOpen(false)} + maxWidth="sm" + fullWidth + PaperProps={{ + sx: { + bgcolor: '#1a1d21', + backgroundImage: 'none', + color: '#fff', + borderRadius: 4, + border: '1px solid rgba(255,255,255,0.1)' + } + }} + > + + + + + {selectedVersion?.type === 'php' ? `PHP ${selectedVersion?.version}` : `MariaDB ${selectedVersion?.version}`} Tuning + + + setIsModalOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}> + + + + + + + {t('tuning.restart_notice')} + + + + {(selectedVersion?.type === 'php' ? PHP_PARAMS : MARIADB_PARAMS).map(p => ( + + + + {p.label} + + + + + setTuningValues({ ...tuningValues, [p.key]: parseInt(e.target.value) || p.min })} + InputProps={{ + endAdornment: {p.unit} + } as any} + sx={{ width: 120, '& .MuiOutlinedInput-root': { height: 32 } }} + /> + + setTuningValues({ ...tuningValues, [p.key]: v as number })} + sx={{ + color: selectedVersion?.type === 'php' ? 'primary.main' : 'secondary.main', + '& .MuiSlider-thumb': { + width: 14, + height: 14, + } + }} + /> + + {t(p.description)} + + + ))} + + + + + + + + + + ) +} diff --git a/src/renderer/src/locales/en.json b/src/renderer/src/locales/en.json index bf7e917..404ad2b 100644 --- a/src/renderer/src/locales/en.json +++ b/src/renderer/src/locales/en.json @@ -276,5 +276,34 @@ "info": "Info", "progress": "Progress" } + }, + "tuning": { + "title": "Server Tuning", + "desc": "Optimize performance of PHP and MariaDB servers with key resource limits.", + "php_total": "PHP Instances", + "mariadb_total": "MariaDB Instances", + "tune_btn": "TUNE", + "saving": "Applying changes...", + "restart_notice": "Some changes require a service restart to take effect.", + "parameters": { + "memory_limit": "Memory Limit", + "memory_limit_desc": "Maximum amount of memory a script may consume.", + "max_execution_time": "Max Execution Time", + "max_execution_time_desc": "Maximum time in seconds a script is allowed to run.", + "post_max_size": "Post Max Size", + "post_max_size_desc": "Maximum size of POST data that PHP will accept.", + "upload_max_filesize": "Upload Max Filesize", + "upload_max_filesize_desc": "Maximum size of an uploaded file.", + "max_input_vars": "Max Input Vars", + "max_input_vars_desc": "How many input variables may be accepted.", + "max_connections": "Max Connections", + "max_connections_desc": "Maximum number of simultaneous client connections.", + "innodb_buffer_pool_size": "InnoDB Buffer Pool", + "innodb_buffer_pool_size_desc": "Crucial for performance. Holds indexes and data from memory.", + "key_buffer_size": "Key Buffer Size", + "key_buffer_size_desc": "Index blocks for MyISAM tables (shared by all threads).", + "max_allowed_packet": "Max Allowed Packet", + "max_allowed_packet_desc": "Maximum size of one packet or any generated/intermediate string." + } } } \ No newline at end of file diff --git a/src/renderer/src/locales/tr.json b/src/renderer/src/locales/tr.json index d43a271..ddf4261 100644 --- a/src/renderer/src/locales/tr.json +++ b/src/renderer/src/locales/tr.json @@ -271,5 +271,34 @@ "info": "Bilgi", "progress": "İlerleme" } + }, + "tuning": { + "title": "Sunucu Optimizasyonu", + "desc": "PHP ve MariaDB sunucularının performansını kritik kaynak sınırlarıyla optimize edin.", + "php_total": "PHP Örneği", + "mariadb_total": "MariaDB Örneği", + "tune_btn": "AYARLA", + "saving": "Değişiklikler uygulanıyor...", + "restart_notice": "Bazı değişikliklerin geçerli olması için servislerin yeniden başlatılması gerekir.", + "parameters": { + "memory_limit": "Bellek Limiti", + "memory_limit_desc": "Bir betiğin tüketebileceği maksimum bellek miktarı.", + "max_execution_time": "Maksimum Çalışma Süresi", + "max_execution_time_desc": "Bir betiğin çalışmasına izin verilen maksimum süre (saniye).", + "post_max_size": "Post Maksimum Boyutu", + "post_max_size_desc": "PHP'nin kabul edeceği maksimum POST verisi boyutu.", + "upload_max_filesize": "Dosya Yükleme Limiti", + "upload_max_filesize_desc": "Yüklenebilecek maksimum dosya boyutu.", + "max_input_vars": "Maksimum Girdi Değişkeni", + "max_input_vars_desc": "Kaç tane girdi değişkeninin (GET/POST/Cookie) kabul edilebileceği.", + "max_connections": "Maksimum Bağlantı", + "max_connections_desc": "Eşzamanlı maksimum istemci bağlantısı sayısı.", + "innodb_buffer_pool_size": "InnoDB Tampon Havuzu", + "innodb_buffer_pool_size_desc": "Performans için kritiktir. İndeksleri ve verileri bellekte tutar.", + "key_buffer_size": "Anahtar Tampon Boyutu", + "key_buffer_size_desc": "MyISAM tabloları için indeks blokları (tüm iş parçacıkları tarafından paylaşılır).", + "max_allowed_packet": "Maksimum Paket Boyutu", + "max_allowed_packet_desc": "Bir paketin veya oluşturulan herhangi bir dizenin maksimum boyutu." + } } } \ No newline at end of file