feat: implement core services for Nginx, PHP, and project management with IPC integration

This commit is contained in:
Ümit Tunç
2026-03-30 08:52:49 +03:00
parent 91327b245c
commit 0ad90876ea
17 changed files with 457 additions and 436 deletions
+90 -83
View File
@@ -5,12 +5,12 @@ import { configService } from '../services/ConfigService'
import { downloadService } from '../services/DownloadService' import { downloadService } from '../services/DownloadService'
import { projectService } from '../services/ProjectService' import { projectService } from '../services/ProjectService'
import { phpManagerService } from '../services/PhpManagerService' import { phpManagerService } from '../services/PhpManagerService'
import { mySqlManagerService } from '../services/MySqlManagerService' import { mariaDbManagerService } from '../services/MariaDbManagerService'
import { nginxManagerService } from '../services/NginxManagerService' import { nginxManagerService } from '../services/NginxManagerService'
import { phpMyAdminService } from '../services/PhpMyAdminService' import { phpMyAdminService } from '../services/PhpMyAdminService'
import { portMappingService } from '../services/PortMappingService' import { portMappingService } from '../services/PortMappingService'
import path from 'path' import path from 'path'
import { mySqlOperationService } from '../services/MySqlOperationService' import { mariaDbOperationService } from '../services/MariaDbOperationService'
function findExecutable(dir: string, name: string): string | null { function findExecutable(dir: string, name: string): string | null {
if (!fs.existsSync(dir)) return null if (!fs.existsSync(dir)) return null
@@ -37,7 +37,8 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin
if (!activeVersion) throw new Error('Nginx sürümü bulunamadı.') if (!activeVersion) throw new Error('Nginx sürümü bulunamadı.')
const nginxPrefix = path.join(nginxDir, activeVersion) const nginxPrefix = path.join(nginxDir, activeVersion)
const binPath = path.join(nginxPrefix, 'nginx.exe') const isWindows = process.platform === 'win32';
const binPath = path.join(nginxPrefix, isWindows ? 'nginx.exe' : 'nginx')
const confDir = path.join(nginxPrefix, 'conf') const confDir = path.join(nginxPrefix, 'conf')
const logDir = path.join(configService.getBasePath(), 'logs') const logDir = path.join(configService.getBasePath(), 'logs')
const rootPath = path.join(app.getAppPath(), 'www') const rootPath = path.join(app.getAppPath(), 'www')
@@ -167,8 +168,10 @@ export function registerIpcHandlers(): void {
const phpVersion = versionMatch ? versionMatch[1] : null const phpVersion = versionMatch ? versionMatch[1] : null
const phpId = phpVersion ? `php-${phpVersion}` : null const phpId = phpVersion ? `php-${phpVersion}` : null
const isWindows = process.platform === 'win32';
const phpDir = phpId ? path.join(binDir, phpId) : binDir const phpDir = phpId ? path.join(binDir, phpId) : binDir
const binPath = findExecutable(phpDir, 'php-cgi.exe') const phpBinary = isWindows ? 'php-cgi.exe' : 'php-cgi';
const binPath = findExecutable(phpDir, phpBinary)
if (!binPath) return { success: false, message: `${phpVersion || ''} PHP executable bulunamadı. Lütfen indirin.` } if (!binPath) return { success: false, message: `${phpVersion || ''} PHP executable bulunamadı. Lütfen indirin.` }
@@ -199,48 +202,47 @@ export function registerIpcHandlers(): void {
return { success, message: success ? `${phpVersion || ''} PHP başlatıldı.` : `${phpVersion || ''} PHP devreye alınamadı.` } return { success, message: success ? `${phpVersion || ''} PHP başlatıldı.` : `${phpVersion || ''} PHP devreye alınamadı.` }
} }
if (serviceName.startsWith('mysql')) { if (serviceName.startsWith('mariadb') || serviceName.startsWith('mysql')) {
const versionMatch = serviceName.match(/^mysql:(.+)$/) const versionMatch = serviceName.match(/^(?:mariadb|mysql):(.+)$/)
const mysqlVersion = versionMatch ? versionMatch[1] : null const dbVersion = versionMatch ? versionMatch[1] : null
const mysqlId = mysqlVersion ? `mysql-${mysqlVersion}` : null const dbId = dbVersion ? `mariadb-${dbVersion}` : 'mariadb-default'
const dataDir = mysqlId const dataDir = path.join(configService.getBasePath(), 'data', dbId)
? path.join(configService.getBasePath(), 'data', mysqlId) const dbBinDir = dbVersion ? path.join(binDir, `mariadb-${dbVersion}`) : binDir
: path.join(configService.getBasePath(), 'data', 'default')
const mysqlDir = mysqlId ? path.join(binDir, mysqlId) : binDir const isWindows = process.platform === 'win32';
const binPath = findExecutable(mysqlDir, 'mysqld.exe') const dbBinary = isWindows ? 'mariadbd.exe' : 'mariadbd';
let binPath = findExecutable(dbBinDir, dbBinary) || findExecutable(dbBinDir, isWindows ? 'mysqld.exe' : 'mysqld');
if (!binPath) return { success: false, message: 'MySQL executable bulunamadı. Lütfen indirin.' } if (!binPath) return { success: false, message: 'MariaDB executable bulunamadı. Lütfen indirin.' }
const mysqlVersions = await mySqlManagerService.getVersions() const dbVersions = await mariaDbManagerService.getVersions()
const mysqlInfo = mysqlVersions.find(v => v.id === mysqlId) const dbInfo = dbVersions.find(v => v.id === `mariadb-${dbVersion}`)
const mysqlPort = mysqlInfo?.port || (mysqlId ? '3306' : settings.mysqlPort) const dbPort = dbInfo?.port || (dbVersion ? portMappingService.getMariaDbPort(dbVersion).toString() : settings.mariaDbPort)
// Force kill this specific version if running or generic mysql // Force kill this specific version if running or generic mysql
await processManager.forceKillAll(serviceName) await processManager.forceKillAll(serviceName)
// Also kill anything else listening on the same port // Also kill anything else listening on the same port
await processManager.killByPort(serviceName, mysqlPort) await processManager.killByPort(serviceName, dbPort)
// Initialize if data directory is empty // Initialize if data directory is empty
if (!fs.existsSync(dataDir) || fs.readdirSync(dataDir).length === 0) { if (!fs.existsSync(dataDir) || fs.readdirSync(dataDir).length === 0) {
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true }) if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
console.log(`Initializing MySQL data directory for ${serviceName}...`) console.log(`Initializing MariaDB data directory for ${serviceName}...`)
try { try {
// Try modern initialization first (5.7+) // Try MariaDB/Modern MySQL initialization first (5.7+/10.x+)
await processManager.runOnce(binPath, ['--initialize-insecure', `--datadir=${dataDir.replace(/\\/g, '/')}`]) await processManager.runOnce(binPath, ['--initialize-insecure', `--datadir=${dataDir.replace(/\\/g, '/')}`])
} catch (err) { } catch (err) {
console.log(`Modern init failed, trying template copy for ${serviceName}...`) console.log(`Modern init failed, trying template copy for ${serviceName}...`)
// Fallback for 5.6: Copy default 'data' folder from binary dir if it exists // Fallback: Copy default 'data' folder from binary dir if it exists
const templateDataDir = path.join(mysqlDir, 'data') const templateDataDir = path.join(dbBinDir, 'data')
if (fs.existsSync(templateDataDir)) { if (fs.existsSync(templateDataDir)) {
// Recursively copy template data (mysql, performance_schema, etc.)
fs.cpSync(templateDataDir, dataDir, { recursive: true }) fs.cpSync(templateDataDir, dataDir, { recursive: true })
console.log(`Successfully copied template data for ${serviceName}`) console.log(`Successfully copied template data for ${serviceName}`)
} else { } else {
throw new Error(`MySQL initialization failed and no template data found at ${templateDataDir}`) throw new Error(`MariaDB initialization failed and no template data found at ${templateDataDir}`)
} }
} }
} }
@@ -248,8 +250,8 @@ export function registerIpcHandlers(): void {
const sanitizedServiceName = serviceName.replace(/:/g, '_') const sanitizedServiceName = serviceName.replace(/:/g, '_')
const logFile = path.join(configService.getBasePath(), 'logs', `${sanitizedServiceName}_error.log`) const logFile = path.join(configService.getBasePath(), 'logs', `${sanitizedServiceName}_error.log`)
const configPath = await configService.generateConfig('my.ini.template', `my_${mysqlId || 'default'}.ini`, { const configPath = await configService.generateConfig('mariadb.ini.template', `mariadb_${dbId || 'default'}.ini`, {
PORT: mysqlPort, PORT: dbPort,
DATADIR: dataDir.replace(/\\/g, '/'), DATADIR: dataDir.replace(/\\/g, '/'),
SOCKET: `/tmp/${sanitizedServiceName}.sock`, SOCKET: `/tmp/${sanitizedServiceName}.sock`,
LOG_FILE: logFile.replace(/\\/g, '/') LOG_FILE: logFile.replace(/\\/g, '/')
@@ -258,18 +260,18 @@ export function registerIpcHandlers(): void {
const success = await processManager.startService(serviceName, binPath, ['--defaults-file=' + configPath], false) const success = await processManager.startService(serviceName, binPath, ['--defaults-file=' + configPath], false)
if (success) { if (success) {
// Quick health check for MySQL // Quick health check
await new Promise(r => setTimeout(r, 1500)) await new Promise(r => setTimeout(r, 1500))
if (!processManager.isServiceRunning(serviceName)) { if (!processManager.isServiceRunning(serviceName)) {
const errorLogs = await processManager.getLastErrorLogs(serviceName) const errorLogs = await processManager.getLastErrorLogs(serviceName)
return { success: false, message: `${mysqlVersion || ''} MySQL başlatıldı ancak çalışma hatası verdi (ExitCode: 1).\n\n${errorLogs.length > 0 ? 'Önemli Hata Logları:\n' + errorLogs.join('\n') : 'Lütfen log sekmesini kontrol edin.'}` } return { success: false, message: `${dbVersion || ''} MariaDB başlatıldı ancak çalışma hatası verdi (ExitCode: 1).\n\n${errorLogs.length > 0 ? 'Önemli Hata Logları:\n' + errorLogs.join('\n') : 'Lütfen log sekmesini kontrol edin.'}` }
} }
} else { } else {
const errorLogs = await processManager.getLastErrorLogs(serviceName) const errorLogs = await processManager.getLastErrorLogs(serviceName)
return { success: false, message: `${mysqlVersion || ''} MySQL başlatılamadı.\n\n${errorLogs.join('\n')}` } return { success: false, message: `${dbVersion || ''} MariaDB başlatılamadı.\n\n${errorLogs.join('\n')}` }
} }
return { success, message: success ? `${mysqlVersion || ''} MySQL başlatıldı.` : `${mysqlVersion || ''} MySQL devreye alınamadı.` } return { success, message: success ? `${dbVersion || ''} MariaDB başlatıldı.` : `${dbVersion || ''} MariaDB devreye alınamadı.` }
} }
return { success: false, message: 'Bilinmeyen servis.' } return { success: false, message: 'Bilinmeyen servis.' }
@@ -320,30 +322,30 @@ export function registerIpcHandlers(): void {
} }
}) })
// MySQL Versions // MariaDB Versions
ipcMain.handle('mysql:versions', async () => { ipcMain.handle('mariadb:versions', async () => {
return await mySqlManagerService.getVersions() return await mariaDbManagerService.getVersions()
}) })
ipcMain.handle('mysql:download', async (event, id: string) => { ipcMain.handle('mariadb:download', async (event, id: string) => {
const versions = await mySqlManagerService.getVersions() const versions = await mariaDbManagerService.getVersions()
const version = versions.find(v => v.id === id) const version = versions.find(v => v.id === id)
if (!version) return { success: false, message: 'Geçersiz MySQL sürümü.' } if (!version) return { success: false, message: 'Geçersiz MariaDB sürümü.' }
try { try {
mySqlManagerService.updateProgress(id, 0, 'downloading') mariaDbManagerService.updateProgress(id, 0, 'downloading')
event.sender.send('mysql:progress', { id, progress: 0 }) event.sender.send('mariadb:progress', { id, progress: 0 })
await downloadService.downloadAndExtract(version.url, id, (p) => { await downloadService.downloadAndExtract(version.url, id, (p) => {
mySqlManagerService.updateProgress(id, p) mariaDbManagerService.updateProgress(id, p)
event.sender.send('mysql:progress', { id, progress: p }) event.sender.send('mariadb:progress', { id, progress: p })
}) })
mySqlManagerService.updateProgress(id, 100, 'installed') mariaDbManagerService.updateProgress(id, 100, 'installed')
return { success: true, message: 'MySQL indirme tamamlandı.' } return { success: true, message: 'MariaDB indirme tamamlandı.' }
} catch (error: any) { } catch (error: any) {
mySqlManagerService.updateProgress(id, 0, 'available') mariaDbManagerService.updateProgress(id, 0, 'available')
return { success: false, message: `MySQL hatası: ${error.message}` } return { success: false, message: `MariaDB hatası: ${error.message}` }
} }
}) })
@@ -565,8 +567,8 @@ export function registerIpcHandlers(): void {
return await phpMyAdminService.isInstalled() return await phpMyAdminService.isInstalled()
}) })
ipcMain.handle('pma:setup', async (event, mysqlPort: string) => { ipcMain.handle('pma:setup', async (event, mariaDbPort: string) => {
return await phpMyAdminService.setup(mysqlPort, (p) => { return await phpMyAdminService.setup(mariaDbPort, (p) => {
event.sender.send('pma:progress', p) event.sender.send('pma:progress', p)
}) })
}) })
@@ -575,13 +577,13 @@ export function registerIpcHandlers(): void {
try { try {
await processManager.forceKillAll(serviceName) await processManager.forceKillAll(serviceName)
const versionMatch = serviceName.match(/^mysql:(.+)$/) const versionMatch = serviceName.match(/^(?:mariadb|mysql):(.+)$/)
const mysqlVersion = versionMatch ? versionMatch[1] : null const dbVersion = versionMatch ? versionMatch[1] : null
const mysqlId = mysqlVersion ? `mysql-${mysqlVersion}` : null const dbId = dbVersion ? `mariadb-${dbVersion}` : null
if (!mysqlId) return { success: false, message: 'Geçersiz servis adı.' } if (!dbId) return { success: false, message: 'Geçersiz servis adı.' }
const dataDir = path.join(configService.getBasePath(), 'data', mysqlId) const dataDir = path.join(configService.getBasePath(), 'data', dbId)
if (fs.existsSync(dataDir)) { if (fs.existsSync(dataDir)) {
fs.rmSync(dataDir, { recursive: true, force: true }) fs.rmSync(dataDir, { recursive: true, force: true })
} }
@@ -591,16 +593,18 @@ export function registerIpcHandlers(): void {
} }
}) })
ipcMain.handle('mysql:import-wizard:check-db', async (_event, { versionId, dbName, rootPass }) => { // MariaDB Operations
const versions = await mySqlManagerService.getVersions() ipcMain.handle('mariadb:import-wizard:check-db', async (_event, { versionId, dbName, rootPass }) => {
const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId) const v = versions.find(v => v.id === versionId)
if (!v) return { exists: false, error: 'MySQL version not found' } if (!v) return { exists: false, error: 'MariaDB version not found' }
const binDir = path.join(configService.getBasePath(), 'bin', versionId) const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const binPath = findExecutable(binDir, 'mysql.exe') const isWindows = process.platform === 'win32'
if (!binPath) return { exists: false, error: 'mysql.exe not found' } const binPath = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!binPath) return { exists: false, error: 'mariadb/mysql executable not found' }
const exists = await mySqlOperationService.checkDatabaseExists({ const exists = await mariaDbOperationService.checkDatabaseExists({
binPath, binPath,
host: '127.0.0.1', host: '127.0.0.1',
port: v.port, port: v.port,
@@ -611,33 +615,34 @@ export function registerIpcHandlers(): void {
return { exists } return { exists }
}) })
ipcMain.handle('mysql:import-wizard:start', async (event, { versionId, filePath, dbName, user, pass, rootPass, overwrite }) => { ipcMain.handle('mariadb:import-wizard:start', async (event, { versionId, filePath, dbName, user, pass, rootPass, overwrite }) => {
const versions = await mySqlManagerService.getVersions() const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId) const v = versions.find(v => v.id === versionId)
if (!v) return { success: false, message: 'MySQL version not found' } if (!v) return { success: false, message: 'MariaDB version not found' }
const binDir = path.join(configService.getBasePath(), 'bin', versionId) const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const mysqlBin = findExecutable(binDir, 'mysql.exe') const isWindows = process.platform === 'win32'
if (!mysqlBin) return { success: false, message: 'mysql.exe not found' } const dbBin = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!dbBin) return { success: false, message: 'mariadb/mysql executable not found' }
const config = { const config = {
binPath: mysqlBin, binPath: dbBin,
host: '127.0.0.1', host: '127.0.0.1',
port: v.port, port: v.port,
rootUser: 'root', rootUser: 'root',
rootPass: rootPass || '' rootPass: rootPass || ''
} }
const log = (msg: string) => event.sender.send('mysql:import:log', msg) const log = (msg: string) => event.sender.send('mariadb:import:log', msg)
try { try {
if (overwrite) { if (overwrite) {
log(`Dropping existing database: ${dbName}`) log(`Dropping existing database: ${dbName}`)
await mySqlOperationService.dropDatabase(config, dbName) await mariaDbOperationService.dropDatabase(config, dbName)
} }
log(`Creating database and user: ${dbName} / ${user}`) log(`Creating database and user: ${dbName} / ${user}`)
const createRes = await mySqlOperationService.createDatabaseAndUser(config, dbName, user, pass) const createRes = await mariaDbOperationService.createDatabaseAndUser(config, dbName, user, pass)
if (!createRes.success) return createRes if (!createRes.success) return createRes
let sqlFiles: string[] = [] let sqlFiles: string[] = []
@@ -645,14 +650,14 @@ export function registerIpcHandlers(): void {
sqlFiles = [filePath] sqlFiles = [filePath]
} else { } else {
log(`Extracting archive: ${path.basename(filePath)}`) log(`Extracting archive: ${path.basename(filePath)}`)
const extractDir = path.join(app.getPath('temp'), `mysql_import_${Date.now()}`) const extractDir = path.join(app.getPath('temp'), `mariadb_import_${Date.now()}`)
sqlFiles = await mySqlOperationService.extractArchive(filePath, extractDir) sqlFiles = await mariaDbOperationService.extractArchive(filePath, extractDir)
if (sqlFiles.length === 0) return { success: false, message: 'No .sql files found in archive.' } if (sqlFiles.length === 0) return { success: false, message: 'No .sql files found in archive.' }
} }
for (const sqlFile of sqlFiles) { for (const sqlFile of sqlFiles) {
log(`Importing: ${path.basename(sqlFile)}`) log(`Importing: ${path.basename(sqlFile)}`)
const importRes = await mySqlOperationService.importSql(config, dbName, sqlFile, (msg) => log(msg)) const importRes = await mariaDbOperationService.importSql(config, dbName, sqlFile, (msg) => log(msg))
if (!importRes.success) return importRes if (!importRes.success) return importRes
} }
@@ -662,17 +667,18 @@ export function registerIpcHandlers(): void {
} }
}) })
ipcMain.handle('mysql:export-wizard:list-dbs', async (_event, { versionId, rootPass }) => { ipcMain.handle('mariadb:export-wizard:list-dbs', async (_event, { versionId, rootPass }) => {
const versions = await mySqlManagerService.getVersions() const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId) const v = versions.find(v => v.id === versionId)
if (!v) return [] if (!v) return []
const binDir = path.join(configService.getBasePath(), 'bin', versionId) const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const mysqlBin = findExecutable(binDir, 'mysql.exe') const isWindows = process.platform === 'win32'
if (!mysqlBin) return [] const dbBin = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!dbBin) return []
return await mySqlOperationService.listDatabases({ return await mariaDbOperationService.listDatabases({
binPath: mysqlBin, binPath: dbBin,
host: '127.0.0.1', host: '127.0.0.1',
port: v.port, port: v.port,
rootUser: 'root', rootUser: 'root',
@@ -680,19 +686,20 @@ export function registerIpcHandlers(): void {
}) })
}) })
ipcMain.handle('mysql:export-wizard:start', async (event, { versionId, dbName, savePath, compress, rootPass }) => { ipcMain.handle('mariadb:export-wizard:start', async (event, { versionId, dbName, savePath, compress, rootPass }) => {
const versions = await mySqlManagerService.getVersions() const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId) const v = versions.find(v => v.id === versionId)
if (!v) return { success: false, message: 'MySQL version not found' } if (!v) return { success: false, message: 'MariaDB version not found' }
const binDir = path.join(configService.getBasePath(), 'bin', versionId) const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const mysqlBin = findExecutable(binDir, 'mysql.exe') const isWindows = process.platform === 'win32'
if (!mysqlBin) return { success: false, message: 'mysql.exe not found' } const dbBin = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!dbBin) return { success: false, message: 'mariadb/mysql executable not found' }
const log = (msg: string) => event.sender.send('mysql:export:log', msg) const log = (msg: string) => event.sender.send('mariadb:export:log', msg)
return await mySqlOperationService.exportSql({ return await mariaDbOperationService.exportSql({
binPath: mysqlBin, binPath: dbBin,
host: '127.0.0.1', host: '127.0.0.1',
port: v.port, port: v.port,
rootUser: 'root', rootUser: 'root',
+41
View File
@@ -0,0 +1,41 @@
{
"mariadb_release_catalog": {
"generated_date": "2026-03-30",
"provider": "MariaDB Foundation",
"releases": [
{
"series": "11.4 (LTS)",
"version": "11.4.2",
"status": "Stable (LTS)",
"platforms": {
"win32": {
"x64": "https://archive.mariadb.org/mariadb-11.4.2/winx64-packages/mariadb-11.4.2-winx64.zip"
},
"darwin": {
"x64": "https://archive.mariadb.org/mariadb-11.4.2/bintar-macosx-x86_64/mariadb-11.4.2-macosx-x86_64.tar.gz",
"arm64": "https://archive.mariadb.org/mariadb-11.4.2/bintar-macosx-arm64/mariadb-11.4.2-macosx-arm64.tar.gz"
},
"linux": {
"x64": "https://archive.mariadb.org/mariadb-11.4.2/bintar-linux-systemd-x86_64/mariadb-11.4.2-linux-systemd-x86_64.tar.gz"
}
}
},
{
"series": "10.11 (LTS)",
"version": "10.11.8",
"status": "Stable (LTS)",
"platforms": {
"win32": {
"x64": "https://archive.mariadb.org/mariadb-10.11.8/winx64-packages/mariadb-10.11.8-winx64.zip"
},
"darwin": {
"x64": "https://archive.mariadb.org/mariadb-10.11.8/bintar-macosx-x86_64/mariadb-10.11.8-macosx-x86_64.tar.gz"
},
"linux": {
"x64": "https://archive.mariadb.org/mariadb-10.11.8/bintar-linux-systemd-x86_64/mariadb-10.11.8-linux-systemd-x86_64.tar.gz"
}
}
}
]
}
}
-61
View File
@@ -1,61 +0,0 @@
{
"mysql_release_catalog": {
"generated_date": "2026-03-28",
"provider": "Oracle MySQL",
"releases": [
{
"series": "9.x (Innovation)",
"version": "9.2.0",
"status": "Latest Innovation",
"php_min_recommended": "PHP 8.2+",
"download_url": "https://dev.mysql.com/downloads/mysql/",
"is_archive": false
},
{
"series": "8.4 (LTS)",
"version": "8.4.2",
"status": "Current LTS",
"php_min_recommended": "PHP 8.1+",
"download_url": "https://dev.mysql.com/downloads/mysql/8.4.html",
"is_archive": false
},
{
"series": "8.0",
"version": "8.0.40",
"status": "End of Life (April 2026)",
"php_min_recommended": "PHP 7.4 - 8.3",
"download_url": "https://downloads.mysql.com/archives/community/?product=community&version=8.0.40",
"is_archive": true
},
{
"series": "5.7",
"version": "5.7.44",
"status": "End of Life (2023)",
"php_min_recommended": "PHP 5.6 - 7.4",
"download_url": "https://downloads.mysql.com/archives/community/?product=community&version=5.7.44",
"is_archive": true
},
{
"series": "5.6",
"version": "5.6.51",
"status": "Legacy / End of Life",
"php_min_recommended": "PHP 5.5 - 5.6",
"download_url": "https://downloads.mysql.com/archives/community/?product=community&version=5.6.51",
"is_archive": true
},
{
"series": "5.5",
"version": "5.5.62",
"status": "Legacy / End of Life",
"php_min_recommended": "PHP 5.4 - 5.6",
"download_url": "https://downloads.mysql.com/archives/community/?product=community&version=5.5.62",
"is_archive": true
}
],
"useful_links": {
"full_archive": "https://downloads.mysql.com/archives/community/",
"yum_repo": "https://dev.mysql.com/downloads/repo/yum/",
"apt_repo": "https://dev.mysql.com/downloads/repo/apt/"
}
}
}
+2 -2
View File
@@ -9,8 +9,8 @@ export class ConfigService {
private defaultSettings = { private defaultSettings = {
nginxPort: '80', nginxPort: '80',
phpPort: '9001', phpPort: '9001',
mysqlPort: '3306', mariaDbPort: '3306',
mysqlPorts: {} as Record<string, string>, mariaDbPorts: {} as Record<string, string>,
language: 'tr' language: 'tr'
} }
+6 -2
View File
@@ -17,7 +17,9 @@ export class DownloadService {
} }
async downloadAndExtract(url: string, targetName: string, onProgress?: (p: number) => void, customTargetDir?: string): Promise<string> { async downloadAndExtract(url: string, targetName: string, onProgress?: (p: number) => void, customTargetDir?: string): Promise<string> {
const tempFile = path.join(app.getPath('temp'), `${targetName}.zip`) const isTarGz = url.toLowerCase().endsWith('.tar.gz') || url.toLowerCase().endsWith('.tgz');
const extension = isTarGz ? '.tar.gz' : '.zip';
const tempFile = path.join(app.getPath('temp'), `${targetName}${extension}`)
const targetDir = customTargetDir || path.join(this.binDir, targetName) const targetDir = customTargetDir || path.join(this.binDir, targetName)
// Cleanup failed/previous attempts if we want a fresh start // Cleanup failed/previous attempts if we want a fresh start
@@ -70,7 +72,9 @@ export class DownloadService {
} }
getBinPath(service: string, executable: string): string { getBinPath(service: string, executable: string): string {
return path.join(this.binDir, service, executable) const isWindows = process.platform === 'win32';
const fullExecutable = isWindows ? (executable.endsWith('.exe') ? executable : `${executable}.exe`) : executable;
return path.join(this.binDir, service, fullExecutable)
} }
} }
@@ -3,7 +3,7 @@ import path from 'path'
import { app } from 'electron' import { app } from 'electron'
import { configService } from './ConfigService' import { configService } from './ConfigService'
export interface MySqlVersion { export interface MariaDbVersion {
id: string id: string
version: string version: string
cycle: string cycle: string
@@ -14,16 +14,16 @@ export interface MySqlVersion {
description?: string description?: string
} }
export class MySqlManagerService { export class MariaDbManagerService {
private versions: MySqlVersion[] = [] private versions: MariaDbVersion[] = []
private binDir: string private binDir: string
private catalogPath: string private catalogPath: string
constructor() { constructor() {
this.binDir = path.join(configService.getBasePath(), 'bin') this.binDir = path.join(configService.getBasePath(), 'bin')
this.catalogPath = path.join(app.getAppPath(), 'src/main/resources/mysql_catalog.json') this.catalogPath = path.join(app.getAppPath(), 'src/main/resources/mariadb_catalog.json')
if (!fs.existsSync(this.catalogPath)) { if (!fs.existsSync(this.catalogPath)) {
this.catalogPath = path.join(process.cwd(), 'src/main/resources/mysql_catalog.json') this.catalogPath = path.join(process.cwd(), 'src/main/resources/mariadb_catalog.json')
} }
this.init() this.init()
} }
@@ -34,54 +34,60 @@ export class MySqlManagerService {
} }
} }
async fetchAvailableVersions(): Promise<MySqlVersion[]> { async fetchAvailableVersions(): Promise<MariaDbVersion[]> {
console.log('Loading MySQL versions from local catalog:', this.catalogPath) console.log('Loading MariaDB versions from local catalog:', this.catalogPath)
try { try {
if (!fs.existsSync(this.catalogPath)) { if (!fs.existsSync(this.catalogPath)) {
console.error('MySQL Catalog not found at:', this.catalogPath) console.error('MariaDB Catalog not found at:', this.catalogPath)
return this.versions return this.versions
} }
const rawData = fs.readFileSync(this.catalogPath, 'utf8') const rawData = fs.readFileSync(this.catalogPath, 'utf8')
const catalog = JSON.parse(rawData) const catalog = JSON.parse(rawData)
const releases = catalog.mysql_release_catalog.releases as any[] const releases = catalog.mariadb_release_catalog.releases as any[]
const dynamicVersions: MySqlVersion[] = releases.map((v, index) => { const platform = process.platform; // win32, darwin, linux
const arch = process.arch; // x64, arm64
const dynamicVersions: MariaDbVersion[] = releases.map((v, index) => {
const version = v.version const version = v.version
const cycle = v.series const cycle = v.series
// Extract MAJOR.MINOR (e.g. 8.0 from 8.0.40) // Resolve URL based on platform and architecture
const versionParts = version.split('.') let url = '';
const majorMinor = `${versionParts[0]}.${versionParts[1]}` if (v.platforms && v.platforms[platform]) {
url = v.platforms[platform][arch] || v.platforms[platform]['x64'] || '';
}
// Working CDN Pattern: https://cdn.mysql.com/archives/mysql-MAJOR.MINOR/mysql-VERSION-winx64.zip if (!url) {
const url = `https://cdn.mysql.com/archives/mysql-${majorMinor}/mysql-${version}-winx64.zip` console.warn(`No MariaDB URL found for platform ${platform}/${arch} in version ${version}`);
}
const settings = configService.getSettings() const settings = configService.getSettings()
const defaultPort = (3306 + index).toString() const defaultPort = (3306 + index).toString()
const customPort = settings.mysqlPorts?.[`mysql-${version}`] || defaultPort const customPort = settings.mariaDbPorts?.[`mariadb-${version}`] || defaultPort
return { return {
id: `mysql-${version}`, id: `mariadb-${version}`,
version, version,
cycle, cycle,
url, url,
status: 'available', status: 'available' as MariaDbVersion['status'],
progress: 0, progress: 0,
port: customPort, port: customPort,
description: v.status description: v.status
} }
}) }).filter(v => v.url !== '') // Only include versions available for this platform
this.updateVersionsList(dynamicVersions) this.updateVersionsList(dynamicVersions)
return this.versions return this.versions
} catch (e: any) { } catch (e: any) {
console.error('Failed to load MySQL catalog:', e.message) console.error('Failed to load MariaDB catalog:', e.message)
return this.versions return this.versions
} }
} }
private updateVersionsList(newList: MySqlVersion[]) { private updateVersionsList(newList: MariaDbVersion[]) {
this.versions = newList.map(dv => { this.versions = newList.map(dv => {
const current = this.versions.find(cv => cv.id === dv.id) const current = this.versions.find(cv => cv.id === dv.id)
if (current && current.status === 'downloading') return current if (current && current.status === 'downloading') return current
@@ -92,14 +98,19 @@ export class MySqlManagerService {
} }
private refreshInstalledStatus() { private refreshInstalledStatus() {
const isWindows = process.platform === 'win32';
const binaryName = isWindows ? 'mariadbd.exe' : 'mariadbd';
this.versions = this.versions.map(v => { this.versions = this.versions.map(v => {
const targetDir = path.join(this.binDir, v.id) const targetDir = path.join(this.binDir, v.id)
const possiblePaths = [ const possibleBinPaths = [
path.join(targetDir, 'bin', 'mysqld.exe'), path.join(targetDir, 'bin', binaryName),
path.join(targetDir, `mysql-${v.version}-winx64`, 'bin', 'mysqld.exe') // Some MariaDB distributions have nested folders after zip/tar extraction
] path.join(targetDir, `mariadb-${v.version}-${isWindows ? 'winx64' : (process.arch === 'arm64' ? 'macosx-arm64' : 'macosx-x86_64')}`, 'bin', binaryName),
path.join(targetDir, `mariadb-${v.version}-${isWindows ? 'winx64' : 'linux-systemd-x86_64'}`, 'bin', binaryName)
];
const isInstalled = possiblePaths.some(p => fs.existsSync(p)) const isInstalled = possibleBinPaths.some(p => fs.existsSync(p));
if (isInstalled) { if (isInstalled) {
return { ...v, status: 'installed', progress: 100 } return { ...v, status: 'installed', progress: 100 }
@@ -108,7 +119,7 @@ export class MySqlManagerService {
}) })
} }
async getVersions(): Promise<MySqlVersion[]> { async getVersions(): Promise<MariaDbVersion[]> {
if (this.versions.length === 0) { if (this.versions.length === 0) {
await this.fetchAvailableVersions() await this.fetchAvailableVersions()
} else { } else {
@@ -117,7 +128,7 @@ export class MySqlManagerService {
return this.versions return this.versions
} }
updateProgress(id: string, progress: number, status: MySqlVersion['status'] = 'downloading') { updateProgress(id: string, progress: number, status: MariaDbVersion['status'] = 'downloading') {
const version = this.versions.find(v => v.id === id) const version = this.versions.find(v => v.id === id)
if (version) { if (version) {
version.progress = progress version.progress = progress
@@ -126,4 +137,4 @@ export class MySqlManagerService {
} }
} }
export const mySqlManagerService = new MySqlManagerService() export const mariaDbManagerService = new MariaDbManagerService()
@@ -4,7 +4,7 @@ import { spawn } from 'child_process'
import decompress from 'decompress' import decompress from 'decompress'
import zlib from 'zlib' import zlib from 'zlib'
export interface MySqlConfig { export interface MariaDbConfig {
binPath: string binPath: string
host: string host: string
port: string port: string
@@ -12,7 +12,7 @@ export interface MySqlConfig {
rootPass: string rootPass: string
} }
export class MySqlOperationService { export class MariaDbOperationService {
async extractArchive(archivePath: string, targetDir: string): Promise<string[]> { async extractArchive(archivePath: string, targetDir: string): Promise<string[]> {
if (!fs.existsSync(targetDir)) { if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true }) fs.mkdirSync(targetDir, { recursive: true })
@@ -58,8 +58,9 @@ export class MySqlOperationService {
return sqlFiles return sqlFiles
} }
async runQuery(config: MySqlConfig, query: string, database?: string): Promise<{ success: boolean; output: string; error?: string }> { async runQuery(config: MariaDbConfig, query: string, database?: string): Promise<{ success: boolean; output: string; error?: string }> {
return new Promise((resolve) => { return new Promise((resolve) => {
const mariaDbPath = config.binPath;
const args = [ const args = [
`--host=${config.host}`, `--host=${config.host}`,
`--port=${config.port}`, `--port=${config.port}`,
@@ -73,13 +74,17 @@ export class MySqlOperationService {
args.push('-e', query) args.push('-e', query)
const child = spawn(config.binPath, args, { windowsHide: true }) const child = spawn(mariaDbPath, args, { windowsHide: true })
let output = '' let output = ''
let error = '' let error = ''
child.stdout.on('data', (data) => output += data.toString()) child.stdout.on('data', (data) => output += data.toString())
child.stderr.on('data', (data) => error += data.toString()) child.stderr.on('data', (data) => error += data.toString())
child.on('error', (err) => {
resolve({ success: false, output, error: err.message })
})
child.on('close', (code) => { child.on('close', (code) => {
if (code === 0) { if (code === 0) {
resolve({ success: true, output }) resolve({ success: true, output })
@@ -90,12 +95,12 @@ export class MySqlOperationService {
}) })
} }
async checkDatabaseExists(config: MySqlConfig, dbName: string): Promise<boolean> { async checkDatabaseExists(config: MariaDbConfig, dbName: string): Promise<boolean> {
const result = await this.runQuery(config, `SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '${dbName}'`) const result = await this.runQuery(config, `SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '${dbName}'`)
return result.success && result.output.includes(dbName) return result.success && result.output.includes(dbName)
} }
async createDatabaseAndUser(config: MySqlConfig, dbName: string, user: string, pass: string): Promise<{ success: boolean; message: string }> { async createDatabaseAndUser(config: MariaDbConfig, dbName: string, user: string, pass: string): Promise<{ success: boolean; message: string }> {
const queries = [ const queries = [
`CREATE DATABASE IF NOT EXISTS \`${dbName}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`, `CREATE DATABASE IF NOT EXISTS \`${dbName}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`,
`CREATE USER IF NOT EXISTS '${user}'@'%' IDENTIFIED BY '${pass}';`, `CREATE USER IF NOT EXISTS '${user}'@'%' IDENTIFIED BY '${pass}';`,
@@ -113,14 +118,14 @@ export class MySqlOperationService {
return { success: true, message: 'Database and user created successfully.' } return { success: true, message: 'Database and user created successfully.' }
} }
async dropDatabase(config: MySqlConfig, dbName: string): Promise<{ success: boolean; message: string }> { async dropDatabase(config: MariaDbConfig, dbName: string): Promise<{ success: boolean; message: string }> {
const result = await this.runQuery(config, `DROP DATABASE IF EXISTS \`${dbName}\``) const result = await this.runQuery(config, `DROP DATABASE IF EXISTS \`${dbName}\``)
return { success: result.success, message: result.success ? 'Database dropped' : `Drop failed: ${result.error}` } return { success: result.success, message: result.success ? 'Database dropped' : `Drop failed: ${result.error}` }
} }
async importSql(config: MySqlConfig, dbName: string, sqlFilePath: string, onProgress?: (log: string) => void): Promise<{ success: boolean; message: string }> { async importSql(config: MariaDbConfig, dbName: string, sqlFilePath: string, onProgress?: (log: string) => void): Promise<{ success: boolean; message: string }> {
return new Promise((resolve) => { return new Promise((resolve) => {
const mysqlPath = config.binPath const mariaDbPath = config.binPath
const args = [ const args = [
`--host=${config.host}`, `--host=${config.host}`,
`--port=${config.port}`, `--port=${config.port}`,
@@ -132,9 +137,9 @@ export class MySqlOperationService {
args.push(`--password=${config.rootPass}`) args.push(`--password=${config.rootPass}`)
} }
onProgress?.(`Executing: "${mysqlPath}" ${args.join(' ')} < "${sqlFilePath}"`) onProgress?.(`Executing: "${mariaDbPath}" ${args.join(' ')} < "${sqlFilePath}"`)
const child = spawn(mysqlPath, args, { windowsHide: true }) const child = spawn(mariaDbPath, args, { windowsHide: true })
// Pipe the file content to stdin // Pipe the file content to stdin
const fileStream = fs.createReadStream(sqlFilePath) const fileStream = fs.createReadStream(sqlFilePath)
@@ -163,7 +168,7 @@ export class MySqlOperationService {
}) })
} }
async listDatabases(config: MySqlConfig): Promise<string[]> { async listDatabases(config: MariaDbConfig): Promise<string[]> {
const result = await this.runQuery(config, 'SHOW DATABASES') const result = await this.runQuery(config, 'SHOW DATABASES')
if (!result.success) return [] if (!result.success) return []
@@ -173,9 +178,23 @@ export class MySqlOperationService {
.filter(line => line && line !== 'Database' && !line.match(/^information_schema|performance_schema|mysql|sys$/i)) .filter(line => line && line !== 'Database' && !line.match(/^information_schema|performance_schema|mysql|sys$/i))
} }
async exportSql(config: MySqlConfig, dbName: string, targetPath: string, compress: boolean, onProgress?: (log: string) => void): Promise<{ success: boolean; message: string }> { async exportSql(config: MariaDbConfig, dbName: string, targetPath: string, compress: boolean, onProgress?: (log: string) => void): Promise<{ success: boolean; message: string }> {
return new Promise((resolve) => { return new Promise((resolve) => {
const dumpPath = config.binPath.replace(/mysql\.exe$/i, 'mysqldump.exe') const isWindows = process.platform === 'win32';
const binDir = path.dirname(config.binPath);
const dumpBinary = isWindows ? 'mariadb-dump.exe' : 'mariadb-dump';
const mysqlDumpFallback = isWindows ? 'mysqldump.exe' : 'mysqldump';
let dumpPath = path.join(binDir, dumpBinary);
if (!fs.existsSync(dumpPath)) {
dumpPath = path.join(binDir, mysqlDumpFallback);
}
if (!fs.existsSync(dumpPath)) {
onProgress?.(`Error: MariaDB/MySQL dump binary not found in ${binDir}`);
return resolve({ success: false, message: 'Dump binary (mariadb-dump or mysqldump) not found.' });
}
const args = [ const args = [
`--host=${config.host}`, `--host=${config.host}`,
`--port=${config.port}`, `--port=${config.port}`,
@@ -219,11 +238,11 @@ export class MySqlOperationService {
resolve({ success: true, message: 'Export completed successfully.' }) resolve({ success: true, message: 'Export completed successfully.' })
} else { } else {
onProgress?.(`Export failed with exit code ${code}`) onProgress?.(`Export failed with exit code ${code}`)
resolve({ success: false, message: `Export failed: ${errorOutput}` }) resolve({ success: false, message: `Export failed (Exit Code: ${code}).\n${errorOutput}` })
} }
}) })
}) })
} }
} }
export const mySqlOperationService = new MySqlOperationService() export const mariaDbOperationService = new MariaDbOperationService()
+4 -4
View File
@@ -15,7 +15,7 @@ export class PhpMyAdminService {
return fs.existsSync(path.join(this.pmaDir, 'index.php')) return fs.existsSync(path.join(this.pmaDir, 'index.php'))
} }
async setup(mysqlPort: string, onProgress?: (progress: number) => void): Promise<boolean> { async setup(mariaDbPort: string, onProgress?: (progress: number) => void): Promise<boolean> {
if (!fs.existsSync(this.pmaDir)) { if (!fs.existsSync(this.pmaDir)) {
fs.mkdirSync(this.pmaDir, { recursive: true }) fs.mkdirSync(this.pmaDir, { recursive: true })
} }
@@ -38,7 +38,7 @@ export class PhpMyAdminService {
} }
// Generate config.inc.php // Generate config.inc.php
this.generateConfig(mysqlPort) this.generateConfig(mariaDbPort)
return true return true
} catch (error) { } catch (error) {
@@ -47,7 +47,7 @@ export class PhpMyAdminService {
} }
} }
private generateConfig(mysqlPort: string = '3306') { private generateConfig(mariaDbPort: string = '3306') {
const configPath = path.join(this.pmaDir, 'config.inc.php') const configPath = path.join(this.pmaDir, 'config.inc.php')
const blowfishSecret = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) const blowfishSecret = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
@@ -56,7 +56,7 @@ $cfg['blowfish_secret'] = '${blowfishSecret}';
$i = 0; $i = 0;
$i++; $i++;
$cfg['Servers'][$i]['auth_type'] = 'config'; $cfg['Servers'][$i]['auth_type'] = 'config';
$cfg['Servers'][$i]['host'] = '127.0.0.1:${mysqlPort}'; $cfg['Servers'][$i]['host'] = '127.0.0.1:${mariaDbPort}';
$cfg['Servers'][$i]['user'] = 'root'; $cfg['Servers'][$i]['user'] = 'root';
$cfg['Servers'][$i]['password'] = ''; $cfg['Servers'][$i]['password'] = '';
$cfg['Servers'][$i]['extension'] = 'mysqli'; $cfg['Servers'][$i]['extension'] = 'mysqli';
+9 -9
View File
@@ -4,14 +4,14 @@ import { configService } from './ConfigService'
interface PortMap { interface PortMap {
php: Record<string, number>; php: Record<string, number>;
mysql: Record<string, number>; mariadb: Record<string, number>;
} }
export class PortMappingService { export class PortMappingService {
private configFile: string private configFile: string
private portMap: PortMap = { php: {}, mysql: {} } private portMap: PortMap = { php: {}, mariadb: {} }
private nextPhpPort = 9001 private nextPhpPort = 9001
private nextMysqlPort = 3306 private nextMariaDbPort = 3306
constructor() { constructor() {
this.configFile = path.join(configService.getBasePath(), 'port_mappings.json') this.configFile = path.join(configService.getBasePath(), 'port_mappings.json')
@@ -26,8 +26,8 @@ export class PortMappingService {
const phpPorts = Object.values(this.portMap.php) const phpPorts = Object.values(this.portMap.php)
if (phpPorts.length > 0) this.nextPhpPort = Math.max(...phpPorts) + 1 if (phpPorts.length > 0) this.nextPhpPort = Math.max(...phpPorts) + 1
const mysqlPorts = Object.values(this.portMap.mysql) const mariaDbPorts = Object.values(this.portMap.mariadb)
if (mysqlPorts.length > 0) this.nextMysqlPort = Math.max(...mysqlPorts) + 1 if (mariaDbPorts.length > 0) this.nextMariaDbPort = Math.max(...mariaDbPorts) + 1
} catch (e) { } catch (e) {
console.error('Failed to load port mappings', e) console.error('Failed to load port mappings', e)
} }
@@ -46,12 +46,12 @@ export class PortMappingService {
return this.portMap.php[version] return this.portMap.php[version]
} }
getMySqlPort(version: string): number { getMariaDbPort(version: string): number {
if (!this.portMap.mysql[version]) { if (!this.portMap.mariadb[version]) {
this.portMap.mysql[version] = this.nextMysqlPort++ this.portMap.mariadb[version] = this.nextMariaDbPort++
this.save() this.save()
} }
return this.portMap.mysql[version] return this.portMap.mariadb[version]
} }
} }
+94 -99
View File
@@ -22,7 +22,7 @@ export class ProcessManager {
fs.mkdirSync(this.logDir, { recursive: true }) fs.mkdirSync(this.logDir, { recursive: true })
} }
// Initialize with default logs // Initialize with default logs
['nginx', 'php', 'mysql'].forEach(s => { ['nginx', 'php', 'mariadb'].forEach(s => {
if (!this.logs.has(s)) { if (!this.logs.has(s)) {
this.logs.set(s, [`[${new Date().toLocaleTimeString()}] Sistem hazır. İşlem bekleniyor...`]) this.logs.set(s, [`[${new Date().toLocaleTimeString()}] Sistem hazır. İşlem bekleniyor...`])
} }
@@ -158,7 +158,16 @@ export class ProcessManager {
const child = this.processes.get(name) const child = this.processes.get(name)
if (child) { if (child) {
this.addLog(name, 'Stopping service...') this.addLog(name, 'Stopping service...')
child.kill() try {
if (process.platform === 'win32') {
child.kill()
} else {
// On Unix, try SIGTERM first, then SIGKILL if needed
child.kill('SIGTERM')
}
} catch (e) {
this.addLog(name, `Stop error: ${e instanceof Error ? e.message : String(e)}`)
}
this.processes.delete(name) this.processes.delete(name)
} }
@@ -167,117 +176,103 @@ export class ProcessManager {
} }
async forceKillAll(name: string): Promise<boolean> { async forceKillAll(name: string): Promise<boolean> {
const isWindows = process.platform === 'win32';
return new Promise((resolve) => { return new Promise((resolve) => {
const childProcess = this.processes.get(name) const childProcess = this.processes.get(name)
if (childProcess && childProcess.pid) {
this.addLog(name, `Killing process by PID: ${childProcess.pid}`)
const kill = spawn('taskkill', ['/F', '/PID', childProcess.pid.toString(), '/T'], {
windowsHide: true,
shell: false
})
kill.on('error', (err) => {
this.addLog(name, `PID Kill error: ${err.message}`)
this.processes.delete(name)
resolve(true)
})
kill.on('close', () => {
this.processes.delete(name)
resolve(true)
})
return
}
// Fallback: If it's a generic service name without version, or we just want to be sure if (isWindows) {
// For MySQL multi-instance, we should avoid global kill if possible. if (childProcess && childProcess.pid) {
const isVersionedMysql = name.startsWith('mysql:') this.addLog(name, `Killing process by PID: ${childProcess.pid}`)
const executableName = name === 'nginx' ? 'nginx.exe' : name === 'php' ? 'php-cgi.exe' : 'mysqld.exe' spawn('taskkill', ['/F', '/PID', childProcess.pid.toString(), '/T'], { windowsHide: true })
.on('close', () => { this.processes.delete(name); resolve(true) })
.on('error', () => { this.processes.delete(name); resolve(true) })
return
}
if (isVersionedMysql) { const isVersionedMariaDb = name.startsWith('mariadb:') || name.startsWith('mysql:')
// For versioned mysql, we try to find by command line (contains data directory name)
const mysqlId = name.replace('mysql:', 'mysql-') if (isVersionedMariaDb) {
this.addLog(name, `Attempting to kill specific MySQL instance: ${mysqlId}`) const dbVersion = name.split(':')[1]
// Using powershell to find and kill as wmic is deprecated/missing on some Win11 const dbId = `mariadb-${dbVersion}`
const psCommand = `Get-CimInstance Win32_Process -Filter "name = 'mysqld.exe' AND CommandLine LIKE '%mysql_data_${mysqlId}%'" | Invoke-CimMethod -MethodName Terminate`
const ps = spawn('powershell', ['-Command', psCommand], { if (process.platform === 'win32') {
windowsHide: true, const psCommand = `Get-CimInstance Win32_Process -Filter "name = 'mariadbd.exe' OR name = 'mysqld.exe'" | Where-Object { $_.CommandLine -like '*${dbId}*' } | Invoke-CimMethod -MethodName Terminate`
shell: false spawn('powershell', ['-Command', psCommand], { windowsHide: true })
}) .on('close', () => resolve(true))
ps.on('error', (err) => { .on('error', () => resolve(true))
this.addLog(name, `PowerShell kill error: ${err.message}`) } else {
resolve(true) // Unix: pkill -f matches full command line (data directory path usually contains the dbId)
}) spawn('pkill', ['-f', dbId], { windowsHide: true })
ps.on('close', () => resolve(true)) .on('close', () => resolve(true))
.on('error', () => resolve(true))
}
} else {
const isWindows = process.platform === 'win32';
if (isWindows) {
const executableName = name === 'nginx' ? 'nginx.exe' : name === 'php' ? 'php-cgi.exe' : 'mariadbd.exe'
spawn('taskkill', ['/F', '/IM', executableName, '/T'], { windowsHide: true })
.on('close', () => resolve(true))
.on('error', () => resolve(true))
} else {
const processName = name === 'nginx' ? 'nginx' : name === 'php' ? 'php-cgi' : 'mariadbd'
spawn('pkill', ['-f', processName], { windowsHide: true })
.on('close', () => resolve(true))
.on('error', () => resolve(true))
}
}
} else { } else {
this.addLog(name, `Force killing all ${executableName} processes...`) // macOS / Linux
const tk = spawn('taskkill', ['/F', '/IM', executableName, '/T'], { if (childProcess && childProcess.pid) {
windowsHide: true, try { process.kill(childProcess.pid, 'SIGKILL') } catch (e) { }
shell: false this.processes.delete(name)
}) }
tk.on('error', (err) => {
this.addLog(name, `Taskkill error: ${err.message}`) const procName = name === 'nginx' ? 'nginx' : name === 'php' ? 'php-fpm' : 'mariadbd'
resolve(true) spawn('pkill', ['-9', '-f', procName])
}) .on('close', () => resolve(true))
tk.on('close', () => resolve(true)) .on('error', () => resolve(true))
} }
}) })
} }
async killByPort(name: string, port: string | number): Promise<boolean> { async killByPort(name: string, port: string | number): Promise<boolean> {
const isWindows = process.platform === 'win32';
return new Promise((resolve) => { return new Promise((resolve) => {
if (!port) return resolve(true) if (!port) return resolve(true)
this.addLog(name, `Checking if port ${port} is in use...`) this.addLog(name, `Checking if port ${port} is in use...`)
// findstr will return lines containing the port and "LISTENING"
const cmd = `netstat -ano | findstr :${port} | findstr LISTENING`
const netstat = spawn('cmd.exe', ['/c', cmd], { windowsHide: true, shell: false })
let output = '' if (isWindows) {
netstat.stdout?.on('data', (data) => { const cmd = `netstat -ano | findstr :${port} | findstr LISTENING`
output += data.toString() const netstat = spawn('cmd.exe', ['/c', cmd], { windowsHide: true })
}) let output = ''
netstat.stdout?.on('data', d => output += d.toString())
netstat.on('close', () => { netstat.on('close', () => {
const lines = output.split('\n').filter(l => l.trim()) const lines = output.split('\n').filter(l => l.trim())
if (lines.length === 0) { if (lines.length === 0) return resolve(true)
this.addLog(name, `Port ${port} is free.`) const pids = new Set<string>()
return resolve(true) lines.forEach(line => {
} const parts = line.trim().split(/\s+/)
const pid = parts[parts.length - 1]
// netstat output format: TCP 0.0.0.0:3306 0.0.0.0:0 LISTENING 1234 if (pid && !isNaN(parseInt(pid))) pids.add(pid)
// We want the last part, which is the PID
const pids = new Set<string>()
lines.forEach(line => {
const parts = line.trim().split(/\s+/)
const pid = parts[parts.length - 1]
if (pid && !isNaN(parseInt(pid)) && parseInt(pid) > 0) {
pids.add(pid)
}
})
if (pids.size === 0) {
this.addLog(name, `Port ${port} in use but PID not found.`)
return resolve(true)
}
this.addLog(name, `Port ${port} is used by PIDs: ${Array.from(pids).join(', ')}. Terminating...`)
const killProcesses = Array.from(pids).map(pid => {
return new Promise<void>((res) => {
const tk = spawn('taskkill', ['/F', '/PID', pid, '/T'], { windowsHide: true, shell: false })
tk.on('close', () => res())
}) })
const kills = Array.from(pids).map(pid => new Promise<void>(res => {
spawn('taskkill', ['/F', '/PID', pid, '/T'], { windowsHide: true }).on('close', () => res())
}))
Promise.all(kills).then(() => resolve(true))
}) })
} else {
Promise.all(killProcesses).then(() => { const lsof = spawn('lsof', ['-i', `:${port}`, '-t'])
this.addLog(name, `Successfully cleared port ${port}.`) let output = ''
resolve(true) lsof.stdout?.on('data', d => output += d.toString())
lsof.on('close', () => {
const pids = output.split('\n').filter(l => l.trim())
if (pids.length === 0) return resolve(true)
const kills = pids.map(pid => new Promise<void>(res => {
spawn('kill', ['-9', pid]).on('close', () => res())
}))
Promise.all(kills).then(() => resolve(true))
}) })
}) }
netstat.on('error', (err) => {
this.addLog(name, `Port check error: ${err.message}`)
resolve(true)
})
}) })
} }
@@ -292,7 +287,7 @@ export class ProcessManager {
} }
// Ensure defaults are present if stopped // Ensure defaults are present if stopped
if (!statuses['nginx']) statuses['nginx'] = 'stopped' if (!statuses['nginx']) statuses['nginx'] = 'stopped'
if (!statuses['mysql']) statuses['mysql'] = 'stopped' if (!statuses['mariadb']) statuses['mariadb'] = 'stopped'
return statuses return statuses
} }
@@ -300,7 +295,7 @@ export class ProcessManager {
if (!this.logDir) return if (!this.logDir) return
const logPaths: Record<string, string> = { const logPaths: Record<string, string> = {
nginx: path.join(this.logDir, 'nginx_error.log'), nginx: path.join(this.logDir, 'nginx_error.log'),
mysql: path.join(this.logDir, 'mysql_error.log') mariadb: path.join(this.logDir, 'mariadb_error.log')
} }
const logPath = logPaths[name] const logPath = logPaths[name]
@@ -341,7 +336,7 @@ export class ProcessManager {
const possibleFiles = [ const possibleFiles = [
path.join(this.logDir, `${sanitizedName}.log`), path.join(this.logDir, `${sanitizedName}.log`),
path.join(this.logDir, `${sanitizedName}_error.log`), path.join(this.logDir, `${sanitizedName}_error.log`),
path.join(this.logDir, `mysql_error.log`), path.join(this.logDir, `mariadb_error.log`),
path.join(this.logDir, `nginx_error.log`) path.join(this.logDir, `nginx_error.log`)
] ]
+1 -1
View File
@@ -7,7 +7,7 @@ export interface Project {
name: string name: string
path: string path: string
phpVersion: string phpVersion: string
mySqlVersion: string mariaDbVersion: string
host: string host: string
} }
+15 -10
View File
@@ -16,9 +16,9 @@ const api = {
getPhpVersions: () => electronAPI.ipcRenderer.invoke('binaries:list-php'), getPhpVersions: () => electronAPI.ipcRenderer.invoke('binaries:list-php'),
downloadPhpVersion: (id: string) => electronAPI.ipcRenderer.invoke('binaries:download-php', id), downloadPhpVersion: (id: string) => electronAPI.ipcRenderer.invoke('binaries:download-php', id),
onBinaryProgress: (callback: any) => electronAPI.ipcRenderer.on('binaries:progress', (_event, data) => callback(data)), onBinaryProgress: (callback: any) => electronAPI.ipcRenderer.on('binaries:progress', (_event, data) => callback(data)),
getMySqlVersions: () => electronAPI.ipcRenderer.invoke('mysql:versions'), getMariaDbVersions: () => electronAPI.ipcRenderer.invoke('mariadb:versions'),
downloadMySqlVersion: (id: string) => electronAPI.ipcRenderer.invoke('mysql:download', id), downloadMariaDbVersion: (id: string) => electronAPI.ipcRenderer.invoke('mariadb:download', id),
onMySqlProgress: (callback: any) => electronAPI.ipcRenderer.on('mysql:progress', (_event, data) => callback(data)), onMariaDbProgress: (callback: any) => electronAPI.ipcRenderer.on('mariadb:progress', (_event, data) => callback(data)),
selectDirectory: () => electronAPI.ipcRenderer.invoke('dialog:select-directory'), selectDirectory: () => electronAPI.ipcRenderer.invoke('dialog:select-directory'),
getSettings: () => electronAPI.ipcRenderer.invoke('config:get'), getSettings: () => electronAPI.ipcRenderer.invoke('config:get'),
saveSettings: (settings: any) => electronAPI.ipcRenderer.invoke('config:save', settings), saveSettings: (settings: any) => electronAPI.ipcRenderer.invoke('config:save', settings),
@@ -26,15 +26,20 @@ const api = {
downloadNginxVersion: (id: string) => electronAPI.ipcRenderer.invoke('nginx:download', id), downloadNginxVersion: (id: string) => electronAPI.ipcRenderer.invoke('nginx:download', id),
onNginxProgress: (callback: any) => electronAPI.ipcRenderer.on('nginx:progress', (_event, data) => callback(data)), onNginxProgress: (callback: any) => electronAPI.ipcRenderer.on('nginx:progress', (_event, data) => callback(data)),
pmaStatus: () => electronAPI.ipcRenderer.invoke('pma:status'), pmaStatus: () => electronAPI.ipcRenderer.invoke('pma:status'),
pmaSetup: (mysqlPort: string) => electronAPI.ipcRenderer.invoke('pma:setup', mysqlPort), pmaSetup: (mariaDbPort: string) => electronAPI.ipcRenderer.invoke('pma:setup', mariaDbPort),
onPmaProgress: (callback: any) => electronAPI.ipcRenderer.on('pma:progress', (_event, data) => callback(data)), onPmaProgress: (callback: any) => electronAPI.ipcRenderer.on('pma:progress', (_event, data) => callback(data)),
selectFile: (filters?: any[]) => electronAPI.ipcRenderer.invoke('dialog:select-file', filters), selectFile: (filters?: any[]) => electronAPI.ipcRenderer.invoke('dialog:select-file', filters),
checkMySqlDb: (versionId: string, dbName: string) => electronAPI.ipcRenderer.invoke('mysql:import-wizard:check-db', { versionId, dbName }), checkMariaDbDb: (versionId: string, dbName: string) => electronAPI.ipcRenderer.invoke('mariadb:import-wizard:check-db', { versionId, dbName }),
startMySqlImport: (data: any) => electronAPI.ipcRenderer.invoke('mysql:import-wizard:start', data), startMariaDbImport: (data: any) => electronAPI.ipcRenderer.invoke('mariadb:import-wizard:start', data),
onMySqlImportLog: (callback: any) => { onMariaDbImportLog: (callback: any) => {
const listener = (_event, msg) => callback(msg) const listener = (_event, msg: any) => callback(msg)
electronAPI.ipcRenderer.on('mysql:import:log', listener) electronAPI.ipcRenderer.on('mariadb:import:log', listener)
return () => electronAPI.ipcRenderer.removeListener('mysql:import:log', listener) return () => electronAPI.ipcRenderer.removeListener('mariadb:import:log', listener)
},
onMariaDbExportLog: (callback: any) => {
const listener = (_event, msg: any) => callback(msg)
electronAPI.ipcRenderer.on('mariadb:export:log', listener)
return () => electronAPI.ipcRenderer.removeListener('mariadb:export:log', listener)
}, },
invoke: (channel: string, ...args: any[]) => electronAPI.ipcRenderer.invoke(channel, ...args) invoke: (channel: string, ...args: any[]) => electronAPI.ipcRenderer.invoke(channel, ...args)
} }
+14 -14
View File
@@ -1,17 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="UTF-8" />
<title>Trunçgil Multi-PHP & MySQL Server</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
/>
</head>
<body> <head>
<div id="root"></div> <meta charset="UTF-8" />
<script type="module" src="/src/main.tsx"></script> <title>Trunçgil Multi-PHP & MariaDB Server</title>
</body> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
</html> <meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+71 -71
View File
@@ -71,8 +71,8 @@ import {
} from '@mui/icons-material' } from '@mui/icons-material'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import MySqlImportWizard from './components/MySqlImportWizard' import MariaDbImportWizard from './components/MariaDbImportWizard'
import MySqlExportWizard from './components/MySqlExportWizard' import MariaDbExportWizard from './components/MariaDbExportWizard'
import NginxCodeEditor from './components/NginxCodeEditor' import NginxCodeEditor from './components/NginxCodeEditor'
declare global { declare global {
@@ -90,8 +90,8 @@ interface ServiceStatus {
interface AppSettings { interface AppSettings {
nginxPort: string nginxPort: string
phpPort: string phpPort: string
mysqlPort: string mariaDbPort: string
mysqlPorts?: Record<string, string> mariaDbPorts?: Record<string, string>
language: string language: string
} }
@@ -100,22 +100,22 @@ function App(): JSX.Element {
const [status, setStatus] = useState<ServiceStatus>({ const [status, setStatus] = useState<ServiceStatus>({
nginx: 'stopped', nginx: 'stopped',
php: 'stopped', php: 'stopped',
mysql: 'stopped' mariadb: 'stopped'
}) })
const [activeTab, setActiveTab] = useState('dashboard') const [activeTab, setActiveTab] = useState('dashboard')
const [settingsTab, setSettingsTab] = useState(0) const [settingsTab, setSettingsTab] = useState(0)
const [dashboardTab, setDashboardTab] = useState(0) const [dashboardTab, setDashboardTab] = useState(0)
const [projects, setProjects] = useState<any[]>([]) const [projects, setProjects] = useState<any[]>([])
const [phpVersions, setPhpVersions] = useState<any[]>([]) const [phpVersions, setPhpVersions] = useState<any[]>([])
const [mySqlVersions, setMySqlVersions] = useState<any[]>([]) const [mariaDbVersions, setMariaDbVersions] = useState<any[]>([])
const [nginxVersions, setNginxVersions] = useState<any[]>([]) const [nginxVersions, setNginxVersions] = useState<any[]>([])
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false) const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false)
const slugify = (text: string) => text.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, ''); const slugify = (text: string) => text.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, '');
const [newProject, setNewProject] = useState<any>({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' }) const [newProject, setNewProject] = useState<any>({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
const [settings, setSettings] = useState<AppSettings>({ const [settings, setSettings] = useState<AppSettings>({
nginxPort: '80', nginxPort: '80',
phpPort: '9001', phpPort: '9001',
mysqlPort: '3306', mariaDbPort: '3306',
language: 'tr' language: 'tr'
}) })
const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({ const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({
@@ -131,8 +131,8 @@ function App(): JSX.Element {
const [pmaInstalled, setPmaInstalled] = useState(false) const [pmaInstalled, setPmaInstalled] = useState(false)
const [pmaLoading, setPmaLoading] = useState(false) const [pmaLoading, setPmaLoading] = useState(false)
const [pmaProgress, setPmaProgress] = useState(0) const [pmaProgress, setPmaProgress] = useState(0)
const [isMySqlWizardOpen, setIsMySqlWizardOpen] = useState(false) const [isMariaDbWizardOpen, setIsMariaDbWizardOpen] = useState(false)
const [isMySqlExportWizardOpen, setIsMySqlExportWizardOpen] = 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 [isProjectNginxConfigOpen, setIsProjectNginxConfigOpen] = useState(false) const [isProjectNginxConfigOpen, setIsProjectNginxConfigOpen] = useState(false)
@@ -178,10 +178,10 @@ function App(): JSX.Element {
} }
} }
const fetchMySqlVersions = async () => { const fetchMariaDbVersions = async () => {
if (window.api && window.api.getMySqlVersions) { if (window.api && window.api.getMariaDbVersions) {
const list = await window.api.getMySqlVersions() const list = await window.api.getMariaDbVersions()
setMySqlVersions(list || []) setMariaDbVersions(list || [])
} }
} }
@@ -207,15 +207,15 @@ function App(): JSX.Element {
const installedPhp = phpVersions.find(v => v.status === 'installed') const installedPhp = phpVersions.find(v => v.status === 'installed')
if (installedPhp) updates.phpVersion = installedPhp.version if (installedPhp) updates.phpVersion = installedPhp.version
} }
if (!newProject.mySqlVersion && mySqlVersions.length > 0) { if (!newProject.mariaDbVersion && mariaDbVersions.length > 0) {
const installedMysql = mySqlVersions.find(v => v.status === 'installed') const installedMariaDb = mariaDbVersions.find(v => v.status === 'installed')
if (installedMysql) updates.mySqlVersion = installedMysql.version if (installedMariaDb) updates.mariaDbVersion = installedMariaDb.version
} }
if (Object.keys(updates).length > 0) { if (Object.keys(updates).length > 0) {
setNewProject(prev => ({ ...prev, ...updates })) setNewProject(prev => ({ ...prev, ...updates }))
} }
} }
}, [isProjectDialogOpen, phpVersions, mySqlVersions]) }, [isProjectDialogOpen, phpVersions, mariaDbVersions])
useEffect(() => { useEffect(() => {
if (!window.api) return if (!window.api) return
@@ -230,7 +230,7 @@ function App(): JSX.Element {
fetchStatus() fetchStatus()
fetchProjects() fetchProjects()
fetchPhpVersions() fetchPhpVersions()
fetchMySqlVersions() fetchMariaDbVersions()
fetchNginxVersions() fetchNginxVersions()
fetchSettings() fetchSettings()
fetchPmaStatus() fetchPmaStatus()
@@ -241,10 +241,10 @@ function App(): JSX.Element {
setPhpVersions(prev => prev.map(v => v.id === id ? { ...v, progress, status: progress === 100 ? 'installed' : 'downloading' } : v)) setPhpVersions(prev => prev.map(v => v.id === id ? { ...v, progress, status: progress === 100 ? 'installed' : 'downloading' } : v))
}) : null }) : null
const removeMySqlListener = window.api.onMySqlProgress ? window.api.onMySqlProgress((data) => { const removeMariaDbListener = window.api.onMariaDbProgress ? window.api.onMariaDbProgress((data) => {
if (!data) return if (!data) return
const { id, progress } = data const { id, progress } = data
setMySqlVersions(prev => prev.map(v => v.id === id ? { ...v, progress, status: progress === 100 ? 'installed' : 'downloading' } : v)) setMariaDbVersions(prev => prev.map(v => v.id === id ? { ...v, progress, status: progress === 100 ? 'installed' : 'downloading' } : v))
}) : null }) : null
const removeNginxListener = window.api.onNginxProgress ? window.api.onNginxProgress((data) => { const removeNginxListener = window.api.onNginxProgress ? window.api.onNginxProgress((data) => {
@@ -261,7 +261,7 @@ function App(): JSX.Element {
fetchStatus() fetchStatus()
// fetchProjects() // Reduced frequency for these // fetchProjects() // Reduced frequency for these
// fetchPhpVersions() // fetchPhpVersions()
// fetchMySqlVersions() // fetchMariaDbVersions()
// fetchNginxVersions() // fetchNginxVersions()
}, 3000) }, 3000)
@@ -276,7 +276,7 @@ function App(): JSX.Element {
clearInterval(interval) clearInterval(interval)
if (logInterval) clearInterval(logInterval) if (logInterval) clearInterval(logInterval)
if (typeof removePhpListener === 'function') removePhpListener() if (typeof removePhpListener === 'function') removePhpListener()
if (typeof removeMySqlListener === 'function') removeMySqlListener() if (typeof removeMariaDbListener === 'function') removeMariaDbListener()
if (typeof removeNginxListener === 'function') removeNginxListener() if (typeof removeNginxListener === 'function') removeNginxListener()
if (typeof removePmaListener === 'function') removePmaListener() if (typeof removePmaListener === 'function') removePmaListener()
} }
@@ -396,15 +396,15 @@ function App(): JSX.Element {
if ((status[s] || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' })) if ((status[s] || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
} }
const mysqlToStart = mySqlVersions.filter(v => v.status === 'installed').map(v => `mysql:${v.version}`) const mariaDbToStart = mariaDbVersions.filter(v => v.status === 'installed').map(v => `mariadb:${v.version}`)
for (const s of mysqlToStart) { for (const s of mariaDbToStart) {
if ((status[s] || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' })) if ((status[s] || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
} }
await Promise.all([ await Promise.all([
...criticalServices.filter(s => status[s] !== 'running').map(s => window.api?.startService(s)), ...criticalServices.filter(s => status[s] !== 'running').map(s => window.api?.startService(s)),
...phpToStart.filter(s => (status[s] || 'stopped') !== 'running').map(s => window.api?.startService(s)), ...phpToStart.filter(s => (status[s] || 'stopped') !== 'running').map(s => window.api?.startService(s)),
...mysqlToStart.filter(s => (status[s] || 'stopped') !== 'running').map(s => window.api?.startService(s)) ...mariaDbToStart.filter(s => (status[s] || 'stopped') !== 'running').map(s => window.api?.startService(s))
]) ])
fetchStatus() fetchStatus()
@@ -415,13 +415,13 @@ function App(): JSX.Element {
if (!window.api) return if (!window.api) return
const criticalServices = ['nginx'] const criticalServices = ['nginx']
const phpToStop = Object.keys(status).filter(s => s.startsWith('php:') && (status[s] === 'running' || status[s] === 'starting')) const phpToStop = Object.keys(status).filter(s => s.startsWith('php:') && (status[s] === 'running' || status[s] === 'starting'))
const mysqlToStop = Object.keys(status).filter(s => s.startsWith('mysql:') && (status[s] === 'running' || status[s] === 'starting')) const mariaDbToStop = Object.keys(status).filter(s => (s.startsWith('mariadb:') || s.startsWith('mysql:')) && (status[s] === 'running' || status[s] === 'starting'))
await Promise.all([ await Promise.all([
...criticalServices.filter(s => status[s] === 'running' || status[s] === 'starting').map(s => window.api?.stopService(s)), ...criticalServices.filter(s => status[s] === 'running' || status[s] === 'starting').map(s => window.api?.stopService(s)),
...phpToStop.map(s => window.api?.stopService(s)), ...phpToStop.map(s => window.api?.stopService(s)),
...mysqlToStop.map(s => window.api?.stopService(s)), ...mariaDbToStop.map(s => window.api?.stopService(s)),
window.api?.stopService('mysql') // Also stop legacy if any window.api?.stopService('mariadb') // Also stop default
]) ])
fetchStatus() fetchStatus()
@@ -444,7 +444,7 @@ function App(): JSX.Element {
await window.api.addProject(newProject) await window.api.addProject(newProject)
} }
setIsProjectDialogOpen(false) setIsProjectDialogOpen(false)
setNewProject({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' }) setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
setNotification({ open: true, message: newProject.id ? 'Proje güncellendi.' : 'Proje başarıyla eklendi.', severity: 'success' }) setNotification({ open: true, message: newProject.id ? 'Proje güncellendi.' : 'Proje başarıyla eklendi.', severity: 'success' })
fetchProjects() fetchProjects()
} }
@@ -559,15 +559,15 @@ function App(): JSX.Element {
fetchPhpVersions() fetchPhpVersions()
} }
const handleDownloadMySql = async (id: string) => { const handleDownloadMariaDb = async (id: string) => {
if (!window.api) return if (!window.api) return
const result = await window.api.downloadMySqlVersion(id) const result = await window.api.downloadMariaDbVersion(id)
setNotification({ setNotification({
open: true, open: true,
message: result.message, message: result.message,
severity: result.success ? 'success' : 'error' severity: result.success ? 'success' : 'error'
}) })
fetchMySqlVersions() fetchMariaDbVersions()
} }
const handleDownloadNginx = async (id: string) => { const handleDownloadNginx = async (id: string) => {
@@ -699,7 +699,7 @@ function App(): JSX.Element {
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}> <Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={settingsTab} onChange={(_e, v) => setSettingsTab(v)} textColor="primary" indicatorColor="primary" variant="scrollable" scrollButtons="auto"> <Tabs value={settingsTab} onChange={(_e, v) => setSettingsTab(v)} textColor="primary" indicatorColor="primary" variant="scrollable" scrollButtons="auto">
<Tab label="PHP" icon={<PhpIcon />} iconPosition="start" /> <Tab label="PHP" icon={<PhpIcon />} iconPosition="start" />
<Tab label="MySQL" icon={<DbIcon />} iconPosition="start" /> <Tab label="MariaDB" icon={<DbIcon />} iconPosition="start" />
<Tab label="Nginx" icon={<WebIcon />} iconPosition="start" /> <Tab label="Nginx" icon={<WebIcon />} iconPosition="start" />
<Tab label="Sunucu Ayarları" icon={<ServerIcon />} iconPosition="start" /> <Tab label="Sunucu Ayarları" icon={<ServerIcon />} iconPosition="start" />
</Tabs> </Tabs>
@@ -749,14 +749,14 @@ function App(): JSX.Element {
{settingsTab === 1 && ( {settingsTab === 1 && (
<Paper sx={{ bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}> <Paper sx={{ bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}>
<Box sx={{ p: 2, borderBottom: '1px solid rgba(255, 255, 255, 0.05)' }}> <Box sx={{ p: 2, borderBottom: '1px solid rgba(255, 255, 255, 0.05)' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>MySQL Sürümleri</Typography> <Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>MariaDB Sürümleri</Typography>
<Typography variant="body2" color="text.secondary">Farklı MySQL sürümlerini indirebilir ve sunucunuzda kullanabilirsiniz.</Typography> <Typography variant="body2" color="text.secondary">Farklı MariaDB sürümlerini indirebilir ve sunucunuzda kullanabilirsiniz.</Typography>
</Box> </Box>
<List sx={{ maxHeight: 500, overflow: 'auto' }}> <List sx={{ maxHeight: 500, overflow: 'auto' }}>
{mySqlVersions.length === 0 && ( {mariaDbVersions.length === 0 && (
<ListItem><ListItemText primary="Yükleniyor veya liste boş..." /></ListItem> <ListItem><ListItemText primary="Yükleniyor veya liste boş..." /></ListItem>
)} )}
{mySqlVersions.map((v) => ( {mariaDbVersions.map((v) => (
<ListItem key={v.id} sx={{ py: 2 }}> <ListItem key={v.id} sx={{ py: 2 }}>
<ListItemIcon> <ListItemIcon>
{v.status === 'installed' ? <InstalledIcon color="success" /> : <AvailableIcon color="action" />} {v.status === 'installed' ? <InstalledIcon color="success" /> : <AvailableIcon color="action" />}
@@ -764,7 +764,7 @@ function App(): JSX.Element {
<ListItemText <ListItemText
primary={ primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{`MySQL ${v.version} (${v.cycle})`} {`MariaDB ${v.version} (${v.cycle})`}
{v.description && ( {v.description && (
<Chip label={v.description} size="small" variant="outlined" sx={{ fontSize: '0.7rem', height: 20 }} /> <Chip label={v.description} size="small" variant="outlined" sx={{ fontSize: '0.7rem', height: 20 }} />
)} )}
@@ -777,11 +777,11 @@ function App(): JSX.Element {
{v.status === 'installed' && ( {v.status === 'installed' && (
<TextField <TextField
label="Port" label="Port"
value={settings.mysqlPorts?.[v.id] || ''} value={settings.mariaDbPorts?.[v.id] || ''}
placeholder={v.port} placeholder={v.port}
onChange={(e) => { onChange={(e) => {
const newPorts = { ...(settings.mysqlPorts || {}), [v.id]: e.target.value } const newPorts = { ...(settings.mariaDbPorts || {}), [v.id]: e.target.value }
setSettings({ ...settings, mysqlPorts: newPorts }) setSettings({ ...settings, mariaDbPorts: newPorts })
}} }}
variant="outlined" variant="outlined"
size="small" size="small"
@@ -790,7 +790,7 @@ function App(): JSX.Element {
)} )}
<Box sx={{ minWidth: 120, textAlign: 'right' }}> <Box sx={{ minWidth: 120, textAlign: 'right' }}>
{v.status === 'available' && ( {v.status === 'available' && (
<Button variant="outlined" size="small" startIcon={<DownloadIcon />} onClick={() => handleDownloadMySql(v.id)}> <Button variant="outlined" size="small" startIcon={<DownloadIcon />} onClick={() => handleDownloadMariaDb(v.id)}>
İNDİR İNDİR
</Button> </Button>
)} )}
@@ -870,9 +870,9 @@ function App(): JSX.Element {
helperText="Varsayılan: 9001" helperText="Varsayılan: 9001"
/> />
<TextField <TextField
label="MySQL Portu" label="MariaDB Portu"
value={settings.mysqlPort} value={settings.mariaDbPort}
onChange={(e) => setSettings({ ...settings, mysqlPort: e.target.value })} onChange={(e) => setSettings({ ...settings, mariaDbPort: e.target.value })}
variant="filled" variant="filled"
size="small" size="small"
helperText="Varsayılan: 3306" helperText="Varsayılan: 3306"
@@ -898,7 +898,7 @@ function App(): JSX.Element {
<Tab label={t('common.all')} /> <Tab label={t('common.all')} />
<Tab label="Nginx" /> <Tab label="Nginx" />
<Tab label="PHP" /> <Tab label="PHP" />
<Tab label="MySQL" /> <Tab label="MariaDB" />
</Tabs> </Tabs>
</Box> </Box>
@@ -988,8 +988,8 @@ function App(): JSX.Element {
</Grid> </Grid>
))} ))}
{/* MySQL Boxes */} {/* MariaDB Boxes */}
{(dashboardTab === 0 || dashboardTab === 3) && mySqlVersions.filter(v => v.status === 'installed').map(v => ( {(dashboardTab === 0 || dashboardTab === 3) && mariaDbVersions.filter(v => v.status === 'installed').map(v => (
<Grid item xs={12} sm={6} md={4} key={v.id}> <Grid item xs={12} sm={6} md={4} key={v.id}>
<Paper elevation={4} sx={{ <Paper elevation={4} sx={{
p: 3, borderRadius: 3, p: 3, borderRadius: 3,
@@ -1001,23 +1001,23 @@ function App(): JSX.Element {
}}> }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<DbIcon color="primary" sx={{ fontSize: 40 }} /> <DbIcon color="primary" sx={{ fontSize: 40 }} />
{getStatusChip(status[`mysql:${v.version}`] || 'stopped')} {getStatusChip(status[`mariadb:${v.version}`] || 'stopped')}
</Box> </Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MySQL {v.version}</Typography> <Typography variant="h6" sx={{ fontWeight: 'bold' }}>MariaDB {v.version}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.mysqlPorts?.[v.id] || v.port}</Typography> <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.mariaDbPorts?.[v.id] || v.port}</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', gap: 1 }}> <Box sx={{ display: 'flex', gap: 1 }}>
<IconButton onClick={() => handleOpenLogs(`mysql:${v.version}`)} title="Hata Logları" size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}> <IconButton onClick={() => handleOpenLogs(`mariadb:${v.version}`)} title="Hata Logları" size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
<TerminalIcon fontSize="small" /> <TerminalIcon fontSize="small" />
</IconButton> </IconButton>
<IconButton onClick={() => handleResetData(`mysql:${v.version}`)} title="Verileri Sıfırla" size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)', '&:hover': { color: 'error.main' } }}> <IconButton onClick={() => handleResetData(`mariadb:${v.version}`)} title="Verileri Sıfırla" size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)', '&:hover': { color: 'error.main' } }}>
<ResetIcon fontSize="small" /> <ResetIcon fontSize="small" />
</IconButton> </IconButton>
</Box> </Box>
<Switch <Switch
checked={(status[`mysql:${v.version}`] || 'stopped') === 'running' || status[`mysql:${v.version}`] === 'starting'} checked={(status[`mariadb:${v.version}`] || 'stopped') === 'running' || status[`mariadb:${v.version}`] === 'starting'}
onChange={() => handleToggleService(`mysql:${v.version}`)} onChange={() => handleToggleService(`mariadb:${v.version}`)}
disabled={status[`mysql:${v.version}`] === 'starting'} disabled={status[`mariadb:${v.version}`] === 'starting'}
/> />
</Box> </Box>
</Paper> </Paper>
@@ -1093,7 +1093,7 @@ function App(): JSX.Element {
<DbIcon color="primary" sx={{ fontSize: 32 }} /> <DbIcon color="primary" sx={{ fontSize: 32 }} />
</Box> </Box>
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MySQL Veri İçe Aktar</Typography> <Typography variant="h6" sx={{ fontWeight: 'bold' }}>MariaDB Veri İçe Aktar</Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Seçtiğiniz bir .sql veya arşiv dosyasını hızlıca veritabanına yükleyin. Seçtiğiniz bir .sql veya arşiv dosyasını hızlıca veritabanına yükleyin.
</Typography> </Typography>
@@ -1102,7 +1102,7 @@ function App(): JSX.Element {
<Button <Button
variant="contained" variant="contained"
size="large" size="large"
onClick={() => setIsMySqlWizardOpen(true)} onClick={() => setIsMariaDbWizardOpen(true)}
startIcon={<DbIcon />} startIcon={<DbIcon />}
sx={{ borderRadius: 2 }} sx={{ borderRadius: 2 }}
> >
@@ -1118,7 +1118,7 @@ function App(): JSX.Element {
<DbIcon color="secondary" sx={{ fontSize: 32 }} /> <DbIcon color="secondary" sx={{ fontSize: 32 }} />
</Box> </Box>
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MySQL Veri Dışa Aktar</Typography> <Typography variant="h6" sx={{ fontWeight: 'bold' }}>MariaDB Veri Dışa Aktar</Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Veritabanlarını .sql veya .sql.gz olarak güvenle yedekleyin. Veritabanlarını .sql veya .sql.gz olarak güvenle yedekleyin.
</Typography> </Typography>
@@ -1128,7 +1128,7 @@ function App(): JSX.Element {
variant="contained" variant="contained"
color="secondary" color="secondary"
size="large" size="large"
onClick={() => setIsMySqlExportWizardOpen(true)} onClick={() => setIsMariaDbExportWizardOpen(true)}
startIcon={<DbIcon />} startIcon={<DbIcon />}
sx={{ borderRadius: 2 }} sx={{ borderRadius: 2 }}
> >
@@ -1388,7 +1388,7 @@ function App(): JSX.Element {
<Dialog open={isProjectDialogOpen} onClose={() => { <Dialog open={isProjectDialogOpen} onClose={() => {
setIsProjectDialogOpen(false) setIsProjectDialogOpen(false)
setNewProject({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' }) setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
}} fullWidth maxWidth="sm"> }} fullWidth maxWidth="sm">
<DialogTitle>{newProject.id ? 'Proje Düzenle' : 'Yeni Proje Ekle'}</DialogTitle> <DialogTitle>{newProject.id ? 'Proje Düzenle' : 'Yeni Proje Ekle'}</DialogTitle>
<DialogContent> <DialogContent>
@@ -1442,7 +1442,7 @@ function App(): JSX.Element {
<DialogActions> <DialogActions>
<Button onClick={() => { <Button onClick={() => {
setIsProjectDialogOpen(false) setIsProjectDialogOpen(false)
setNewProject({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' }) setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
}}>İptal</Button> }}>İptal</Button>
<Button <Button
onClick={handleAddProject} onClick={handleAddProject}
@@ -1454,16 +1454,16 @@ function App(): JSX.Element {
</DialogActions> </DialogActions>
</Dialog> </Dialog>
<MySqlImportWizard <MariaDbImportWizard
open={isMySqlWizardOpen} open={isMariaDbWizardOpen}
onClose={() => setIsMySqlWizardOpen(false)} onClose={() => setIsMariaDbWizardOpen(false)}
mySqlVersions={mySqlVersions} mariaDbVersions={mariaDbVersions}
/> />
<MySqlExportWizard <MariaDbExportWizard
open={isMySqlExportWizardOpen} open={isMariaDbExportWizardOpen}
onClose={() => setIsMySqlExportWizardOpen(false)} onClose={() => setIsMariaDbExportWizardOpen(false)}
mySqlVersions={mySqlVersions} mariaDbVersions={mariaDbVersions}
/> />
<Dialog <Dialog
@@ -30,10 +30,10 @@ import {
} from '@mui/icons-material' } from '@mui/icons-material'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
interface MySqlExportWizardProps { interface MariaDbExportWizardProps {
open: boolean open: boolean
onClose: () => void onClose: () => void
mySqlVersions: any[] mariaDbVersions: any[]
} }
const steps = [ const steps = [
@@ -43,7 +43,7 @@ const steps = [
'Özet ve İşlem' 'Özet ve İşlem'
] ]
const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, mySqlVersions }) => { const MariaDbExportWizard: React.FC<MariaDbExportWizardProps> = ({ open, onClose, mariaDbVersions }) => {
const [activeStep, setActiveStep] = useState(0) const [activeStep, setActiveStep] = useState(0)
const [selectedVersion, setSelectedVersion] = useState('') const [selectedVersion, setSelectedVersion] = useState('')
const [databases, setDatabases] = useState<string[]>([]) const [databases, setDatabases] = useState<string[]>([])
@@ -63,10 +63,10 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
setSavePath('') setSavePath('')
setLogs([]) setLogs([])
setExportResult(null) setExportResult(null)
const installedVersion = mySqlVersions.find(v => v.status === 'installed') const installedVersion = mariaDbVersions.find(v => v.status === 'installed')
if (installedVersion) setSelectedVersion(installedVersion.id) if (installedVersion) setSelectedVersion(installedVersion.id)
} }
}, [open, mySqlVersions]) }, [open, mariaDbVersions])
useEffect(() => { useEffect(() => {
if (logEndRef.current) { if (logEndRef.current) {
@@ -79,12 +79,12 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
const handleStep1Next = async () => { const handleStep1Next = async () => {
if (!selectedVersion) { if (!selectedVersion) {
Swal.fire('Hata', 'Lütfen bir MySQL sunucusu seçin.', 'error') Swal.fire('Hata', 'Lütfen bir MariaDB sunucusu seçin.', 'error')
return return
} }
setLoading(true) setLoading(true)
try { try {
const dbs = await window.api.invoke('mysql:export-wizard:list-dbs', { versionId: selectedVersion, rootPass }) const dbs = await window.api.invoke('mariadb:export-wizard:list-dbs', { versionId: selectedVersion, rootPass })
setDatabases(dbs) setDatabases(dbs)
nextStep() nextStep()
} catch (err: any) { } catch (err: any) {
@@ -105,12 +105,12 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
setLoading(true) setLoading(true)
setLogs(['[Sistem] Dışa aktarma işlemi başlatılıyor...']) setLogs(['[Sistem] Dışa aktarma işlemi başlatılıyor...'])
const removeListener = window.api.onMySqlExportLog((msg: string) => { const removeListener = window.api.onMariaDbExportLog((msg: string) => {
setLogs(prev => [...prev, msg]) setLogs(prev => [...prev, msg])
}) })
try { try {
const result = await window.api.invoke('mysql:export-wizard:start', { const result = await window.api.invoke('mariadb:export-wizard:start', {
versionId: selectedVersion, versionId: selectedVersion,
dbName: selectedDb, dbName: selectedDb,
savePath, savePath,
@@ -131,17 +131,17 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
case 0: case 0:
return ( return (
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
<Typography variant="body1" gutterBottom>Veritabanın hangi MySQL sunucusunda olduğunu seçin.</Typography> <Typography variant="body1" gutterBottom>Veritabanın hangi MariaDB sunucusunda olduğunu seçin.</Typography>
<FormControl fullWidth sx={{ mt: 2 }}> <FormControl fullWidth sx={{ mt: 2 }}>
<InputLabel>MySQL Sunucusu</InputLabel> <InputLabel>MariaDB Sunucusu</InputLabel>
<Select <Select
value={selectedVersion} value={selectedVersion}
label="MySQL Sunucusu" label="MariaDB Sunucusu"
onChange={(e) => setSelectedVersion(e.target.value)} onChange={(e) => setSelectedVersion(e.target.value)}
> >
{mySqlVersions.filter(v => v.status === 'installed').map(v => ( {mariaDbVersions.filter(v => v.status === 'installed').map(v => (
<MenuItem key={v.id} value={v.id}> <MenuItem key={v.id} value={v.id}>
MySQL {v.version} (Port: {v.port}) MariaDB {v.version} (Port: {v.port})
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
@@ -153,7 +153,7 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
value={rootPass} value={rootPass}
type="password" type="password"
onChange={(e) => setRootPass(e.target.value)} onChange={(e) => setRootPass(e.target.value)}
helperText="MySQL 'root' kullanıcısının şifresi. Boşsa boş bırakın." helperText="MariaDB 'root' kullanıcısının şifresi. Boşsa boş bırakın."
/> />
</Box> </Box>
) )
@@ -266,7 +266,7 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
return ( return (
<Dialog open={open} onClose={loading ? undefined : onClose} maxWidth="sm" fullWidth> <Dialog open={open} onClose={loading ? undefined : onClose} maxWidth="sm" fullWidth>
<DialogTitle>MySQL Veritabanı Dışa Aktarma Sihirbazı</DialogTitle> <DialogTitle>MariaDB Veritabanı Dışa Aktarma Sihirbazı</DialogTitle>
<DialogContent> <DialogContent>
<Stepper activeStep={activeStep} orientation="horizontal" sx={{ pt: 2, pb: 4 }}> <Stepper activeStep={activeStep} orientation="horizontal" sx={{ pt: 2, pb: 4 }}>
{steps.map((label) => ( {steps.map((label) => (
@@ -306,4 +306,4 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
) )
} }
export default MySqlExportWizard export default MariaDbExportWizard
@@ -30,10 +30,10 @@ import {
} from '@mui/icons-material' } from '@mui/icons-material'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
interface MySqlImportWizardProps { interface MariaDbImportWizardProps {
open: boolean open: boolean
onClose: () => void onClose: () => void
mySqlVersions: any[] mariaDbVersions: any[]
} }
const steps = [ const steps = [
@@ -43,7 +43,7 @@ const steps = [
'Özet ve İşlem' 'Özet ve İşlem'
] ]
const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, mySqlVersions }) => { const MariaDbImportWizard: React.FC<MariaDbImportWizardProps> = ({ open, onClose, mariaDbVersions }) => {
const [activeStep, setActiveStep] = useState(0) const [activeStep, setActiveStep] = useState(0)
const [filePath, setFilePath] = useState('') const [filePath, setFilePath] = useState('')
const [selectedVersion, setSelectedVersion] = useState('') const [selectedVersion, setSelectedVersion] = useState('')
@@ -64,7 +64,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
setFilePath('') setFilePath('')
setLogs([]) setLogs([])
setImportResult(null) setImportResult(null)
const installedVersion = mySqlVersions.find(v => v.status === 'installed') const installedVersion = mariaDbVersions.find(v => v.status === 'installed')
if (installedVersion) setSelectedVersion(installedVersion.id) if (installedVersion) setSelectedVersion(installedVersion.id)
// Generate random credentials // Generate random credentials
@@ -76,7 +76,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
rootPass: '' rootPass: ''
}) })
} }
}, [open, mySqlVersions]) }, [open, mariaDbVersions])
useEffect(() => { useEffect(() => {
if (logEndRef.current) { if (logEndRef.current) {
@@ -103,7 +103,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
const handleStep2Next = () => { const handleStep2Next = () => {
if (!selectedVersion) { if (!selectedVersion) {
Swal.fire('Hata', 'Lütfen bir MySQL sunucusu seçin.', 'error') Swal.fire('Hata', 'Lütfen bir MariaDB sunucusu seçin.', 'error')
return return
} }
nextStep() nextStep()
@@ -117,7 +117,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
setLoading(true) setLoading(true)
try { try {
const { exists } = await window.api.checkMySqlDb(selectedVersion, dbConfig.dbName, dbConfig.rootPass) const { exists } = await window.api.checkMariaDbDb(selectedVersion, dbConfig.dbName, dbConfig.rootPass)
if (exists) { if (exists) {
const result = await Swal.fire({ const result = await Swal.fire({
title: 'Veritabanı Mevcut!', title: 'Veritabanı Mevcut!',
@@ -142,19 +142,19 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
setLoading(true) setLoading(true)
setLogs(['[Sistem] İşlem başlatılıyor...']) setLogs(['[Sistem] İşlem başlatılıyor...'])
const removeListener = window.api.onMySqlImportLog((msg: string) => { const removeListener = window.api.onMariaDbImportLog((msg: string) => {
setLogs(prev => [...prev, msg]) setLogs(prev => [...prev, msg])
}) })
try { try {
const result = await window.api.startMySqlImport({ const result = await window.api.startMariaDbImport({
versionId: selectedVersion, versionId: selectedVersion,
filePath, filePath,
dbName: dbConfig.dbName, dbName: dbConfig.dbName,
user: dbConfig.user, user: dbConfig.user,
pass: dbConfig.pass, pass: dbConfig.pass,
rootPass: dbConfig.rootPass, rootPass: dbConfig.rootPass,
overwrite: true // Logic handled by checkMySqlDb before overwrite: true // Logic handled by checkMariaDbDb before
}) })
setImportResult(result) setImportResult(result)
} catch (err: any) { } catch (err: any) {
@@ -193,17 +193,17 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
case 1: case 1:
return ( return (
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
<Typography variant="body1" gutterBottom>Dosyanın hangi MySQL sunucusuna yükleneceğini seçin.</Typography> <Typography variant="body1" gutterBottom>Dosyanın hangi MariaDB sunucusuna yükleneceğini seçin.</Typography>
<FormControl fullWidth sx={{ mt: 2 }}> <FormControl fullWidth sx={{ mt: 2 }}>
<InputLabel>MySQL Sunucusu</InputLabel> <InputLabel>MariaDB Sunucusu</InputLabel>
<Select <Select
value={selectedVersion} value={selectedVersion}
label="MySQL Sunucusu" label="MariaDB Sunucusu"
onChange={(e) => setSelectedVersion(e.target.value)} onChange={(e) => setSelectedVersion(e.target.value)}
> >
{mySqlVersions.filter(v => v.status === 'installed').map(v => ( {mariaDbVersions.filter(v => v.status === 'installed').map(v => (
<MenuItem key={v.id} value={v.id}> <MenuItem key={v.id} value={v.id}>
MySQL {v.version} (Port: {v.port}) MariaDB {v.version} (Port: {v.port})
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
@@ -215,7 +215,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
value={dbConfig.rootPass} value={dbConfig.rootPass}
type="password" type="password"
onChange={(e) => setDbConfig({ ...dbConfig, rootPass: e.target.value })} onChange={(e) => setDbConfig({ ...dbConfig, rootPass: e.target.value })}
helperText="MySQL 'root' kullanıcısının mevcut şifresini girin. Boşsa boş bırakın." helperText="MariaDB 'root' kullanıcısının mevcut şifresini girin. Boşsa boş bırakın."
/> />
</Box> </Box>
) )
@@ -315,7 +315,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
return ( return (
<Dialog open={open} onClose={loading ? undefined : onClose} maxWidth="sm" fullWidth> <Dialog open={open} onClose={loading ? undefined : onClose} maxWidth="sm" fullWidth>
<DialogTitle>MySQL Hızlı Dump/Import Sihirbazı</DialogTitle> <DialogTitle>MariaDB Hızlı Dump/Import Sihirbazı</DialogTitle>
<DialogContent> <DialogContent>
<Stepper activeStep={activeStep} orientation="horizontal" sx={{ pt: 2, pb: 4 }}> <Stepper activeStep={activeStep} orientation="horizontal" sx={{ pt: 2, pb: 4 }}>
{steps.map((label) => ( {steps.map((label) => (
@@ -355,4 +355,4 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
) )
} }
export default MySqlImportWizard export default MariaDbImportWizard