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 { projectService } from '../services/ProjectService'
import { phpManagerService } from '../services/PhpManagerService'
import { mySqlManagerService } from '../services/MySqlManagerService'
import { mariaDbManagerService } from '../services/MariaDbManagerService'
import { nginxManagerService } from '../services/NginxManagerService'
import { phpMyAdminService } from '../services/PhpMyAdminService'
import { portMappingService } from '../services/PortMappingService'
import path from 'path'
import { mySqlOperationService } from '../services/MySqlOperationService'
import { mariaDbOperationService } from '../services/MariaDbOperationService'
function findExecutable(dir: string, name: string): string | 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ı.')
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 logDir = path.join(configService.getBasePath(), 'logs')
const rootPath = path.join(app.getAppPath(), 'www')
@@ -167,8 +168,10 @@ export function registerIpcHandlers(): void {
const phpVersion = versionMatch ? versionMatch[1] : null
const phpId = phpVersion ? `php-${phpVersion}` : null
const isWindows = process.platform === 'win32';
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.` }
@@ -199,48 +202,47 @@ export function registerIpcHandlers(): void {
return { success, message: success ? `${phpVersion || ''} PHP başlatıldı.` : `${phpVersion || ''} PHP devreye alınamadı.` }
}
if (serviceName.startsWith('mysql')) {
const versionMatch = serviceName.match(/^mysql:(.+)$/)
const mysqlVersion = versionMatch ? versionMatch[1] : null
const mysqlId = mysqlVersion ? `mysql-${mysqlVersion}` : null
if (serviceName.startsWith('mariadb') || serviceName.startsWith('mysql')) {
const versionMatch = serviceName.match(/^(?:mariadb|mysql):(.+)$/)
const dbVersion = versionMatch ? versionMatch[1] : null
const dbId = dbVersion ? `mariadb-${dbVersion}` : 'mariadb-default'
const dataDir = mysqlId
? path.join(configService.getBasePath(), 'data', mysqlId)
: path.join(configService.getBasePath(), 'data', 'default')
const dataDir = path.join(configService.getBasePath(), 'data', dbId)
const dbBinDir = dbVersion ? path.join(binDir, `mariadb-${dbVersion}`) : binDir
const mysqlDir = mysqlId ? path.join(binDir, mysqlId) : binDir
const binPath = findExecutable(mysqlDir, 'mysqld.exe')
const isWindows = process.platform === 'win32';
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 mysqlInfo = mysqlVersions.find(v => v.id === mysqlId)
const mysqlPort = mysqlInfo?.port || (mysqlId ? '3306' : settings.mysqlPort)
const dbVersions = await mariaDbManagerService.getVersions()
const dbInfo = dbVersions.find(v => v.id === `mariadb-${dbVersion}`)
const dbPort = dbInfo?.port || (dbVersion ? portMappingService.getMariaDbPort(dbVersion).toString() : settings.mariaDbPort)
// Force kill this specific version if running or generic mysql
await processManager.forceKillAll(serviceName)
// 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
if (!fs.existsSync(dataDir) || fs.readdirSync(dataDir).length === 0) {
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 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, '/')}`])
} catch (err) {
console.log(`Modern init failed, trying template copy for ${serviceName}...`)
// Fallback for 5.6: Copy default 'data' folder from binary dir if it exists
const templateDataDir = path.join(mysqlDir, 'data')
// Fallback: Copy default 'data' folder from binary dir if it exists
const templateDataDir = path.join(dbBinDir, 'data')
if (fs.existsSync(templateDataDir)) {
// Recursively copy template data (mysql, performance_schema, etc.)
fs.cpSync(templateDataDir, dataDir, { recursive: true })
console.log(`Successfully copied template data for ${serviceName}`)
} 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 logFile = path.join(configService.getBasePath(), 'logs', `${sanitizedServiceName}_error.log`)
const configPath = await configService.generateConfig('my.ini.template', `my_${mysqlId || 'default'}.ini`, {
PORT: mysqlPort,
const configPath = await configService.generateConfig('mariadb.ini.template', `mariadb_${dbId || 'default'}.ini`, {
PORT: dbPort,
DATADIR: dataDir.replace(/\\/g, '/'),
SOCKET: `/tmp/${sanitizedServiceName}.sock`,
LOG_FILE: logFile.replace(/\\/g, '/')
@@ -258,18 +260,18 @@ export function registerIpcHandlers(): void {
const success = await processManager.startService(serviceName, binPath, ['--defaults-file=' + configPath], false)
if (success) {
// Quick health check for MySQL
// Quick health check
await new Promise(r => setTimeout(r, 1500))
if (!processManager.isServiceRunning(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 {
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.' }
@@ -320,30 +322,30 @@ export function registerIpcHandlers(): void {
}
})
// MySQL Versions
ipcMain.handle('mysql:versions', async () => {
return await mySqlManagerService.getVersions()
// MariaDB Versions
ipcMain.handle('mariadb:versions', async () => {
return await mariaDbManagerService.getVersions()
})
ipcMain.handle('mysql:download', async (event, id: string) => {
const versions = await mySqlManagerService.getVersions()
ipcMain.handle('mariadb:download', async (event, id: string) => {
const versions = await mariaDbManagerService.getVersions()
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 {
mySqlManagerService.updateProgress(id, 0, 'downloading')
event.sender.send('mysql:progress', { id, progress: 0 })
mariaDbManagerService.updateProgress(id, 0, 'downloading')
event.sender.send('mariadb:progress', { id, progress: 0 })
await downloadService.downloadAndExtract(version.url, id, (p) => {
mySqlManagerService.updateProgress(id, p)
event.sender.send('mysql:progress', { id, progress: p })
mariaDbManagerService.updateProgress(id, p)
event.sender.send('mariadb:progress', { id, progress: p })
})
mySqlManagerService.updateProgress(id, 100, 'installed')
return { success: true, message: 'MySQL indirme tamamlandı.' }
mariaDbManagerService.updateProgress(id, 100, 'installed')
return { success: true, message: 'MariaDB indirme tamamlandı.' }
} catch (error: any) {
mySqlManagerService.updateProgress(id, 0, 'available')
return { success: false, message: `MySQL hatası: ${error.message}` }
mariaDbManagerService.updateProgress(id, 0, 'available')
return { success: false, message: `MariaDB hatası: ${error.message}` }
}
})
@@ -565,8 +567,8 @@ export function registerIpcHandlers(): void {
return await phpMyAdminService.isInstalled()
})
ipcMain.handle('pma:setup', async (event, mysqlPort: string) => {
return await phpMyAdminService.setup(mysqlPort, (p) => {
ipcMain.handle('pma:setup', async (event, mariaDbPort: string) => {
return await phpMyAdminService.setup(mariaDbPort, (p) => {
event.sender.send('pma:progress', p)
})
})
@@ -575,13 +577,13 @@ export function registerIpcHandlers(): void {
try {
await processManager.forceKillAll(serviceName)
const versionMatch = serviceName.match(/^mysql:(.+)$/)
const mysqlVersion = versionMatch ? versionMatch[1] : null
const mysqlId = mysqlVersion ? `mysql-${mysqlVersion}` : null
const versionMatch = serviceName.match(/^(?:mariadb|mysql):(.+)$/)
const dbVersion = versionMatch ? versionMatch[1] : 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)) {
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 }) => {
const versions = await mySqlManagerService.getVersions()
// MariaDB Operations
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)
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 binPath = findExecutable(binDir, 'mysql.exe')
if (!binPath) return { exists: false, error: 'mysql.exe not found' }
const isWindows = process.platform === 'win32'
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,
host: '127.0.0.1',
port: v.port,
@@ -611,33 +615,34 @@ export function registerIpcHandlers(): void {
return { exists }
})
ipcMain.handle('mysql:import-wizard:start', async (event, { versionId, filePath, dbName, user, pass, rootPass, overwrite }) => {
const versions = await mySqlManagerService.getVersions()
ipcMain.handle('mariadb:import-wizard:start', async (event, { versionId, filePath, dbName, user, pass, rootPass, overwrite }) => {
const versions = await mariaDbManagerService.getVersions()
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 mysqlBin = findExecutable(binDir, 'mysql.exe')
if (!mysqlBin) return { success: false, message: 'mysql.exe not found' }
const isWindows = process.platform === 'win32'
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 = {
binPath: mysqlBin,
binPath: dbBin,
host: '127.0.0.1',
port: v.port,
rootUser: 'root',
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 {
if (overwrite) {
log(`Dropping existing database: ${dbName}`)
await mySqlOperationService.dropDatabase(config, dbName)
await mariaDbOperationService.dropDatabase(config, dbName)
}
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
let sqlFiles: string[] = []
@@ -645,14 +650,14 @@ export function registerIpcHandlers(): void {
sqlFiles = [filePath]
} else {
log(`Extracting archive: ${path.basename(filePath)}`)
const extractDir = path.join(app.getPath('temp'), `mysql_import_${Date.now()}`)
sqlFiles = await mySqlOperationService.extractArchive(filePath, extractDir)
const extractDir = path.join(app.getPath('temp'), `mariadb_import_${Date.now()}`)
sqlFiles = await mariaDbOperationService.extractArchive(filePath, extractDir)
if (sqlFiles.length === 0) return { success: false, message: 'No .sql files found in archive.' }
}
for (const sqlFile of sqlFiles) {
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
}
@@ -662,17 +667,18 @@ export function registerIpcHandlers(): void {
}
})
ipcMain.handle('mysql:export-wizard:list-dbs', async (_event, { versionId, rootPass }) => {
const versions = await mySqlManagerService.getVersions()
ipcMain.handle('mariadb:export-wizard:list-dbs', async (_event, { versionId, rootPass }) => {
const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId)
if (!v) return []
const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const mysqlBin = findExecutable(binDir, 'mysql.exe')
if (!mysqlBin) return []
const isWindows = process.platform === 'win32'
const dbBin = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!dbBin) return []
return await mySqlOperationService.listDatabases({
binPath: mysqlBin,
return await mariaDbOperationService.listDatabases({
binPath: dbBin,
host: '127.0.0.1',
port: v.port,
rootUser: 'root',
@@ -680,19 +686,20 @@ export function registerIpcHandlers(): void {
})
})
ipcMain.handle('mysql:export-wizard:start', async (event, { versionId, dbName, savePath, compress, rootPass }) => {
const versions = await mySqlManagerService.getVersions()
ipcMain.handle('mariadb:export-wizard:start', async (event, { versionId, dbName, savePath, compress, rootPass }) => {
const versions = await mariaDbManagerService.getVersions()
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 mysqlBin = findExecutable(binDir, 'mysql.exe')
if (!mysqlBin) return { success: false, message: 'mysql.exe not found' }
const isWindows = process.platform === 'win32'
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({
binPath: mysqlBin,
return await mariaDbOperationService.exportSql({
binPath: dbBin,
host: '127.0.0.1',
port: v.port,
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 = {
nginxPort: '80',
phpPort: '9001',
mysqlPort: '3306',
mysqlPorts: {} as Record<string, string>,
mariaDbPort: '3306',
mariaDbPorts: {} as Record<string, string>,
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> {
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)
// Cleanup failed/previous attempts if we want a fresh start
@@ -70,7 +72,9 @@ export class DownloadService {
}
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 { configService } from './ConfigService'
export interface MySqlVersion {
export interface MariaDbVersion {
id: string
version: string
cycle: string
@@ -14,16 +14,16 @@ export interface MySqlVersion {
description?: string
}
export class MySqlManagerService {
private versions: MySqlVersion[] = []
export class MariaDbManagerService {
private versions: MariaDbVersion[] = []
private binDir: string
private catalogPath: string
constructor() {
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)) {
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()
}
@@ -34,54 +34,60 @@ export class MySqlManagerService {
}
}
async fetchAvailableVersions(): Promise<MySqlVersion[]> {
console.log('Loading MySQL versions from local catalog:', this.catalogPath)
async fetchAvailableVersions(): Promise<MariaDbVersion[]> {
console.log('Loading MariaDB versions from local catalog:', this.catalogPath)
try {
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
}
const rawData = fs.readFileSync(this.catalogPath, 'utf8')
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 cycle = v.series
// Extract MAJOR.MINOR (e.g. 8.0 from 8.0.40)
const versionParts = version.split('.')
const majorMinor = `${versionParts[0]}.${versionParts[1]}`
// Resolve URL based on platform and architecture
let url = '';
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
const url = `https://cdn.mysql.com/archives/mysql-${majorMinor}/mysql-${version}-winx64.zip`
if (!url) {
console.warn(`No MariaDB URL found for platform ${platform}/${arch} in version ${version}`);
}
const settings = configService.getSettings()
const defaultPort = (3306 + index).toString()
const customPort = settings.mysqlPorts?.[`mysql-${version}`] || defaultPort
const customPort = settings.mariaDbPorts?.[`mariadb-${version}`] || defaultPort
return {
id: `mysql-${version}`,
id: `mariadb-${version}`,
version,
cycle,
url,
status: 'available',
status: 'available' as MariaDbVersion['status'],
progress: 0,
port: customPort,
description: v.status
}
})
}).filter(v => v.url !== '') // Only include versions available for this platform
this.updateVersionsList(dynamicVersions)
return this.versions
} catch (e: any) {
console.error('Failed to load MySQL catalog:', e.message)
console.error('Failed to load MariaDB catalog:', e.message)
return this.versions
}
}
private updateVersionsList(newList: MySqlVersion[]) {
private updateVersionsList(newList: MariaDbVersion[]) {
this.versions = newList.map(dv => {
const current = this.versions.find(cv => cv.id === dv.id)
if (current && current.status === 'downloading') return current
@@ -92,14 +98,19 @@ export class MySqlManagerService {
}
private refreshInstalledStatus() {
const isWindows = process.platform === 'win32';
const binaryName = isWindows ? 'mariadbd.exe' : 'mariadbd';
this.versions = this.versions.map(v => {
const targetDir = path.join(this.binDir, v.id)
const possiblePaths = [
path.join(targetDir, 'bin', 'mysqld.exe'),
path.join(targetDir, `mysql-${v.version}-winx64`, 'bin', 'mysqld.exe')
]
const possibleBinPaths = [
path.join(targetDir, 'bin', binaryName),
// 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) {
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) {
await this.fetchAvailableVersions()
} else {
@@ -117,7 +128,7 @@ export class MySqlManagerService {
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)
if (version) {
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 zlib from 'zlib'
export interface MySqlConfig {
export interface MariaDbConfig {
binPath: string
host: string
port: string
@@ -12,7 +12,7 @@ export interface MySqlConfig {
rootPass: string
}
export class MySqlOperationService {
export class MariaDbOperationService {
async extractArchive(archivePath: string, targetDir: string): Promise<string[]> {
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
@@ -58,8 +58,9 @@ export class MySqlOperationService {
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) => {
const mariaDbPath = config.binPath;
const args = [
`--host=${config.host}`,
`--port=${config.port}`,
@@ -73,13 +74,17 @@ export class MySqlOperationService {
args.push('-e', query)
const child = spawn(config.binPath, args, { windowsHide: true })
const child = spawn(mariaDbPath, args, { windowsHide: true })
let output = ''
let error = ''
child.stdout.on('data', (data) => output += 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) => {
if (code === 0) {
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}'`)
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 = [
`CREATE DATABASE IF NOT EXISTS \`${dbName}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`,
`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.' }
}
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}\``)
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) => {
const mysqlPath = config.binPath
const mariaDbPath = config.binPath
const args = [
`--host=${config.host}`,
`--port=${config.port}`,
@@ -132,9 +137,9 @@ export class MySqlOperationService {
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
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')
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))
}
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) => {
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 = [
`--host=${config.host}`,
`--port=${config.port}`,
@@ -219,11 +238,11 @@ export class MySqlOperationService {
resolve({ success: true, message: 'Export completed successfully.' })
} else {
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'))
}
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)) {
fs.mkdirSync(this.pmaDir, { recursive: true })
}
@@ -38,7 +38,7 @@ export class PhpMyAdminService {
}
// Generate config.inc.php
this.generateConfig(mysqlPort)
this.generateConfig(mariaDbPort)
return true
} 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 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++;
$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]['password'] = '';
$cfg['Servers'][$i]['extension'] = 'mysqli';
+9 -9
View File
@@ -4,14 +4,14 @@ import { configService } from './ConfigService'
interface PortMap {
php: Record<string, number>;
mysql: Record<string, number>;
mariadb: Record<string, number>;
}
export class PortMappingService {
private configFile: string
private portMap: PortMap = { php: {}, mysql: {} }
private portMap: PortMap = { php: {}, mariadb: {} }
private nextPhpPort = 9001
private nextMysqlPort = 3306
private nextMariaDbPort = 3306
constructor() {
this.configFile = path.join(configService.getBasePath(), 'port_mappings.json')
@@ -26,8 +26,8 @@ export class PortMappingService {
const phpPorts = Object.values(this.portMap.php)
if (phpPorts.length > 0) this.nextPhpPort = Math.max(...phpPorts) + 1
const mysqlPorts = Object.values(this.portMap.mysql)
if (mysqlPorts.length > 0) this.nextMysqlPort = Math.max(...mysqlPorts) + 1
const mariaDbPorts = Object.values(this.portMap.mariadb)
if (mariaDbPorts.length > 0) this.nextMariaDbPort = Math.max(...mariaDbPorts) + 1
} catch (e) {
console.error('Failed to load port mappings', e)
}
@@ -46,12 +46,12 @@ export class PortMappingService {
return this.portMap.php[version]
}
getMySqlPort(version: string): number {
if (!this.portMap.mysql[version]) {
this.portMap.mysql[version] = this.nextMysqlPort++
getMariaDbPort(version: string): number {
if (!this.portMap.mariadb[version]) {
this.portMap.mariadb[version] = this.nextMariaDbPort++
this.save()
}
return this.portMap.mysql[version]
return this.portMap.mariadb[version]
}
}
+84 -89
View File
@@ -22,7 +22,7 @@ export class ProcessManager {
fs.mkdirSync(this.logDir, { recursive: true })
}
// Initialize with default logs
['nginx', 'php', 'mysql'].forEach(s => {
['nginx', 'php', 'mariadb'].forEach(s => {
if (!this.logs.has(s)) {
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)
if (child) {
this.addLog(name, 'Stopping service...')
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)
}
@@ -167,118 +176,104 @@ export class ProcessManager {
}
async forceKillAll(name: string): Promise<boolean> {
const isWindows = process.platform === 'win32';
return new Promise((resolve) => {
const childProcess = this.processes.get(name)
if (isWindows) {
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)
})
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
}
// Fallback: If it's a generic service name without version, or we just want to be sure
// For MySQL multi-instance, we should avoid global kill if possible.
const isVersionedMysql = name.startsWith('mysql:')
const executableName = name === 'nginx' ? 'nginx.exe' : name === 'php' ? 'php-cgi.exe' : 'mysqld.exe'
const isVersionedMariaDb = name.startsWith('mariadb:') || name.startsWith('mysql:')
if (isVersionedMysql) {
// For versioned mysql, we try to find by command line (contains data directory name)
const mysqlId = name.replace('mysql:', 'mysql-')
this.addLog(name, `Attempting to kill specific MySQL instance: ${mysqlId}`)
// Using powershell to find and kill as wmic is deprecated/missing on some Win11
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], {
windowsHide: true,
shell: false
})
ps.on('error', (err) => {
this.addLog(name, `PowerShell kill error: ${err.message}`)
resolve(true)
})
ps.on('close', () => resolve(true))
if (isVersionedMariaDb) {
const dbVersion = name.split(':')[1]
const dbId = `mariadb-${dbVersion}`
if (process.platform === 'win32') {
const psCommand = `Get-CimInstance Win32_Process -Filter "name = 'mariadbd.exe' OR name = 'mysqld.exe'" | Where-Object { $_.CommandLine -like '*${dbId}*' } | Invoke-CimMethod -MethodName Terminate`
spawn('powershell', ['-Command', psCommand], { windowsHide: true })
.on('close', () => resolve(true))
.on('error', () => resolve(true))
} else {
this.addLog(name, `Force killing all ${executableName} processes...`)
const tk = spawn('taskkill', ['/F', '/IM', executableName, '/T'], {
windowsHide: true,
shell: false
})
tk.on('error', (err) => {
this.addLog(name, `Taskkill error: ${err.message}`)
resolve(true)
})
tk.on('close', () => resolve(true))
// Unix: pkill -f matches full command line (data directory path usually contains the dbId)
spawn('pkill', ['-f', dbId], { windowsHide: 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 {
// macOS / Linux
if (childProcess && childProcess.pid) {
try { process.kill(childProcess.pid, 'SIGKILL') } catch (e) { }
this.processes.delete(name)
}
const procName = name === 'nginx' ? 'nginx' : name === 'php' ? 'php-fpm' : 'mariadbd'
spawn('pkill', ['-9', '-f', procName])
.on('close', () => resolve(true))
.on('error', () => resolve(true))
}
})
}
async killByPort(name: string, port: string | number): Promise<boolean> {
const isWindows = process.platform === 'win32';
return new Promise((resolve) => {
if (!port) return resolve(true)
this.addLog(name, `Checking if port ${port} is in use...`)
// findstr will return lines containing the port and "LISTENING"
if (isWindows) {
const cmd = `netstat -ano | findstr :${port} | findstr LISTENING`
const netstat = spawn('cmd.exe', ['/c', cmd], { windowsHide: true, shell: false })
const netstat = spawn('cmd.exe', ['/c', cmd], { windowsHide: true })
let output = ''
netstat.stdout?.on('data', (data) => {
output += data.toString()
})
netstat.stdout?.on('data', d => output += d.toString())
netstat.on('close', () => {
const lines = output.split('\n').filter(l => l.trim())
if (lines.length === 0) {
this.addLog(name, `Port ${port} is free.`)
return resolve(true)
}
// netstat output format: TCP 0.0.0.0:3306 0.0.0.0:0 LISTENING 1234
// We want the last part, which is the PID
if (lines.length === 0) return resolve(true)
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 (pid && !isNaN(parseInt(pid))) pids.add(pid)
})
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 {
const lsof = spawn('lsof', ['-i', `:${port}`, '-t'])
let output = ''
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))
})
}
})
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())
})
})
Promise.all(killProcesses).then(() => {
this.addLog(name, `Successfully cleared port ${port}.`)
resolve(true)
})
})
netstat.on('error', (err) => {
this.addLog(name, `Port check error: ${err.message}`)
resolve(true)
})
})
}
isServiceRunning(name: string): boolean {
@@ -292,7 +287,7 @@ export class ProcessManager {
}
// Ensure defaults are present if stopped
if (!statuses['nginx']) statuses['nginx'] = 'stopped'
if (!statuses['mysql']) statuses['mysql'] = 'stopped'
if (!statuses['mariadb']) statuses['mariadb'] = 'stopped'
return statuses
}
@@ -300,7 +295,7 @@ export class ProcessManager {
if (!this.logDir) return
const logPaths: Record<string, string> = {
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]
@@ -341,7 +336,7 @@ export class ProcessManager {
const possibleFiles = [
path.join(this.logDir, `${sanitizedName}.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`)
]
+1 -1
View File
@@ -7,7 +7,7 @@ export interface Project {
name: string
path: string
phpVersion: string
mySqlVersion: string
mariaDbVersion: string
host: string
}
+15 -10
View File
@@ -16,9 +16,9 @@ const api = {
getPhpVersions: () => electronAPI.ipcRenderer.invoke('binaries:list-php'),
downloadPhpVersion: (id: string) => electronAPI.ipcRenderer.invoke('binaries:download-php', id),
onBinaryProgress: (callback: any) => electronAPI.ipcRenderer.on('binaries:progress', (_event, data) => callback(data)),
getMySqlVersions: () => electronAPI.ipcRenderer.invoke('mysql:versions'),
downloadMySqlVersion: (id: string) => electronAPI.ipcRenderer.invoke('mysql:download', id),
onMySqlProgress: (callback: any) => electronAPI.ipcRenderer.on('mysql:progress', (_event, data) => callback(data)),
getMariaDbVersions: () => electronAPI.ipcRenderer.invoke('mariadb:versions'),
downloadMariaDbVersion: (id: string) => electronAPI.ipcRenderer.invoke('mariadb:download', id),
onMariaDbProgress: (callback: any) => electronAPI.ipcRenderer.on('mariadb:progress', (_event, data) => callback(data)),
selectDirectory: () => electronAPI.ipcRenderer.invoke('dialog:select-directory'),
getSettings: () => electronAPI.ipcRenderer.invoke('config:get'),
saveSettings: (settings: any) => electronAPI.ipcRenderer.invoke('config:save', settings),
@@ -26,15 +26,20 @@ const api = {
downloadNginxVersion: (id: string) => electronAPI.ipcRenderer.invoke('nginx:download', id),
onNginxProgress: (callback: any) => electronAPI.ipcRenderer.on('nginx:progress', (_event, data) => callback(data)),
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)),
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 }),
startMySqlImport: (data: any) => electronAPI.ipcRenderer.invoke('mysql:import-wizard:start', data),
onMySqlImportLog: (callback: any) => {
const listener = (_event, msg) => callback(msg)
electronAPI.ipcRenderer.on('mysql:import:log', listener)
return () => electronAPI.ipcRenderer.removeListener('mysql:import:log', listener)
checkMariaDbDb: (versionId: string, dbName: string) => electronAPI.ipcRenderer.invoke('mariadb:import-wizard:check-db', { versionId, dbName }),
startMariaDbImport: (data: any) => electronAPI.ipcRenderer.invoke('mariadb:import-wizard:start', data),
onMariaDbImportLog: (callback: any) => {
const listener = (_event, msg: any) => callback(msg)
electronAPI.ipcRenderer.on('mariadb: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)
}
+5 -5
View File
@@ -1,17 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Trunçgil Multi-PHP & MySQL Server</title>
<title>Trunçgil Multi-PHP & MariaDB 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'"
/>
<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'
import Swal from 'sweetalert2'
import { useTranslation } from 'react-i18next'
import MySqlImportWizard from './components/MySqlImportWizard'
import MySqlExportWizard from './components/MySqlExportWizard'
import MariaDbImportWizard from './components/MariaDbImportWizard'
import MariaDbExportWizard from './components/MariaDbExportWizard'
import NginxCodeEditor from './components/NginxCodeEditor'
declare global {
@@ -90,8 +90,8 @@ interface ServiceStatus {
interface AppSettings {
nginxPort: string
phpPort: string
mysqlPort: string
mysqlPorts?: Record<string, string>
mariaDbPort: string
mariaDbPorts?: Record<string, string>
language: string
}
@@ -100,22 +100,22 @@ function App(): JSX.Element {
const [status, setStatus] = useState<ServiceStatus>({
nginx: 'stopped',
php: 'stopped',
mysql: 'stopped'
mariadb: 'stopped'
})
const [activeTab, setActiveTab] = useState('dashboard')
const [settingsTab, setSettingsTab] = useState(0)
const [dashboardTab, setDashboardTab] = useState(0)
const [projects, setProjects] = useState<any[]>([])
const [phpVersions, setPhpVersions] = useState<any[]>([])
const [mySqlVersions, setMySqlVersions] = useState<any[]>([])
const [mariaDbVersions, setMariaDbVersions] = useState<any[]>([])
const [nginxVersions, setNginxVersions] = useState<any[]>([])
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false)
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>({
nginxPort: '80',
phpPort: '9001',
mysqlPort: '3306',
mariaDbPort: '3306',
language: 'tr'
})
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 [pmaLoading, setPmaLoading] = useState(false)
const [pmaProgress, setPmaProgress] = useState(0)
const [isMySqlWizardOpen, setIsMySqlWizardOpen] = useState(false)
const [isMySqlExportWizardOpen, setIsMySqlExportWizardOpen] = useState(false)
const [isMariaDbWizardOpen, setIsMariaDbWizardOpen] = useState(false)
const [isMariaDbExportWizardOpen, setIsMariaDbExportWizardOpen] = useState(false)
const [isNginxConfigOpen, setIsNginxConfigOpen] = useState(false)
const [nginxConfig, setNginxConfig] = useState('')
const [isProjectNginxConfigOpen, setIsProjectNginxConfigOpen] = useState(false)
@@ -178,10 +178,10 @@ function App(): JSX.Element {
}
}
const fetchMySqlVersions = async () => {
if (window.api && window.api.getMySqlVersions) {
const list = await window.api.getMySqlVersions()
setMySqlVersions(list || [])
const fetchMariaDbVersions = async () => {
if (window.api && window.api.getMariaDbVersions) {
const list = await window.api.getMariaDbVersions()
setMariaDbVersions(list || [])
}
}
@@ -207,15 +207,15 @@ function App(): JSX.Element {
const installedPhp = phpVersions.find(v => v.status === 'installed')
if (installedPhp) updates.phpVersion = installedPhp.version
}
if (!newProject.mySqlVersion && mySqlVersions.length > 0) {
const installedMysql = mySqlVersions.find(v => v.status === 'installed')
if (installedMysql) updates.mySqlVersion = installedMysql.version
if (!newProject.mariaDbVersion && mariaDbVersions.length > 0) {
const installedMariaDb = mariaDbVersions.find(v => v.status === 'installed')
if (installedMariaDb) updates.mariaDbVersion = installedMariaDb.version
}
if (Object.keys(updates).length > 0) {
setNewProject(prev => ({ ...prev, ...updates }))
}
}
}, [isProjectDialogOpen, phpVersions, mySqlVersions])
}, [isProjectDialogOpen, phpVersions, mariaDbVersions])
useEffect(() => {
if (!window.api) return
@@ -230,7 +230,7 @@ function App(): JSX.Element {
fetchStatus()
fetchProjects()
fetchPhpVersions()
fetchMySqlVersions()
fetchMariaDbVersions()
fetchNginxVersions()
fetchSettings()
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))
}) : null
const removeMySqlListener = window.api.onMySqlProgress ? window.api.onMySqlProgress((data) => {
const removeMariaDbListener = window.api.onMariaDbProgress ? window.api.onMariaDbProgress((data) => {
if (!data) return
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
const removeNginxListener = window.api.onNginxProgress ? window.api.onNginxProgress((data) => {
@@ -261,7 +261,7 @@ function App(): JSX.Element {
fetchStatus()
// fetchProjects() // Reduced frequency for these
// fetchPhpVersions()
// fetchMySqlVersions()
// fetchMariaDbVersions()
// fetchNginxVersions()
}, 3000)
@@ -276,7 +276,7 @@ function App(): JSX.Element {
clearInterval(interval)
if (logInterval) clearInterval(logInterval)
if (typeof removePhpListener === 'function') removePhpListener()
if (typeof removeMySqlListener === 'function') removeMySqlListener()
if (typeof removeMariaDbListener === 'function') removeMariaDbListener()
if (typeof removeNginxListener === 'function') removeNginxListener()
if (typeof removePmaListener === 'function') removePmaListener()
}
@@ -396,15 +396,15 @@ function App(): JSX.Element {
if ((status[s] || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
}
const mysqlToStart = mySqlVersions.filter(v => v.status === 'installed').map(v => `mysql:${v.version}`)
for (const s of mysqlToStart) {
const mariaDbToStart = mariaDbVersions.filter(v => v.status === 'installed').map(v => `mariadb:${v.version}`)
for (const s of mariaDbToStart) {
if ((status[s] || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
}
await Promise.all([
...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)),
...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()
@@ -415,13 +415,13 @@ function App(): JSX.Element {
if (!window.api) return
const criticalServices = ['nginx']
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([
...criticalServices.filter(s => status[s] === 'running' || status[s] === 'starting').map(s => window.api?.stopService(s)),
...phpToStop.map(s => window.api?.stopService(s)),
...mysqlToStop.map(s => window.api?.stopService(s)),
window.api?.stopService('mysql') // Also stop legacy if any
...mariaDbToStop.map(s => window.api?.stopService(s)),
window.api?.stopService('mariadb') // Also stop default
])
fetchStatus()
@@ -444,7 +444,7 @@ function App(): JSX.Element {
await window.api.addProject(newProject)
}
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' })
fetchProjects()
}
@@ -559,15 +559,15 @@ function App(): JSX.Element {
fetchPhpVersions()
}
const handleDownloadMySql = async (id: string) => {
const handleDownloadMariaDb = async (id: string) => {
if (!window.api) return
const result = await window.api.downloadMySqlVersion(id)
const result = await window.api.downloadMariaDbVersion(id)
setNotification({
open: true,
message: result.message,
severity: result.success ? 'success' : 'error'
})
fetchMySqlVersions()
fetchMariaDbVersions()
}
const handleDownloadNginx = async (id: string) => {
@@ -699,7 +699,7 @@ function App(): JSX.Element {
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<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="MySQL" icon={<DbIcon />} iconPosition="start" />
<Tab label="MariaDB" icon={<DbIcon />} iconPosition="start" />
<Tab label="Nginx" icon={<WebIcon />} iconPosition="start" />
<Tab label="Sunucu Ayarları" icon={<ServerIcon />} iconPosition="start" />
</Tabs>
@@ -749,14 +749,14 @@ function App(): JSX.Element {
{settingsTab === 1 && (
<Paper sx={{ bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}>
<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="body2" color="text.secondary">Farklı MySQL sürümlerini indirebilir ve sunucunuzda kullanabilirsiniz.</Typography>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>MariaDB Sürümleri</Typography>
<Typography variant="body2" color="text.secondary">Farklı MariaDB sürümlerini indirebilir ve sunucunuzda kullanabilirsiniz.</Typography>
</Box>
<List sx={{ maxHeight: 500, overflow: 'auto' }}>
{mySqlVersions.length === 0 && (
{mariaDbVersions.length === 0 && (
<ListItem><ListItemText primary="Yükleniyor veya liste boş..." /></ListItem>
)}
{mySqlVersions.map((v) => (
{mariaDbVersions.map((v) => (
<ListItem key={v.id} sx={{ py: 2 }}>
<ListItemIcon>
{v.status === 'installed' ? <InstalledIcon color="success" /> : <AvailableIcon color="action" />}
@@ -764,7 +764,7 @@ function App(): JSX.Element {
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{`MySQL ${v.version} (${v.cycle})`}
{`MariaDB ${v.version} (${v.cycle})`}
{v.description && (
<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' && (
<TextField
label="Port"
value={settings.mysqlPorts?.[v.id] || ''}
value={settings.mariaDbPorts?.[v.id] || ''}
placeholder={v.port}
onChange={(e) => {
const newPorts = { ...(settings.mysqlPorts || {}), [v.id]: e.target.value }
setSettings({ ...settings, mysqlPorts: newPorts })
const newPorts = { ...(settings.mariaDbPorts || {}), [v.id]: e.target.value }
setSettings({ ...settings, mariaDbPorts: newPorts })
}}
variant="outlined"
size="small"
@@ -790,7 +790,7 @@ function App(): JSX.Element {
)}
<Box sx={{ minWidth: 120, textAlign: 'right' }}>
{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
</Button>
)}
@@ -870,9 +870,9 @@ function App(): JSX.Element {
helperText="Varsayılan: 9001"
/>
<TextField
label="MySQL Portu"
value={settings.mysqlPort}
onChange={(e) => setSettings({ ...settings, mysqlPort: e.target.value })}
label="MariaDB Portu"
value={settings.mariaDbPort}
onChange={(e) => setSettings({ ...settings, mariaDbPort: e.target.value })}
variant="filled"
size="small"
helperText="Varsayılan: 3306"
@@ -898,7 +898,7 @@ function App(): JSX.Element {
<Tab label={t('common.all')} />
<Tab label="Nginx" />
<Tab label="PHP" />
<Tab label="MySQL" />
<Tab label="MariaDB" />
</Tabs>
</Box>
@@ -988,8 +988,8 @@ function App(): JSX.Element {
</Grid>
))}
{/* MySQL Boxes */}
{(dashboardTab === 0 || dashboardTab === 3) && mySqlVersions.filter(v => v.status === 'installed').map(v => (
{/* MariaDB Boxes */}
{(dashboardTab === 0 || dashboardTab === 3) && mariaDbVersions.filter(v => v.status === 'installed').map(v => (
<Grid item xs={12} sm={6} md={4} key={v.id}>
<Paper elevation={4} sx={{
p: 3, borderRadius: 3,
@@ -1001,23 +1001,23 @@ function App(): JSX.Element {
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<DbIcon color="primary" sx={{ fontSize: 40 }} />
{getStatusChip(status[`mysql:${v.version}`] || 'stopped')}
{getStatusChip(status[`mariadb:${v.version}`] || 'stopped')}
</Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MySQL {v.version}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.mysqlPorts?.[v.id] || v.port}</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MariaDB {v.version}</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', 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" />
</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" />
</IconButton>
</Box>
<Switch
checked={(status[`mysql:${v.version}`] || 'stopped') === 'running' || status[`mysql:${v.version}`] === 'starting'}
onChange={() => handleToggleService(`mysql:${v.version}`)}
disabled={status[`mysql:${v.version}`] === 'starting'}
checked={(status[`mariadb:${v.version}`] || 'stopped') === 'running' || status[`mariadb:${v.version}`] === 'starting'}
onChange={() => handleToggleService(`mariadb:${v.version}`)}
disabled={status[`mariadb:${v.version}`] === 'starting'}
/>
</Box>
</Paper>
@@ -1093,7 +1093,7 @@ function App(): JSX.Element {
<DbIcon color="primary" sx={{ fontSize: 32 }} />
</Box>
<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">
Seçtiğiniz bir .sql veya arşiv dosyasını hızlıca veritabanına yükleyin.
</Typography>
@@ -1102,7 +1102,7 @@ function App(): JSX.Element {
<Button
variant="contained"
size="large"
onClick={() => setIsMySqlWizardOpen(true)}
onClick={() => setIsMariaDbWizardOpen(true)}
startIcon={<DbIcon />}
sx={{ borderRadius: 2 }}
>
@@ -1118,7 +1118,7 @@ function App(): JSX.Element {
<DbIcon color="secondary" sx={{ fontSize: 32 }} />
</Box>
<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">
Veritabanlarını .sql veya .sql.gz olarak güvenle yedekleyin.
</Typography>
@@ -1128,7 +1128,7 @@ function App(): JSX.Element {
variant="contained"
color="secondary"
size="large"
onClick={() => setIsMySqlExportWizardOpen(true)}
onClick={() => setIsMariaDbExportWizardOpen(true)}
startIcon={<DbIcon />}
sx={{ borderRadius: 2 }}
>
@@ -1388,7 +1388,7 @@ function App(): JSX.Element {
<Dialog open={isProjectDialogOpen} onClose={() => {
setIsProjectDialogOpen(false)
setNewProject({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' })
setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
}} fullWidth maxWidth="sm">
<DialogTitle>{newProject.id ? 'Proje Düzenle' : 'Yeni Proje Ekle'}</DialogTitle>
<DialogContent>
@@ -1442,7 +1442,7 @@ function App(): JSX.Element {
<DialogActions>
<Button onClick={() => {
setIsProjectDialogOpen(false)
setNewProject({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' })
setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
}}>İptal</Button>
<Button
onClick={handleAddProject}
@@ -1454,16 +1454,16 @@ function App(): JSX.Element {
</DialogActions>
</Dialog>
<MySqlImportWizard
open={isMySqlWizardOpen}
onClose={() => setIsMySqlWizardOpen(false)}
mySqlVersions={mySqlVersions}
<MariaDbImportWizard
open={isMariaDbWizardOpen}
onClose={() => setIsMariaDbWizardOpen(false)}
mariaDbVersions={mariaDbVersions}
/>
<MySqlExportWizard
open={isMySqlExportWizardOpen}
onClose={() => setIsMySqlExportWizardOpen(false)}
mySqlVersions={mySqlVersions}
<MariaDbExportWizard
open={isMariaDbExportWizardOpen}
onClose={() => setIsMariaDbExportWizardOpen(false)}
mariaDbVersions={mariaDbVersions}
/>
<Dialog
@@ -30,10 +30,10 @@ import {
} from '@mui/icons-material'
import Swal from 'sweetalert2'
interface MySqlExportWizardProps {
interface MariaDbExportWizardProps {
open: boolean
onClose: () => void
mySqlVersions: any[]
mariaDbVersions: any[]
}
const steps = [
@@ -43,7 +43,7 @@ const steps = [
'Ö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 [selectedVersion, setSelectedVersion] = useState('')
const [databases, setDatabases] = useState<string[]>([])
@@ -63,10 +63,10 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
setSavePath('')
setLogs([])
setExportResult(null)
const installedVersion = mySqlVersions.find(v => v.status === 'installed')
const installedVersion = mariaDbVersions.find(v => v.status === 'installed')
if (installedVersion) setSelectedVersion(installedVersion.id)
}
}, [open, mySqlVersions])
}, [open, mariaDbVersions])
useEffect(() => {
if (logEndRef.current) {
@@ -79,12 +79,12 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
const handleStep1Next = async () => {
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
}
setLoading(true)
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)
nextStep()
} catch (err: any) {
@@ -105,12 +105,12 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
setLoading(true)
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])
})
try {
const result = await window.api.invoke('mysql:export-wizard:start', {
const result = await window.api.invoke('mariadb:export-wizard:start', {
versionId: selectedVersion,
dbName: selectedDb,
savePath,
@@ -131,17 +131,17 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
case 0:
return (
<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 }}>
<InputLabel>MySQL Sunucusu</InputLabel>
<InputLabel>MariaDB Sunucusu</InputLabel>
<Select
value={selectedVersion}
label="MySQL Sunucusu"
label="MariaDB Sunucusu"
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}>
MySQL {v.version} (Port: {v.port})
MariaDB {v.version} (Port: {v.port})
</MenuItem>
))}
</Select>
@@ -153,7 +153,7 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
value={rootPass}
type="password"
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>
)
@@ -266,7 +266,7 @@ const MySqlExportWizard: React.FC<MySqlExportWizardProps> = ({ open, onClose, my
return (
<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>
<Stepper activeStep={activeStep} orientation="horizontal" sx={{ pt: 2, pb: 4 }}>
{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'
import Swal from 'sweetalert2'
interface MySqlImportWizardProps {
interface MariaDbImportWizardProps {
open: boolean
onClose: () => void
mySqlVersions: any[]
mariaDbVersions: any[]
}
const steps = [
@@ -43,7 +43,7 @@ const steps = [
'Ö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 [filePath, setFilePath] = useState('')
const [selectedVersion, setSelectedVersion] = useState('')
@@ -64,7 +64,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
setFilePath('')
setLogs([])
setImportResult(null)
const installedVersion = mySqlVersions.find(v => v.status === 'installed')
const installedVersion = mariaDbVersions.find(v => v.status === 'installed')
if (installedVersion) setSelectedVersion(installedVersion.id)
// Generate random credentials
@@ -76,7 +76,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
rootPass: ''
})
}
}, [open, mySqlVersions])
}, [open, mariaDbVersions])
useEffect(() => {
if (logEndRef.current) {
@@ -103,7 +103,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
const handleStep2Next = () => {
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
}
nextStep()
@@ -117,7 +117,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
setLoading(true)
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) {
const result = await Swal.fire({
title: 'Veritabanı Mevcut!',
@@ -142,19 +142,19 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
setLoading(true)
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])
})
try {
const result = await window.api.startMySqlImport({
const result = await window.api.startMariaDbImport({
versionId: selectedVersion,
filePath,
dbName: dbConfig.dbName,
user: dbConfig.user,
pass: dbConfig.pass,
rootPass: dbConfig.rootPass,
overwrite: true // Logic handled by checkMySqlDb before
overwrite: true // Logic handled by checkMariaDbDb before
})
setImportResult(result)
} catch (err: any) {
@@ -193,17 +193,17 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
case 1:
return (
<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 }}>
<InputLabel>MySQL Sunucusu</InputLabel>
<InputLabel>MariaDB Sunucusu</InputLabel>
<Select
value={selectedVersion}
label="MySQL Sunucusu"
label="MariaDB Sunucusu"
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}>
MySQL {v.version} (Port: {v.port})
MariaDB {v.version} (Port: {v.port})
</MenuItem>
))}
</Select>
@@ -215,7 +215,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
value={dbConfig.rootPass}
type="password"
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>
)
@@ -315,7 +315,7 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
return (
<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>
<Stepper activeStep={activeStep} orientation="horizontal" sx={{ pt: 2, pb: 4 }}>
{steps.map((label) => (
@@ -355,4 +355,4 @@ const MySqlImportWizard: React.FC<MySqlImportWizardProps> = ({ open, onClose, my
)
}
export default MySqlImportWizard
export default MariaDbImportWizard