feat: initialize electron main process with tray support and implement core IPC service handlers

This commit is contained in:
Ümit Tunç
2026-04-02 12:13:29 +03:00
parent 51b3157d1b
commit f059ebb83b
5 changed files with 253 additions and 57 deletions
+2 -2
View File
@@ -46,8 +46,8 @@ function createTray(): void {
function createWindow(): void {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
width: 1400,
height: 900,
show: false,
autoHideMenuBar: true,
icon: join(__dirname, '../../resources/icon.png'),
+48 -48
View File
@@ -256,6 +256,54 @@ async function restartApache(): Promise<{ success: boolean; message: string }> {
}
export function registerIpcHandlers(): void {
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()
return {
enabled: settings.cliEnvEnabled !== false,
defaultPhp: settings.defaultPhpVersion,
defaultMariaDb: settings.defaultMariaDbVersion,
binDir: cliBinService.getBinDir(),
aliases: cliBinService.getAllAliases()
}
})
ipcMain.handle('cli:set-config', async (_event, config: any) => {
const settings = configService.getSettings()
configService.updateSettings({
...settings,
cliEnvEnabled: config.enabled,
defaultPhpVersion: config.defaultPhp,
defaultMariaDbVersion: config.defaultMariaDb
})
await cliAliasService.generateAliases()
return { success: true }
})
ipcMain.handle('cli:sync', async () => {
await cliAliasService.generateAliases()
return { success: true }
})
// Service management
ipcMain.handle('services:reload', async (_event, serviceName: string) => {
if (serviceName === 'nginx') return await reloadNginx()
@@ -1042,52 +1090,4 @@ export function registerIpcHandlers(): void {
rootPass: rootPass || ''
}, 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()
return {
enabled: settings.cliEnvEnabled !== false,
defaultPhp: settings.defaultPhpVersion,
defaultMariaDb: settings.defaultMariaDbVersion,
binDir: cliBinService.getBinDir(),
aliases: cliBinService.getAllAliases()
}
})
ipcMain.handle('cli:set-config', async (_event, config: any) => {
const settings = configService.getSettings()
configService.updateSettings({
...settings,
cliEnvEnabled: config.enabled,
defaultPhpVersion: config.defaultPhp,
defaultMariaDbVersion: config.defaultMariaDb
})
await cliAliasService.generateAliases()
return { success: true }
})
ipcMain.handle('cli:sync', async () => {
await cliAliasService.generateAliases()
return { success: true }
})
}
+195 -5
View File
@@ -1,4 +1,7 @@
import { useState, useEffect } from 'react'
import Prism from 'prismjs'
import 'prismjs/themes/prism-tomorrow.css'
import 'prismjs/components/prism-sql'
import logo from './assets/logo.svg'
import {
Box,
@@ -169,9 +172,10 @@ function App(): JSX.Element {
// MariaDB Process List State
const [processList, setProcessList] = useState<any[]>([])
const [isProcessListOpen, setIsProcessListOpen] = useState(false)
const [selectedMariaDbId, setSelectedMariaDbId] = useState<string | null>(null)
const [isProcessListLoading, setIsProcessListLoading] = useState(false)
const [selectedMariaDbId, setSelectedMariaDbId] = useState<string>('')
const [isProcessListOpen, setIsProcessListOpen] = useState(false)
const [selectedQuery, setSelectedQuery] = useState<string | null>(null)
const [isNginxConfigOpen, setIsNginxConfigOpen] = useState(false)
const [nginxConfig, setNginxConfig] = useState('')
const [isApacheConfigOpen, setIsApacheConfigOpen] = useState(false)
@@ -193,6 +197,25 @@ function App(): JSX.Element {
return { filePath, line, host }
}
// Auto-refresh for Process Monitor
useEffect(() => {
if (activeTab === 'db_monitor' && selectedMariaDbId) {
fetchProcessList(selectedMariaDbId)
const interval = setInterval(() => {
fetchProcessList(selectedMariaDbId)
}, 3000)
return () => clearInterval(interval)
}
return () => { }
}, [activeTab, selectedMariaDbId])
useEffect(() => {
if (activeTab === 'db_monitor' && !selectedMariaDbId && mariaDbVersions.length > 0) {
const installed = mariaDbVersions.find(v => v.status === 'installed')
if (installed) setSelectedMariaDbId(installed.id)
}
}, [activeTab, mariaDbVersions])
const fetchStatus = async () => {
if (window.api && window.api.getServiceStatus) {
const currentStatus = await window.api.getServiceStatus()
@@ -882,8 +905,7 @@ function App(): JSX.Element {
const handleOpenProcessList = async (id: string) => {
setSelectedMariaDbId(id)
setIsProcessListOpen(true)
fetchProcessList(id)
setActiveTab('db_monitor')
}
const fetchProcessList = async (id: string) => {
@@ -923,6 +945,67 @@ function App(): JSX.Element {
return (
<>
<Dialog
open={!!selectedQuery}
onClose={() => setSelectedQuery(null)}
maxWidth="lg"
fullWidth
PaperProps={{
sx: {
bgcolor: '#1a1d21',
backgroundImage: 'none',
color: '#fff',
borderRadius: 2,
border: '1px solid rgba(255,255,255,0.1)'
}
}}
>
<DialogTitle sx={{ borderBottom: '1px solid rgba(255,255,255,0.1)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 800 }}>{t('mariadb_process_monitor.query_details')}</Typography>
<IconButton onClick={() => setSelectedQuery(null)} sx={{ color: 'rgba(255,255,255,0.5)' }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent sx={{ p: 0 }}>
<Box sx={{ p: 3, maxHeight: '70vh', overflow: 'auto' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
<code
className="language-sql"
dangerouslySetInnerHTML={{
__html: Prism.highlight(
(selectedQuery || '')
.replace(/\\r\\n/g, '\n')
.replace(/\\n/g, '\n')
.replace(/\\t/g, ' '),
Prism.languages.sql,
'sql'
)
}}
/>
</pre>
</Box>
</DialogContent>
<DialogActions sx={{ borderTop: '1px solid rgba(255,255,255,0.1)', p: 2 }}>
<Button
startIcon={<ContentCopyIcon />}
onClick={() => {
navigator.clipboard.writeText(selectedQuery || '')
Swal.fire({
icon: 'success',
title: t('common.copied'),
toast: true,
position: 'top-end',
timer: 2000,
showConfirmButton: false
})
}}
>
{t('common.copy')}
</Button>
<Button onClick={() => setSelectedQuery(null)}>{t('common.close')}</Button>
</DialogActions>
</Dialog>
<GlobalStyles styles={{
'.swal2-container': {
zIndex: '9999 !important'
@@ -985,6 +1068,11 @@ function App(): JSX.Element {
))}
</Select>
</FormControl>
<Tooltip title={t('mariadb_process_monitor.title')}>
<IconButton color="inherit" onClick={() => setActiveTab('db_monitor')} sx={{ '&:hover': { color: 'primary.main' } }}>
<TerminalIcon />
</IconButton>
</Tooltip>
<IconButton color="inherit" onClick={() => setActiveTab('settings')} sx={{ ml: 1 }}>
<SettingsIcon />
</IconButton>
@@ -1023,6 +1111,12 @@ function App(): JSX.Element {
<ListItemText primary={t('common.tools')} />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton selected={activeTab === 'db_monitor'} onClick={() => setActiveTab('db_monitor')}>
<ListItemIcon><DbIcon color={activeTab === 'db_monitor' ? 'primary' : 'inherit'} /></ListItemIcon>
<ListItemText primary={t('mariadb_process_monitor.title')} />
</ListItemButton>
</ListItem>
</List>
<Divider />
<List>
@@ -1038,7 +1132,7 @@ function App(): JSX.Element {
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<Toolbar />
<Container maxWidth="md">
<Container maxWidth={false} sx={{ py: 2 }}>
{activeTab === 'settings' && (
<Box>
@@ -1657,6 +1751,102 @@ function App(): JSX.Element {
</>
)}
{activeTab === 'db_monitor' && (
<Box sx={{ mt: 2 }}>
<Paper sx={{ p: 3, bgcolor: 'rgba(255,255,255,0.03)', borderRadius: 2 }}>
<Box sx={{ mb: 3, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box>
<Typography variant="h5" sx={{ fontWeight: 800, color: '#fff', mb: 1 }}>
{t('mariadb_process_monitor.title')}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.5)' }}>
{t('mariadb_process_monitor.auto_refresh_desc')}
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
<FormControl size="small" variant="outlined" sx={{ minWidth: 200 }}>
<InputLabel sx={{ color: 'rgba(255,255,255,0.5)' }}>{t('settings.mariadb_versions')}</InputLabel>
<Select
value={selectedMariaDbId}
onChange={(e) => setSelectedMariaDbId(e.target.value)}
label={t('settings.mariadb_versions')}
sx={{
color: '#fff',
'.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.1)' }
}}
>
{mariaDbVersions.filter(v => v.status === 'installed').map(v => (
<MenuItem key={v.id} value={v.id}>MariaDB {v.version}</MenuItem>
))}
</Select>
</FormControl>
{isProcessListLoading && <CircularProgress size={20} sx={{ color: '#39A7FF' }} />}
</Box>
</Box>
<TableContainer component={Paper} variant="outlined" sx={{ bgcolor: 'rgba(0,0,0,0.2)', borderColor: 'rgba(255,255,255,0.05)', maxHeight: '60vh' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>ID</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>USER</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>HOST</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>DB</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>COMMAND</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>TIME</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>STATE</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>INFO</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>PROGRESS</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem', textAlign: 'right' }}>ACTION</TableCell>
</TableRow>
</TableHead>
<TableBody>
{processList.length === 0 ? (
<TableRow>
<TableCell colSpan={10} align="center" sx={{ py: 4, color: 'rgba(255,255,255,0.3)', borderColor: 'rgba(255,255,255,0.05)' }}>
{t('common.no_data')}
</TableCell>
</TableRow>
) : (
processList.map((proc) => (
<TableRow key={proc.Id} hover sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)', fontFamily: 'monospace' }}>{proc.Id}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: '#39A7FF' }}>{proc.User}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)' }}>{proc.Host}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: proc.db ? '#ff9f43' : 'rgba(255,255,255,0.3)' }}>{proc.db || 'system'}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: '#33d9b2' }}>{proc.Command}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)' }}>{proc.Time}s</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: proc.State === 'Query' ? '#39A7FF' : 'rgba(255,255,255,0.5)' }}>{proc.State || '-'}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)', maxWidth: 300, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<Tooltip title={t('mariadb_process_monitor.click_to_view_sql')}>
<Box
onClick={() => setSelectedQuery(proc.Info)}
sx={{
cursor: proc.Info ? 'pointer' : 'default',
'&:hover': { color: proc.Info ? '#39A7FF' : 'inherit' },
transition: 'color 0.2s'
}}
>
{proc.Info || '-'}
</Box>
</Tooltip>
</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: '#39A7FF' }}>{proc.Progress}%</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', textAlign: 'right' }}>
<IconButton size="small" color="error" onClick={() => handleKillProcess(selectedMariaDbId, proc.Id)}>
<DeleteIcon fontSize="inherit" />
</IconButton>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
</Paper>
</Box>
)}
{activeTab === 'tools' && (
<Box>
<Typography variant="h5" sx={{ mb: 3, fontWeight: 'medium' }}>
+4 -1
View File
@@ -257,10 +257,13 @@
}
},
"mariadb_process_monitor": {
"title": "MariaDB Process List",
"title": "MariaDB Process Monitor",
"auto_refresh_desc": "The process list is automatically refreshed every 3 seconds.",
"kill_btn": "KILL",
"kill_confirm": "Are you sure you want to kill this process?",
"refresh_btn": "Refresh",
"click_to_view_sql": "Click to View SQL Query",
"query_details": "Query Details",
"no_processes": "No active processes found.",
"columns": {
"id": "ID",
+4 -1
View File
@@ -252,10 +252,13 @@
}
},
"mariadb_process_monitor": {
"title": "MariaDB Süreç Listesi (Process List)",
"title": "MariaDB Süreç İzleyici",
"auto_refresh_desc": "Süreç listesi her 3 saniyede bir otomatik olarak yenilenir.",
"kill_btn": "KILL",
"kill_confirm": "Bu süreci sonlandırmak istediğinizden emin misiniz?",
"refresh_btn": "Yenile",
"click_to_view_sql": "SQL Sorgusunu Görüntülemek İçin Tıklayın",
"query_details": "Sorgu Detayları",
"no_processes": "Aktif süreç bulunamadı.",
"columns": {
"id": "ID",