diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 8d066b5..4c38ede 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -1043,6 +1043,25 @@ export function registerIpcHandlers(): void { }, dbName, savePath, compress, (msg) => log(msg)) }) + ipcMain.handle('mariadb:processlist', async (_event, versionId) => { + try { + return await mariaDbManagerService.getProcessList(versionId) + } catch (error: any) { + console.error('Failed to get process list:', error) + return [] + } + }) + + ipcMain.handle('mariadb:kill-process', async (_event, { versionId, processId }) => { + try { + await mariaDbManagerService.killProcess(versionId, processId) + return { success: true } + } catch (error: any) { + console.error('Failed to kill process:', error) + return { success: false, error: error.message } + } + }) + // CLI & Environment ipcMain.handle('cli:get-config', async () => { const settings = configService.getSettings() diff --git a/src/main/services/CliBinService.ts b/src/main/services/CliBinService.ts index 41454da..71eef45 100644 --- a/src/main/services/CliBinService.ts +++ b/src/main/services/CliBinService.ts @@ -134,25 +134,21 @@ export class CliBinService { if (process.platform !== 'win32') return try { - // Use PowerShell to manage PATH safely. - // 1. Get current User PATH - // 2. Add binDir if not exists - // 3. Set new PATH - // 4. Broadcast WM_SETTINGCHANGE to notify system const binDir = this.getBinDirInternal() + // We use Base64 encoding for the PS command to avoid any escaping issues with quotes, semicolons, etc. const psScript = ` + $binDir = '${binDir}' $currentPath = [Environment]::GetEnvironmentVariable('Path', 'User') - if ($currentPath -notlike '*${binDir}*') { + if ($currentPath -notlike "*$binDir*") { $newPath = $currentPath if ($newPath -and -not $newPath.EndsWith(';')) { $newPath += ';' } - $newPath += '${binDir}' + $newPath += $binDir [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') - # Notify Windows of the change (WM_SETTINGCHANGE) $signature = @' - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] - public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult); +[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] +public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult); '@ $type = Add-Type -MemberDefinition $signature -Name "Win32" -Namespace "Native" -PassThru -ErrorAction SilentlyContinue if ($type) { @@ -164,7 +160,9 @@ export class CliBinService { } ` - const { stdout } = await execAsync(`powershell -Command "${psScript.replace(/\n/g, ' ')}"`) + const encodedCommand = Buffer.from(psScript, 'utf16le').toString('base64') + const { stdout } = await execAsync(`powershell -EncodedCommand ${encodedCommand}`) + if (stdout.includes('PATH_UPDATED')) { console.log(`Successfully added Multi-PHP bin to User PATH: ${binDir}`) } diff --git a/src/main/services/MariaDbManagerService.ts b/src/main/services/MariaDbManagerService.ts index 7a919eb..e4c6682 100644 --- a/src/main/services/MariaDbManagerService.ts +++ b/src/main/services/MariaDbManagerService.ts @@ -2,6 +2,7 @@ import fs from 'fs' import path from 'path' import { app } from 'electron' import { configService } from './ConfigService' +import { mariaDbOperationService } from './MariaDbOperationService' export interface MariaDbVersion { id: string @@ -156,6 +157,64 @@ export class MariaDbManagerService { } return null } + + async getProcessList(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() + const config = { + binPath: binaryPath, + host: '127.0.0.1', + port: version.port, + rootUser: 'root', + rootPass: settings.mariaDbPasswords?.[versionId] || '' + } + + const result = await mariaDbOperationService.runQuery(config, 'SHOW FULL PROCESSLIST') + if (!result.success) throw new Error(result.error) + + // Parse mysql tab-separated output + const lines = result.output.split('\n').filter(l => l.trim().length > 0) + if (lines.length === 0) return [] + + const headers = lines[0].split('\t') + const rows = lines.slice(1).map(line => { + const values = line.split('\t') + const row: any = {} + headers.forEach((h, i) => { + row[h] = values[i] === 'NULL' ? null : values[i] + }) + return row + }) + + return rows + } + + async killProcess(versionId: string, processId: 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() + const config = { + binPath: binaryPath, + host: '127.0.0.1', + port: version.port, + rootUser: 'root', + rootPass: settings.mariaDbPasswords?.[versionId] || '' + } + + const result = await mariaDbOperationService.runQuery(config, `KILL ${processId}`) + if (!result.success) throw new Error(result.error) + + return true + } } export const mariaDbManagerService = new MariaDbManagerService() diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 6920c2d..8cd6e66 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -166,6 +166,12 @@ function App(): JSX.Element { const [pmaProgress, setPmaProgress] = useState(0) const [isMariaDbWizardOpen, setIsMariaDbWizardOpen] = useState(false) const [isMariaDbExportWizardOpen, setIsMariaDbExportWizardOpen] = useState(false) + + // MariaDB Process List State + const [processList, setProcessList] = useState([]) + const [isProcessListOpen, setIsProcessListOpen] = useState(false) + const [selectedMariaDbId, setSelectedMariaDbId] = useState(null) + const [isProcessListLoading, setIsProcessListLoading] = useState(false) const [isNginxConfigOpen, setIsNginxConfigOpen] = useState(false) const [nginxConfig, setNginxConfig] = useState('') const [isApacheConfigOpen, setIsApacheConfigOpen] = useState(false) @@ -327,12 +333,20 @@ function App(): JSX.Element { if (logDialogOpen && selectedService) { logInterval = setInterval(() => { fetchLogs(selectedService) - }, 1000) + }, 3000) + } + + let processInterval: NodeJS.Timeout | null = null + if (isProcessListOpen && selectedMariaDbId) { + processInterval = setInterval(() => { + fetchProcessList(selectedMariaDbId) + }, 3000) } return () => { clearInterval(interval) if (logInterval) clearInterval(logInterval) + if (processInterval) clearInterval(processInterval) if (typeof removePhpListener === 'function') removePhpListener() if (typeof removeMariaDbListener === 'function') removeMariaDbListener() if (typeof removeNginxListener === 'function') removeNginxListener() @@ -866,6 +880,45 @@ function App(): JSX.Element { setIsPhpExtensionsOpen(true) } + const handleOpenProcessList = async (id: string) => { + setSelectedMariaDbId(id) + setIsProcessListOpen(true) + fetchProcessList(id) + } + + const fetchProcessList = async (id: string) => { + if (!window.api) return + setIsProcessListLoading(true) + const list = await window.api.invoke('mariadb:processlist', id) + setProcessList(list || []) + setIsProcessListLoading(false) + } + + const handleKillProcess = async (id: string, pid: string) => { + if (!window.api) return + const result = await Swal.fire({ + title: t('mariadb_process_monitor.kill_confirm'), + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6', + confirmButtonText: t('mariadb_process_monitor.kill_btn'), + cancelButtonText: t('common.cancel'), + background: '#1a1d21', + color: '#fff' + }) + + if (result.isConfirmed) { + const res = await window.api.invoke('mariadb:kill-process', { versionId: id, processId: pid }) + if (res.success) { + setNotification({ open: true, message: t('common.ok'), severity: 'success' }) + fetchProcessList(id) + } else { + setNotification({ open: true, message: res.error, severity: 'error' }) + } + } + } + const installedPhpVersions = phpVersions.filter(v => v.status === 'installed') return ( @@ -1584,6 +1637,9 @@ function App(): JSX.Element { handleOpenLogs(`mariadb:${v.version}`)} title={t('diagnostics.logs')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}> + handleOpenProcessList(v.id)} title={t('mariadb_process_monitor.title')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)', '&:hover': { color: 'primary.main' } }}> + + handleResetData(`mariadb:${v.version}`)} title={t('common.reset')} size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)', '&:hover': { color: 'error.main' } }}> @@ -2106,8 +2162,106 @@ function App(): JSX.Element { open={isMariaDbExportWizardOpen} onClose={() => setIsMariaDbExportWizardOpen(false)} mariaDbVersions={mariaDbVersions} + versionId={selectedMariaDbId || ''} /> + {/* MariaDB Process List Dialog */} + setIsProcessListOpen(false)} + maxWidth="lg" + fullWidth + PaperProps={{ + sx: { + bgcolor: '#1a1d21', + color: '#fff', + borderRadius: 2 + } + }} + > + + + + + {t('mariadb_process_monitor.title')} - {selectedMariaDbId} + + + + + setIsProcessListOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}> + + + + + + + + + + {t('mariadb_process_monitor.columns.id')} + {t('mariadb_process_monitor.columns.user')} + {t('mariadb_process_monitor.columns.host')} + {t('mariadb_process_monitor.columns.db')} + {t('mariadb_process_monitor.columns.command')} + {t('mariadb_process_monitor.columns.time')} + {t('mariadb_process_monitor.columns.state')} + {t('mariadb_process_monitor.columns.info')} + {t('mariadb_process_monitor.columns.progress')} + {t('common.actions')} + + + + {processList.length === 0 ? ( + + + {t('mariadb_process_monitor.no_processes')} + + + ) : ( + processList.map((p) => ( + + {p.Id} + {p.User} + {p.Host} + {p.db || NULL} + + + + {p.Time}s + {p.State} + + {p.Info || ''} + + + {p.Progress ? `${parseFloat(p.Progress).toFixed(2)}%` : '-'} + + + + + + )) + )} + +
+
+
+
+ setIsNginxConfigOpen(false)} diff --git a/src/renderer/src/components/MariaDbExportWizard.tsx b/src/renderer/src/components/MariaDbExportWizard.tsx index b62f413..ba5ac18 100644 --- a/src/renderer/src/components/MariaDbExportWizard.tsx +++ b/src/renderer/src/components/MariaDbExportWizard.tsx @@ -35,9 +35,10 @@ interface MariaDbExportWizardProps { open: boolean onClose: () => void mariaDbVersions: any[] + versionId?: string } -const MariaDbExportWizard: React.FC = ({ open, onClose, mariaDbVersions }) => { +const MariaDbExportWizard: React.FC = ({ open, onClose, mariaDbVersions, versionId }) => { const { t } = useTranslation() const steps = [ @@ -65,10 +66,14 @@ const MariaDbExportWizard: React.FC = ({ open, onClose setSavePath('') setLogs([]) setExportResult(null) - const installedVersion = mariaDbVersions.find(v => v.status === 'installed') - if (installedVersion) setSelectedVersion(installedVersion.id) + if (versionId) { + setSelectedVersion(versionId) + } else { + const installedVersion = mariaDbVersions.find(v => v.status === 'installed') + if (installedVersion) setSelectedVersion(installedVersion.id) + } } - }, [open, mariaDbVersions]) + }, [open, mariaDbVersions, versionId]) useEffect(() => { if (logEndRef.current) { diff --git a/src/renderer/src/locales/en.json b/src/renderer/src/locales/en.json index d6ca3c8..7f696c9 100644 --- a/src/renderer/src/locales/en.json +++ b/src/renderer/src/locales/en.json @@ -1,5 +1,6 @@ { "common": { + "actions": "Actions", "start": "Start", "stop": "Stop", "restart": "Restart", @@ -254,5 +255,23 @@ "prev_match": "Previous (Shift+Enter)", "next_match": "Next (Enter)" } + }, + "mariadb_process_monitor": { + "title": "MariaDB Process List", + "kill_btn": "KILL", + "kill_confirm": "Are you sure you want to kill this process?", + "refresh_btn": "Refresh", + "no_processes": "No active processes found.", + "columns": { + "id": "ID", + "user": "User", + "host": "Host", + "db": "db", + "command": "Command", + "time": "Time", + "state": "State", + "info": "Info", + "progress": "Progress" + } } } \ No newline at end of file diff --git a/src/renderer/src/locales/tr.json b/src/renderer/src/locales/tr.json index 2b38c22..fe17186 100644 --- a/src/renderer/src/locales/tr.json +++ b/src/renderer/src/locales/tr.json @@ -1,5 +1,6 @@ { "common": { + "actions": "İşlem", "start": "Başlat", "stop": "Durdur", "restart": "Yeniden Başlat", @@ -79,7 +80,7 @@ "updated": "Proje başarıyla güncellendi.", "deleted": "Proje silindi.", "delete_confirm": "Bu projeyi silmek istediğinize emin misiniz? Dosyalarınız silinmez, sadece listeden kaldırılır.", - "browse" : "Gözat" + "browse": "Gözat" }, "settings": { "general": "Genel Ayarlar", @@ -249,5 +250,23 @@ "prev_match": "Önceki (Shift+Enter)", "next_match": "Sonraki (Enter)" } + }, + "mariadb_process_monitor": { + "title": "MariaDB Süreç Listesi (Process List)", + "kill_btn": "KILL", + "kill_confirm": "Bu süreci sonlandırmak istediğinizden emin misiniz?", + "refresh_btn": "Yenile", + "no_processes": "Aktif süreç bulunamadı.", + "columns": { + "id": "ID", + "user": "Kullanıcı", + "host": "Host", + "db": "Veritabanı", + "command": "Komut", + "time": "Süre", + "state": "Durum", + "info": "Bilgi", + "progress": "İlerleme" + } } } \ No newline at end of file