diff --git a/service_events.json b/service_events.json
index 3b36791..2d1e110 100644
--- a/service_events.json
+++ b/service_events.json
@@ -1,4 +1,116 @@
[
+ {
+ "service": "php:5.6.40",
+ "action": "start",
+ "status": "success",
+ "id": "e224nli4b",
+ "timestamp": 1775131071408
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "start",
+ "status": "success",
+ "id": "uylwtxbbj",
+ "timestamp": 1775131071388
+ },
+ {
+ "service": "php:8.2.30",
+ "action": "start",
+ "status": "success",
+ "id": "2s4hcn046",
+ "timestamp": 1775131071369
+ },
+ {
+ "service": "php:8.5.4",
+ "action": "start",
+ "status": "success",
+ "id": "6jyn1ulp5",
+ "timestamp": 1775131071348
+ },
+ {
+ "service": "mariadb:10.11.8",
+ "action": "start",
+ "status": "success",
+ "id": "rwjbq8erc",
+ "timestamp": 1775131070886
+ },
+ {
+ "service": "mariadb:11.4.2",
+ "action": "start",
+ "status": "success",
+ "id": "ckyqrf43d",
+ "timestamp": 1775131070866
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "a91gjnuiz",
+ "timestamp": 1775131070360
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "6hzh7wm5t",
+ "timestamp": 1775131070285
+ },
+ {
+ "service": "php:5.6.40",
+ "action": "start",
+ "status": "success",
+ "id": "e7dkj5bii",
+ "timestamp": 1775130858539
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "start",
+ "status": "success",
+ "id": "osp7uacka",
+ "timestamp": 1775130858516
+ },
+ {
+ "service": "php:8.2.30",
+ "action": "start",
+ "status": "success",
+ "id": "w98vrvad6",
+ "timestamp": 1775130858493
+ },
+ {
+ "service": "php:8.5.4",
+ "action": "start",
+ "status": "success",
+ "id": "kjbfmzipg",
+ "timestamp": 1775130858466
+ },
+ {
+ "service": "mariadb:11.4.2",
+ "action": "start",
+ "status": "success",
+ "id": "src638nzz",
+ "timestamp": 1775130857997
+ },
+ {
+ "service": "mariadb:10.11.8",
+ "action": "start",
+ "status": "success",
+ "id": "l5izsomjp",
+ "timestamp": 1775130857977
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "tjho3pnv0",
+ "timestamp": 1775130857478
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "0l999aqql",
+ "timestamp": 1775130857409
+ },
{
"service": "mariadb:10.11.8",
"action": "start",
diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts
index da658f4..335fd5a 100644
--- a/src/main/ipc/index.ts
+++ b/src/main/ipc/index.ts
@@ -276,6 +276,60 @@ export function registerIpcHandlers(): void {
}
})
+ ipcMain.handle('mariadb:databases-with-sizes', async (_event, versionId) => {
+ try {
+ return await mariaDbManagerService.getDatabasesWithSizes(versionId)
+ } catch (error: any) {
+ console.error('Failed to get databases:', error)
+ return []
+ }
+ })
+
+ ipcMain.handle('mariadb:create-db', async (_event, { versionId, dbName }) => {
+ try {
+ return await mariaDbManagerService.createDatabase(versionId, dbName)
+ } catch (error: any) {
+ console.error('Failed to create database:', error)
+ return { success: false, message: error.message }
+ }
+ })
+
+ ipcMain.handle('mariadb:drop-db', async (_event, { versionId, dbName }) => {
+ try {
+ return await mariaDbManagerService.dropDatabase(versionId, dbName)
+ } catch (error: any) {
+ console.error('Failed to drop database:', error)
+ return { success: false, message: error.message }
+ }
+ })
+
+ ipcMain.handle('mariadb:users', async (_event, versionId) => {
+ try {
+ return await mariaDbManagerService.getUsers(versionId)
+ } catch (error: any) {
+ console.error('Failed to get users:', error)
+ return []
+ }
+ })
+
+ ipcMain.handle('mariadb:create-user', async (_event, { versionId, user, host, password }) => {
+ try {
+ return await mariaDbManagerService.createUser(versionId, { user, host, password })
+ } catch (error: any) {
+ console.error('Failed to create user:', error)
+ return { success: false, message: error.message }
+ }
+ })
+
+ ipcMain.handle('mariadb:drop-user', async (_event, { versionId, user, host }) => {
+ try {
+ return await mariaDbManagerService.dropUser(versionId, { user, host })
+ } catch (error: any) {
+ console.error('Failed to drop user:', error)
+ return { success: false, message: error.message }
+ }
+ })
+
// CLI & Environment
ipcMain.handle('cli:get-config', async () => {
const settings = configService.getSettings()
diff --git a/src/main/services/MariaDbManagerService.ts b/src/main/services/MariaDbManagerService.ts
index e4c6682..d33c8ae 100644
--- a/src/main/services/MariaDbManagerService.ts
+++ b/src/main/services/MariaDbManagerService.ts
@@ -215,6 +215,54 @@ export class MariaDbManagerService {
return true
}
+
+ private async getMariaDbConfig(versionId: string) {
+ const version = this.versions.find(v => v.id === versionId)
+ if (!version) throw new Error('Version not found')
+
+ const binaryPath = await this.getMariaDbBinaryPath(version.version)
+ if (!binaryPath) throw new Error('MariaDB binary not found')
+
+ const settings = configService.getSettings()
+ return {
+ binPath: binaryPath,
+ host: '127.0.0.1',
+ port: version.port,
+ rootUser: 'root',
+ rootPass: settings.mariaDbPasswords?.[versionId] || ''
+ }
+ }
+
+ async getDatabasesWithSizes(versionId: string) {
+ const config = await this.getMariaDbConfig(versionId)
+ return await mariaDbOperationService.getDatabasesWithSizes(config)
+ }
+
+ async createDatabase(versionId: string, dbName: string) {
+ const config = await this.getMariaDbConfig(versionId)
+ const result = await mariaDbOperationService.runQuery(config, `CREATE DATABASE IF NOT EXISTS \`${dbName}\` CHARACTER SET utf8 COLLATE utf8_unicode_ci`)
+ return { success: result.success, message: result.success ? 'Database created' : result.error }
+ }
+
+ async dropDatabase(versionId: string, dbName: string) {
+ const config = await this.getMariaDbConfig(versionId)
+ return await mariaDbOperationService.dropDatabase(config, dbName)
+ }
+
+ async getUsers(versionId: string) {
+ const config = await this.getMariaDbConfig(versionId)
+ return await mariaDbOperationService.listUsers(config)
+ }
+
+ async createUser(versionId: string, { user, host, password }: any) {
+ const config = await this.getMariaDbConfig(versionId)
+ return await mariaDbOperationService.createUser(config, user, host, password)
+ }
+
+ async dropUser(versionId: string, { user, host }: any) {
+ const config = await this.getMariaDbConfig(versionId)
+ return await mariaDbOperationService.dropUser(config, user, host)
+ }
}
export const mariaDbManagerService = new MariaDbManagerService()
diff --git a/src/main/services/MariaDbOperationService.ts b/src/main/services/MariaDbOperationService.ts
index 84c9a35..c58b7e4 100644
--- a/src/main/services/MariaDbOperationService.ts
+++ b/src/main/services/MariaDbOperationService.ts
@@ -216,6 +216,52 @@ export class MariaDbOperationService {
.filter(line => line && line !== 'Database' && !line.match(/^information_schema|performance_schema|mysql|sys$/i))
}
+ async getDatabasesWithSizes(config: MariaDbConfig): Promise<{ name: string; size: number }[]> {
+ const query = "SELECT table_schema AS 'db', SUM(data_length + index_length) / 1024 / 1024 AS 'size' FROM information_schema.TABLES GROUP BY table_schema";
+ const result = await this.runQuery(config, query)
+ if (!result.success) return []
+
+ const lines = result.output.split('\n').filter(l => l.trim().length > 0)
+ if (lines.length <= 1) return []
+
+ return lines.slice(1).map(line => {
+ const [name, size] = line.split('\t')
+ return { name, size: parseFloat(size) || 0 }
+ }).filter(db => !db.name.match(/^information_schema|performance_schema|mysql|sys$/i))
+ }
+
+ async listUsers(config: MariaDbConfig): Promise<{ user: string; host: string }[]> {
+ const result = await this.runQuery(config, "SELECT user, host FROM mysql.user")
+ if (!result.success) return []
+
+ const lines = result.output.split('\n').filter(l => l.trim().length > 0)
+ if (lines.length <= 1) return []
+
+ return lines.slice(1).map(line => {
+ const [user, host] = line.split('\t')
+ return { user, host }
+ })
+ }
+
+ async createUser(config: MariaDbConfig, user: string, host: string, pass: string): Promise<{ success: boolean; message: string }> {
+ const queries = [
+ `CREATE USER IF NOT EXISTS '${user}'@'${host}' IDENTIFIED BY '${pass}';`,
+ `GRANT ALL PRIVILEGES ON *.* TO '${user}'@'${host}' WITH GRANT OPTION;`,
+ `FLUSH PRIVILEGES;`
+ ]
+
+ for (const query of queries) {
+ const result = await this.runQuery(config, query)
+ if (!result.success) return { success: false, message: result.error || 'Query failed' }
+ }
+ return { success: true, message: 'User created successfully' }
+ }
+
+ async dropUser(config: MariaDbConfig, user: string, host: string): Promise<{ success: boolean; message: string }> {
+ const result = await this.runQuery(config, `DROP USER '${user}'@'${host}'`)
+ return { success: result.success, message: result.success ? 'User dropped' : result.error || 'Drop failed' }
+ }
+
async exportSql(config: MariaDbConfig, dbName: string, targetPath: string, compress: boolean, onProgress?: (log: string) => void): Promise<{ success: boolean; message: string }> {
return new Promise((resolve) => {
const isWindows = process.platform === 'win32';
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index 7ffc6b4..78e8178 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -86,6 +86,7 @@ import { useTranslation } from 'react-i18next'
import { supportedLngs } from './i18n'
import MariaDbImportWizard from './components/MariaDbImportWizard'
import MariaDbExportWizard from './components/MariaDbExportWizard'
+import MariaDbDatabaseManager from './components/MariaDbDatabaseManager'
import NginxCodeEditor from './components/NginxCodeEditor'
import ApacheCodeEditor from './components/ApacheCodeEditor'
import PhpExtensionManager from './components/PhpExtensionManager'
@@ -1119,9 +1120,15 @@ function App(): JSX.Element {
+
+ setActiveTab('db_manager')}>
+
+
+
+
setActiveTab('db_monitor')}>
-
+
@@ -1142,6 +1149,7 @@ function App(): JSX.Element {
{activeTab === 'tuning' && }
+ {activeTab === 'db_manager' && }
{activeTab === 'settings' && (
diff --git a/src/renderer/src/components/MariaDbDatabaseManager.tsx b/src/renderer/src/components/MariaDbDatabaseManager.tsx
new file mode 100644
index 0000000..4379ba4
--- /dev/null
+++ b/src/renderer/src/components/MariaDbDatabaseManager.tsx
@@ -0,0 +1,321 @@
+import React, { useState, useEffect } from 'react'
+import {
+ Box,
+ Paper,
+ Typography,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Button,
+ Select,
+ MenuItem,
+ FormControl,
+ InputLabel,
+ Stack,
+ Divider,
+ Tooltip,
+ CircularProgress,
+ Alert
+} from '@mui/material'
+import {
+ Delete as DeleteIcon,
+ Add as AddIcon,
+ Refresh as RefreshIcon,
+ Storage as DbIcon,
+ Person as UserIcon,
+ Language as HostIcon
+} from '@mui/icons-material'
+import Swal from 'sweetalert2'
+import { useTranslation } from 'react-i18next'
+
+interface DatabaseInfo {
+ name: string
+ size: number
+}
+
+interface UserInfo {
+ user: string
+ host: string
+}
+
+interface MariaDbDatabaseManagerProps {
+ versions: any[]
+}
+
+const MariaDbDatabaseManager: React.FC = ({ versions }) => {
+ const { t } = useTranslation()
+ const [selectedVersion, setSelectedVersion] = useState('')
+ const [databases, setDatabases] = useState([])
+ const [users, setUsers] = useState([])
+ const [loading, setLoading] = useState(false)
+
+ const installedVersions = versions.filter(v => v.status === 'installed')
+
+ useEffect(() => {
+ if (!selectedVersion && installedVersions.length > 0) {
+ setSelectedVersion(installedVersions[0].id)
+ }
+ }, [installedVersions])
+
+ useEffect(() => {
+ if (selectedVersion) {
+ fetchData()
+ }
+ }, [selectedVersion])
+
+ const fetchData = async () => {
+ if (!selectedVersion) return
+ setLoading(true)
+ try {
+ const dbList = await window.api.invoke('mariadb:databases-with-sizes', selectedVersion)
+ const userList = await window.api.invoke('mariadb:users', selectedVersion)
+ setDatabases(dbList || [])
+ setUsers(userList || [])
+ } catch (error) {
+ console.error('Fetch data error:', error)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const handleCreateDb = async () => {
+ const { value: dbName } = await Swal.fire({
+ title: t('mariadb_manager.create_db_title'),
+ input: 'text',
+ inputLabel: t('mariadb_manager.db_name_label'),
+ showCancelButton: true,
+ background: '#1e1e1e',
+ color: '#fff',
+ inputValidator: (value) => {
+ if (!value) return t('common.error_required')
+ return null
+ }
+ })
+
+ if (dbName) {
+ const result = await window.api.invoke('mariadb:create-db', { versionId: selectedVersion, dbName })
+ if (result.success) {
+ Swal.fire({ icon: 'success', title: t('common.ok'), background: '#1e1e1e', color: '#fff' })
+ fetchData()
+ } else {
+ Swal.fire({ icon: 'error', title: t('common.error'), text: result.message, background: '#1e1e1e', color: '#fff' })
+ }
+ }
+ }
+
+ const handleDeleteDb = async (dbName: string) => {
+ const result = await Swal.fire({
+ title: t('common.are_you_sure'),
+ text: t('mariadb_manager.delete_db_confirm', { db: dbName }),
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#d33',
+ background: '#1e1e1e',
+ color: '#fff'
+ })
+
+ if (result.isConfirmed) {
+ const res = await window.api.invoke('mariadb:drop-db', { versionId: selectedVersion, dbName })
+ if (res.success) {
+ fetchData()
+ } else {
+ Swal.fire({ icon: 'error', title: t('common.error'), text: res.message, background: '#1e1e1e', color: '#fff' })
+ }
+ }
+ }
+
+ const handleCreateUser = async () => {
+ const { value: formValues } = await Swal.fire({
+ title: t('mariadb_manager.create_user_title'),
+ html:
+ `` +
+ `` +
+ ``,
+ focusConfirm: false,
+ background: '#1e1e1e',
+ color: '#fff',
+ preConfirm: () => {
+ const user = (document.getElementById('swal-user') as HTMLInputElement).value
+ const host = (document.getElementById('swal-host') as HTMLInputElement).value
+ const password = (document.getElementById('swal-pass') as HTMLInputElement).value
+ if (!user || !host) {
+ Swal.showValidationMessage(t('common.error_required'))
+ return null
+ }
+ return { user, host, password }
+ }
+ })
+
+ if (formValues) {
+ const result = await window.api.invoke('mariadb:create-user', { versionId: selectedVersion, ...formValues })
+ if (result.success) {
+ Swal.fire({ icon: 'success', title: t('common.ok'), background: '#1e1e1e', color: '#fff' })
+ fetchData()
+ } else {
+ Swal.fire({ icon: 'error', title: t('common.error'), text: result.message, background: '#1e1e1e', color: '#fff' })
+ }
+ }
+ }
+
+ const handleDeleteUser = async (user: string, host: string) => {
+ const result = await Swal.fire({
+ title: t('common.are_you_sure'),
+ text: t('mariadb_manager.delete_user_confirm', { user: `${user}@${host}` }),
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#d33',
+ background: '#1e1e1e',
+ color: '#fff'
+ })
+
+ if (result.isConfirmed) {
+ const res = await window.api.invoke('mariadb:drop-user', { versionId: selectedVersion, user, host })
+ if (res.success) {
+ fetchData()
+ } else {
+ Swal.fire({ icon: 'error', title: t('common.error'), text: res.message, background: '#1e1e1e', color: '#fff' })
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+ {t('mariadb_manager.title')}
+
+
+
+ {t('mariadb.version')}
+
+
+ } onClick={fetchData} disabled={loading}>
+ {t('common.refresh')}
+
+
+
+
+ {!selectedVersion && (
+ {t('mariadb_manager.no_active_version')}
+ )}
+
+ {selectedVersion && (
+
+ {/* Databases Section */}
+
+
+
+ {t('mariadb_manager.databases_title')}
+
+ } onClick={handleCreateDb}>
+ {t('mariadb_manager.create_db_btn')}
+
+
+
+
+
+
+ {t('mariadb_manager.db_name')}
+ {t('mariadb_manager.db_size')}
+ {t('common.actions')}
+
+
+
+ {loading ? (
+
+ ) : databases.length === 0 ? (
+ {t('common.no_data')}
+ ) : databases.map(db => (
+
+ {db.name}
+ {db.size.toFixed(2)} MB
+
+
+ handleDeleteDb(db.name)}>
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ {/* Users Section */}
+
+
+
+ {t('mariadb_manager.users_title')}
+
+ } onClick={handleCreateUser}>
+ {t('mariadb_manager.create_user_btn')}
+
+
+
+
+
+
+ {t('mariadb_manager.user_label')}
+ Host
+ {t('common.actions')}
+
+
+
+ {loading ? (
+
+ ) : users.length === 0 ? (
+ {t('common.no_data')}
+ ) : users.map(user => (
+
+
+
+
+ {user.user}
+
+
+
+
+
+ {user.host}
+
+
+
+
+ handleDeleteUser(user.user, user.host)}>
+
+
+
+
+
+ ))}
+
+
+
+
+
+ )}
+
+
+ )
+}
+
+export default MariaDbDatabaseManager
diff --git a/src/renderer/src/locales/en.json b/src/renderer/src/locales/en.json
index 2456718..fcbef45 100644
--- a/src/renderer/src/locales/en.json
+++ b/src/renderer/src/locales/en.json
@@ -322,5 +322,22 @@
"timestamp": "Time",
"details": "Details"
}
+ },
+ "mariadb_manager": {
+ "title": "MariaDB Management",
+ "databases_title": "Databases",
+ "users_title": "Users",
+ "create_db_btn": "New Database",
+ "create_db_title": "Create New Database",
+ "db_name_label": "Database Name",
+ "db_name": "Database",
+ "db_size": "Size",
+ "create_user_btn": "New User",
+ "create_user_title": "Create New User",
+ "user_label": "Username",
+ "password_label": "Password",
+ "delete_db_confirm": "Are you sure you want to delete database '{{db}}'? This action cannot be undone!",
+ "delete_user_confirm": "Are you sure you want to delete user '{{user}}'?",
+ "no_active_version": "Please select an installed MariaDB version first or download one."
}
}
\ No newline at end of file
diff --git a/src/renderer/src/locales/tr.json b/src/renderer/src/locales/tr.json
index df9d7ce..872d129 100644
--- a/src/renderer/src/locales/tr.json
+++ b/src/renderer/src/locales/tr.json
@@ -317,5 +317,22 @@
"timestamp": "Zaman",
"details": "Detay"
}
+ },
+ "mariadb_manager": {
+ "title": "MariaDB Yönetimi",
+ "databases_title": "Veritabanları",
+ "users_title": "Kullanıcılar",
+ "create_db_btn": "Yeni Veritabanı",
+ "create_db_title": "Yeni Veritabanı Oluştur",
+ "db_name_label": "Veritabanı Adı",
+ "db_name": "Veritabanı",
+ "db_size": "Boyut",
+ "create_user_btn": "Yeni Kullanıcı",
+ "create_user_title": "Yeni Kullanıcı Oluştur",
+ "user_label": "Kullanıcı Adı",
+ "password_label": "Şifre",
+ "delete_db_confirm": "'{{db}}' veritabanını silmek istediğinize emin misiniz? Bu işlem geri alınamaz!",
+ "delete_user_confirm": "'{{user}}' kullanıcısını silmek istediğinize emin misiniz?",
+ "no_active_version": "Lütfen önce yüklü bir MariaDB sürümü seçin veya bir sürüm indirin."
}
}
\ No newline at end of file