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')} + + + + + + + {!selectedVersion && ( + {t('mariadb_manager.no_active_version')} + )} + + {selectedVersion && ( + + {/* Databases Section */} + + + + {t('mariadb_manager.databases_title')} + + + + + + + + {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')} + + + + + + + + {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