From e7581d442ad0923dc24eb0a71b9cf2e89e11f8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Fri, 3 Apr 2026 15:24:20 +0300 Subject: [PATCH] feat: implement multi-service management dashboard with project, database, and PHP extension support --- service_events.json | 635 ++++++++++++++++++ src/main/ipc/index.ts | 4 + src/renderer/src/App.tsx | 349 ++++++---- .../components/Dashboard/DashboardDetails.tsx | 69 +- .../components/Dashboard/DashboardManager.tsx | 48 +- .../src/components/MariaDbDatabaseManager.tsx | 78 +-- .../src/components/PhpExtensionManager.tsx | 81 ++- .../ProjectManager/ProjectDetails.tsx | 126 ++-- .../ProjectManager/ProjectManager.tsx | 5 +- src/renderer/src/locales/en.json | 2 + src/renderer/src/locales/tr.json | 2 + 11 files changed, 1066 insertions(+), 333 deletions(-) diff --git a/service_events.json b/service_events.json index f496842..166307c 100644 --- a/service_events.json +++ b/service_events.json @@ -1,4 +1,639 @@ [ + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "5cna280nd", + "timestamp": 1775215993040 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "16dhybbv5", + "timestamp": 1775215993039 + }, + { + "service": "php:5.6.40", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "hilyatq39", + "timestamp": 1775215993031 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "hbnrqkyoy", + "timestamp": 1775215993023 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "m5oe9unpm", + "timestamp": 1775215978795 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "g9k4uuyb6", + "timestamp": 1775215978779 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "igj6t393e", + "timestamp": 1775215978760 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "bv7xjvoud", + "timestamp": 1775215978742 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "w225k0xie", + "timestamp": 1775215978291 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "x5k5b4mth", + "timestamp": 1775215978272 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "51ppyzb6e", + "timestamp": 1775215977753 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "yq4trg6v9", + "timestamp": 1775215977702 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "2cesewr4o", + "timestamp": 1775215972222 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "vrt0ac3xz", + "timestamp": 1775215972214 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "tz9lm7fhp", + "timestamp": 1775215972206 + }, + { + "service": "php:5.6.40", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "xrg2toecz", + "timestamp": 1775215972199 + }, + { + "service": "php:8.2.30", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "82hwbc4ly", + "timestamp": 1775215972191 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "2noq8lw9w", + "timestamp": 1775215972185 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "9oha4uilw", + "timestamp": 1775215972159 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "jrwvf5v1v", + "timestamp": 1775215972157 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "1czbs8x8z", + "timestamp": 1775215964037 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "0up4w85t2", + "timestamp": 1775215964021 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "gxnmtaz6o", + "timestamp": 1775215964004 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "dzb88em3m", + "timestamp": 1775215963987 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "bu12qdsrx", + "timestamp": 1775215963482 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "kpy95fkd1", + "timestamp": 1775215963459 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "6z4v7vlbz", + "timestamp": 1775215962983 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "hs28kfp1j", + "timestamp": 1775215962935 + }, + { + "service": "php:5.6.40", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "xbrt3pru2", + "timestamp": 1775215960741 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "a9dtwotqn", + "timestamp": 1775215960735 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "f1p0g2q9k", + "timestamp": 1775215960728 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "ybtvisrvv", + "timestamp": 1775215960706 + }, + { + "service": "php:8.2.30", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "p5uu02qoq", + "timestamp": 1775215960705 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "3l8qhvac1", + "timestamp": 1775215960695 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "rradgo2b9", + "timestamp": 1775215905340 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "7xbqz2ep0", + "timestamp": 1775215904095 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "439aj13y2", + "timestamp": 1775215901797 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "mmfuuw4ec", + "timestamp": 1775215900591 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "vdc11eywn", + "timestamp": 1775215869698 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "nvd414ghx", + "timestamp": 1775215868488 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "8acx9s7xg", + "timestamp": 1775215865230 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "sxtv8gp6o", + "timestamp": 1775215863969 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "7i2g4nqu5", + "timestamp": 1775215838776 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "5oab99keg", + "timestamp": 1775215837463 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "spcyyxy1w", + "timestamp": 1775215831998 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "vbhzxidnn", + "timestamp": 1775215796300 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "gm6ejjr5a", + "timestamp": 1775215796279 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "i8npou62c", + "timestamp": 1775215796260 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "6ncfpzo9k", + "timestamp": 1775215796242 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "im5nypjfv", + "timestamp": 1775215795208 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "za9mzxy37", + "timestamp": 1775215792771 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "mhlcdoxmt", + "timestamp": 1775215792764 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "eycw4hxb5", + "timestamp": 1775215792756 + }, + { + "service": "php:5.6.40", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "2z7211041", + "timestamp": 1775215792749 + }, + { + "service": "php:8.2.30", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "jg1diypfu", + "timestamp": 1775215792743 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "6rg35x2f8", + "timestamp": 1775215792736 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "ydizt9o8j", + "timestamp": 1775215792717 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "afhcuxp1i", + "timestamp": 1775215792707 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "u0nzj9fmp", + "timestamp": 1775215787250 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "lakde3svs", + "timestamp": 1775215781259 + }, + { + "service": "nginx", + "action": "stop", + "status": "info", + "id": "89gx0ty64", + "timestamp": 1775215781255 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "sj461rtvu", + "timestamp": 1775215438924 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "y8yibjkty", + "timestamp": 1775215438908 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "c2gx42hum", + "timestamp": 1775215438891 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "zzhvseonm", + "timestamp": 1775215438875 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "j39z9a0js", + "timestamp": 1775215438399 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "wjz63bmwh", + "timestamp": 1775215438380 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "cl5wh9qzj", + "timestamp": 1775215437873 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "chvykaz6s", + "timestamp": 1775215437824 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "e1p660xfr", + "timestamp": 1775215430918 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "zcp93q2mf", + "timestamp": 1775215430911 + }, + { + "service": "php:8.5.4", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "9lsi8dk1j", + "timestamp": 1775215430903 + }, + { + "service": "php:5.6.40", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "g8a4x1i2q", + "timestamp": 1775215430896 + }, + { + "service": "php:8.2.30", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "tjrzyhopg", + "timestamp": 1775215430890 + }, + { + "service": "php:7.4.33", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "wu418ce9i", + "timestamp": 1775215430884 + }, + { + "service": "nginx", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "8tbns1luu", + "timestamp": 1775215430877 + }, + { + "service": "apache", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "bfecmmhs2", + "timestamp": 1775215430863 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "vhbul5hj6", + "timestamp": 1775215425999 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "xhdvhe9v9", + "timestamp": 1775215425982 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "whbiv7hnd", + "timestamp": 1775215425964 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "njoudmced", + "timestamp": 1775215425945 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "fv15yavsp", + "timestamp": 1775215425449 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "q646z3m24", + "timestamp": 1775215425427 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "i9b435j7i", + "timestamp": 1775215424957 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "g9eb3ymy8", + "timestamp": 1775215424900 + }, { "service": "php:5.6.40", "action": "start", diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index ddcd359..eaaa4bd 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -719,6 +719,10 @@ export function registerIpcHandlers(): void { return processManager.getStatuses() }) + ipcMain.handle('service:status', async (_event, name: string) => { + return processManager.getServiceState(name); + }) + ipcMain.handle('services:logs', async (_event, serviceName: string) => { return processManager.getLogs(serviceName) }) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index a48fb33..5648df4 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -459,79 +459,130 @@ function App(): JSX.Element { setSettings(prev => ({ ...prev, language: newLang })) } - const handleStartAll = async () => { - if (!window.api) return - const criticalServices = ['nginx', 'apache'] - for (const s of criticalServices) { - if (status[s]?.status === 'stopped') setStatus(prev => ({ ...prev, [s]: { status: 'starting' } })) + const runServiceOperation = async (title: string, services: string[], operation: 'start' | 'stop') => { + const results: Record = {}; + const getDisplayLabel = (srv: string) => { + if (srv === 'nginx') return 'Nginx'; + if (srv === 'apache') return 'Apache'; + if (srv.startsWith('php:')) return `PHP ${srv.split(':')[1]}`; + if (srv.startsWith('mariadb:')) return `MariaDB ${srv.split(':')[1]}`; + return srv; + }; + + const updateSwal = (currentSrv?: string) => { + const html = ` +
+ ${services.map(srv => { + const label = getDisplayLabel(srv); + const res = results[srv]; + let icon = ''; + let statusColor = 'rgba(255,255,255,0.4)'; + let note = operation === 'start' ? t('common.ready') : t('common.running'); + + if (res?.success) { + icon = ''; + statusColor = '#33d9b2'; + note = operation === 'start' ? t('common.started') : t('common.stopped'); + } else if (res?.error) { + icon = ''; + statusColor = '#ff5252'; + note = t('common.error'); + } else if (srv === currentSrv) { + icon = ''; + statusColor = '#39A7FF'; + note = operation === 'start' ? t('common.starting') : t('common.stopping'); + } + + return '
' + + '' + icon + ' ' + label + '' + + '' + note.toUpperCase() + '' + + '
'; + }).join('')} +
+ `; + Swal.update({ html }); + }; + + Swal.fire({ + title: title.toUpperCase(), + html: '
', + allowOutsideClick: false, + showConfirmButton: false, + background: '#1a1d21', + color: '#fff', + customClass: { + title: 'swal-title-small' + }, + didOpen: () => { + updateSwal(); + } + }); + + for (const srv of services) { + updateSwal(srv); + try { + const res = operation === 'start' ? await window.api.startService(srv) : await window.api.stopService(srv); + + if (res.success) { + // Poll for status confirmation + let attempts = 0; + const maxAttempts = 15; // ~7.5 seconds + let confirmed = false; + + while (attempts < maxAttempts) { + await new Promise(resolve => setTimeout(resolve, 500)); + const statusRes = await window.api.invoke('service:status', srv); + + if (operation === 'start' && statusRes === 'running') { + confirmed = true; + break; + } + if (operation === 'stop' && statusRes === 'stopped') { + confirmed = true; + break; + } + attempts++; + } + + + results[srv] = { success: confirmed, error: !confirmed ? t('common.timeout') : null }; + } else { + results[srv] = { success: false, error: res.message }; + } + } catch (err: any) { + results[srv] = { success: false, error: err.message }; + } + updateSwal(); } - const phpToStart = phpVersions.filter(v => v.status === 'installed').map(v => `php:${v.version}`) - for (const s of phpToStart) { - if ((status[s]?.status || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: { status: 'starting' } })) - } - const mariaDbToStart = mariaDbVersions.filter(v => v.status === 'installed').map(v => `mariadb:${v.version}`) - for (const s of mariaDbToStart) { - if ((status[s]?.status || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: { status: 'starting' } })) - } + const anyError = Object.values(results).some(r => r.error); + + Swal.update({ + icon: anyError ? 'warning' : 'success', + showConfirmButton: true, + confirmButtonText: t('common.close').toUpperCase() + }); - const results = await Promise.all([ - ...criticalServices.filter(s => status[s]?.status !== 'running').map(s => window.api?.startService(s).then((r: any) => ({ service: s, ...r }))), - ...phpToStart.filter(s => (status[s]?.status || 'stopped') !== 'running').map(s => window.api?.startService(s).then((r: any) => ({ service: s, ...r }))), - ...mariaDbToStart.filter(s => (status[s]?.status || 'stopped') !== 'running').map(s => window.api?.startService(s).then((r: any) => ({ service: s, ...r }))) - ]) - - const errors = results.filter(r => r && !r.success) - if (errors.length > 0) { - const errorList = errors.map(e => `${e.service}: ${e.message}`).join('
') - Swal.fire({ - title: t('common.error'), - html: `
${errorList}
`, - icon: 'error', - background: '#1e1e1e', - color: '#fff' - }) - } else { - setNotification({ open: true, message: t('dashboard.all_started'), severity: 'success' }) + // If no error, auto-close after 3s + if (!anyError) { + setTimeout(() => { + if (Swal.isVisible()) Swal.close(); + }, 3000); } - fetchStatus() - } + fetchStatus(); + }; - const handleStopAll = async () => { - if (!window.api) return - - // Show immediate "stopping" UI feedback for all running services - const updatedStatus = { ...status } - Object.keys(updatedStatus).forEach(s => { - if (updatedStatus[s].status === 'running') updatedStatus[s].status = 'stopping' - }) - setStatus(updatedStatus) + const handleStartAll = () => { + const services = ['nginx', 'apache', ...phpVersions.filter(v => v.status === 'installed').map(v => `php:${v.version}`), ...mariaDbVersions.filter(v => v.status === 'installed').map(v => `mariadb:${v.version}`)]; + runServiceOperation(t('dashboard.starting_all'), services, 'start'); + }; - const result = await window.api.invoke('services:stop-all') - - if (!result.success) { - Swal.fire({ - title: t('common.error'), - text: result.message, - icon: 'error', - background: '#1e1e1e', - color: '#fff' - }) - } else { - setNotification({ open: true, message: t('dashboard.all_stopped'), severity: 'success' }) - } - - fetchStatus() - } - - const formatMemory = (bytes?: number) => { - if (!bytes) return '' - const mb = bytes / (1024 * 1024) - if (mb > 1024) return `${(mb / 1024).toFixed(1)} GB` - return `${Math.round(mb)} MB` - } + const handleStopAll = () => { + const services = ['nginx', 'apache', ...phpVersions.filter(v => v.status === 'installed').map(v => `php:${v.version}`), ...mariaDbVersions.filter(v => v.status === 'installed').map(v => `mariadb:${v.version}`)]; + runServiceOperation(t('dashboard.stopping_all'), services, 'stop'); + }; const handleAddProject = async () => { if (!window.api) return @@ -716,7 +767,6 @@ function App(): JSX.Element { setIsPhpExtensionsOpen(true) } - const fetchProcessList = async (id: string) => { if (!window.api) return setIsProcessListLoading(true) @@ -752,6 +802,7 @@ function App(): JSX.Element { const installedPhpVersions = phpVersions.filter(v => v.status === 'installed') + return ( <> - + - - - - - - - - - - - - + + + + + + + + + - setActiveTab('db_monitor')} sx={{ '&:hover': { color: 'primary.main' } }}> - + setActiveTab('db_monitor')} sx={{ p: 0.5, opacity: 0.7, '&:hover': { opacity: 1, color: 'primary.main' } }}> + - setActiveTab('settings')} sx={{ ml: 1 }}> - + + setActiveTab('settings')} sx={{ p: 0.5, opacity: 0.7, '&:hover': { opacity: 1 } }}> + + - setActiveTab('dashboard')}> - - + setActiveTab('dashboard')} sx={{ py: 0.5 }}> + + - setActiveTab('projects')}> - - + setActiveTab('projects')} sx={{ py: 0.5 }}> + + - setActiveTab('tuning')}> - - + setActiveTab('tuning')} sx={{ py: 0.5 }}> + + - setActiveTab('tools')}> - - + setActiveTab('tools')} sx={{ py: 0.5 }}> + + - setActiveTab('db_manager')}> - - + setActiveTab('db_manager')} sx={{ py: 0.5 }}> + + - setActiveTab('db_monitor')}> - - + setActiveTab('db_monitor')} sx={{ py: 0.5 }}> + + - + - setActiveTab('user_guide')}> - - + setActiveTab('user_guide')} sx={{ py: 0.5 }}> + + - setActiveTab('settings')}> - - + setActiveTab('settings')} sx={{ py: 0.5 }}> + + - setActiveTab('event_monitor')}> - - + setActiveTab('event_monitor')} sx={{ py: 0.5 }}> + + @@ -1402,9 +1501,11 @@ function App(): JSX.Element { t={t} onFetchStatus={fetchStatus} onSelectProject={handleSelectProject} + runServiceOperation={runServiceOperation} /> )} + {activeTab === 'db_monitor' && ( @@ -1634,13 +1735,10 @@ function App(): JSX.Element { status[`php:${p.phpVersion}`]?.status === 'running' ); if (isRunning) { - await window.api.stopService(p.serverType === 'apache' ? 'apache' : 'nginx'); - await window.api.stopService(`php:${p.phpVersion}`); + runServiceOperation(t('projects.stop_site'), [p.serverType === 'apache' ? 'apache' : 'nginx', `php:${p.phpVersion}`], 'stop'); } else { - await window.api.startService(p.serverType === 'apache' ? 'apache' : 'nginx'); - await window.api.startService(`php:${p.phpVersion}`); + runServiceOperation(t('projects.start_site'), [p.serverType === 'apache' ? 'apache' : 'nginx', `php:${p.phpVersion}`], 'start'); } - fetchStatus(); }} onOpenSite={(p: any, isHostMapped: boolean) => { const port = p.serverType === 'apache' ? settings.apachePort : settings.nginxPort @@ -1665,6 +1763,7 @@ function App(): JSX.Element { } }} onManageDb={() => setActiveTab('db_manager')} + onRefreshProjects={fetchProjects} /> )} diff --git a/src/renderer/src/components/Dashboard/DashboardDetails.tsx b/src/renderer/src/components/Dashboard/DashboardDetails.tsx index dbeefb9..a677318 100644 --- a/src/renderer/src/components/Dashboard/DashboardDetails.tsx +++ b/src/renderer/src/components/Dashboard/DashboardDetails.tsx @@ -284,22 +284,22 @@ export default function DashboardDetails({ {serviceType === 'mariadb' && } - + {serviceId.replace(':', ' ').replace('php', 'PHP').replace('mariadb', 'MariaDB').replace('nginx', 'Nginx').replace('apache', 'Apache')} - + - {isStarting && {t('common.starting')}} - {isStopping && {t('common.stopping')}} - {isRunning && {t('common.running')}} - {!isRunning && !isStarting && !isStopping && {t('common.stopped')}} + {isStarting && {t('common.starting')}} + {isStopping && {t('common.stopping')}} + {isRunning && {t('common.running')}} + {!isRunning && !isStarting && !isStopping && {t('common.stopped')}} - + {isRunning && !isTransitioning && ( - onReloadService(serviceId)} sx={{ bgcolor: 'rgba(57, 167, 255, 0.05)', color: 'primary.main', border: '1px solid rgba(57, 167, 255, 0.1)', width: 48, height: 48, borderRadius: 3 }}> - + onReloadService(serviceId)} sx={{ bgcolor: 'rgba(57, 167, 255, 0.05)', color: 'primary.main', border: '1px solid rgba(57, 167, 255, 0.1)', width: 32, height: 32, borderRadius: 2 }}> + )} @@ -307,9 +307,10 @@ export default function DashboardDetails({ variant="outlined" color={isRunning ? 'error' : 'success'} disabled={isTransitioning} - startIcon={isTransitioning ? : isRunning ? : } + size="small" + startIcon={isTransitioning ? : isRunning ? : } onClick={() => onToggleService(serviceId)} - sx={{ borderRadius: 3, px: 4, height: 48, fontWeight: 800, fontSize: '1rem', boxShadow: 'none' }} + sx={{ borderRadius: 2, px: 2, height: 32, fontWeight: 800, fontSize: '0.75rem', boxShadow: 'none' }} > {isStarting ? t('common.starting') : (isStopping ? t('common.stopping') : (isRunning ? t('common.stop') : t('common.start')))} @@ -318,7 +319,7 @@ export default function DashboardDetails({ setActiveTab(v)} textColor="primary" indicatorColor="primary" sx={{ - '.MuiTab-root': { textTransform: 'none', minHeight: 64, fontWeight: 700, fontSize: '0.95rem' } + '.MuiTab-root': { textTransform: 'none', minHeight: 48, fontWeight: 700, fontSize: '0.8rem' } }}> @@ -331,38 +332,37 @@ export default function DashboardDetails({ - + {activeTab === 'overview' && ( - + - - {t('dashboard.system_stats', 'Sistem İstatistikleri')} + + {t('dashboard.system_stats', 'Sistem İstatistikleri')} - + - - {t('dashboard.service_info', 'Servis Bilgileri')} + + {t('dashboard.service_info', 'Servis Bilgileri')} - - + + - {t('common.port', 'Port')} - - - {getPort()} - - + {t('common.port', 'Port')} + + {getPort()} + - {t('common.status', 'Durum')} + {t('common.status', 'Durum')} @@ -373,21 +373,22 @@ export default function DashboardDetails({ {(activeTab === 'settings_template' || activeTab === 'settings_live') && ( - - + + {activeTab === 'settings_live' ? 'php.ini (Aktif Konfigürasyon)' : getTemplateName()} + {isLoadingConfig ? ( diff --git a/src/renderer/src/components/Dashboard/DashboardManager.tsx b/src/renderer/src/components/Dashboard/DashboardManager.tsx index 4349687..3921e41 100644 --- a/src/renderer/src/components/Dashboard/DashboardManager.tsx +++ b/src/renderer/src/components/Dashboard/DashboardManager.tsx @@ -13,6 +13,7 @@ interface DashboardManagerProps { t: any; onFetchStatus: () => void; onSelectProject: (projectId: string) => void; + runServiceOperation: (title: string, services: string[], operation: 'start' | 'stop') => void; } export default function DashboardManager({ @@ -23,53 +24,20 @@ export default function DashboardManager({ mariaDbVersions, t, onFetchStatus, - onSelectProject + onSelectProject, + runServiceOperation }: DashboardManagerProps) { const [selectedServiceId, setSelectedServiceId] = useState('nginx'); - const [isToggling, setIsToggling] = useState(null); - const [toggledStates, setToggledStates] = useState>({}); const handleToggleService = async (service: string) => { - if (!window.api) return; - if (isToggling) return; - const currentStatus = status[service]?.status || 'stopped'; const isRunning = currentStatus === 'running'; + const title = isRunning ? t('common.stopping') : t('common.starting'); + const operation = isRunning ? 'stop' : 'start'; - setIsToggling(service); - // Optimistically set transition state if not already there - setToggledStates(prev => ({ ...prev, [service]: isRunning ? 'stopping' : 'starting' })); - - try { - const result = isRunning - ? await window.api.stopService(service) - : await window.api.startService(service); - - if (result && !result.success) { - Swal.fire({ - title: t('common.error'), - text: result.message, - icon: 'error', - background: '#1e1e1e', - color: '#fff' - }); - } - } finally { - setIsToggling(null); - setToggledStates(prev => { - const updated = { ...prev }; - delete updated[service]; - return updated; - }); - onFetchStatus(); - } + runServiceOperation(title, [service], operation); }; - // Merge actual status with our local transition states - const mergedStatus = { ...status }; - Object.keys(toggledStates).forEach(service => { - mergedStatus[service] = { ...mergedStatus[service], status: toggledStates[service] }; - }); const handleReloadService = async (service: string) => { if (!window.api) return; @@ -89,7 +57,7 @@ export default function DashboardManager({ return ( = ({ version {t('mariadb_manager.title')} - - - {t('mariadb.version')} + + + {t('mariadb.version')} - {!selectedVersion && ( - {t('mariadb_manager.no_active_version')} + {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')} + {t('mariadb_manager.db_name')} + {t('mariadb_manager.db_size')} + {t('common.actions')} {loading ? ( - + ) : databases.length === 0 ? ( - {t('common.no_data')} + {t('common.no_data')} ) : databases.map(db => ( - {db.name} - {db.size.toFixed(2)} MB - + {db.name} + {db.size.toFixed(2)} MB + - handleDeleteDb(db.name)}> - + handleDeleteDb(db.name)} sx={{ p: 0.5 }}> + @@ -258,50 +259,50 @@ const MariaDbDatabaseManager: React.FC = ({ version - + {/* Users Section */} - - + + {t('mariadb_manager.users_title')} - - +
- {t('mariadb_manager.user_label')} - Host - {t('common.actions')} + {t('mariadb_manager.user_label')} + Host + {t('common.actions')} {loading ? ( - + ) : users.length === 0 ? ( - {t('common.no_data')} + {t('common.no_data')} ) : users.map(user => ( - + - + {user.user} - + - + {user.host} - + - handleDeleteUser(user.user, user.host)}> - + handleDeleteUser(user.user, user.host)} sx={{ p: 0.5 }}> + @@ -311,6 +312,7 @@ const MariaDbDatabaseManager: React.FC = ({ version
+
)}
diff --git a/src/renderer/src/components/PhpExtensionManager.tsx b/src/renderer/src/components/PhpExtensionManager.tsx index f3c1533..7898e8a 100644 --- a/src/renderer/src/components/PhpExtensionManager.tsx +++ b/src/renderer/src/components/PhpExtensionManager.tsx @@ -178,17 +178,17 @@ const PhpExtensionManager: React.FC = ({ open, onClose } }} > - + - {t('php.extensions.title', { version: phpVersion })} - {t('php.extensions.desc')} + {t('php.extensions.title', { version: phpVersion })} + {t('php.extensions.desc')} - - + + - - + + = ({ open, onClose InputProps={{ startAdornment: ( - + ), - sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)' } + sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)', fontSize: '0.75rem', height: 32 } }} /> {loading ? ( - - + + ) : ( - + {filteredExtensions.length === 0 ? ( ) : ( @@ -232,14 +233,15 @@ const PhpExtensionManager: React.FC = ({ open, onClose + ) : ( handleToggle(ext.name, e.target.checked)} color="primary" /> @@ -248,13 +250,13 @@ const PhpExtensionManager: React.FC = ({ open, onClose > + {ext.name} - {ext.enabled && } + {ext.enabled && } } secondary={ext.dll} - secondaryTypographyProps={{ sx: { color: 'text.secondary', fontSize: '0.75rem' } }} + secondaryTypographyProps={{ sx: { color: 'text.secondary', fontSize: '0.65rem', opacity: 0.5 } }} /> )) @@ -262,11 +264,11 @@ const PhpExtensionManager: React.FC = ({ open, onClose )} - - - @@ -282,14 +284,16 @@ const PhpExtensionManager: React.FC = ({ open, onClose bgcolor: '#1a1a1a', color: '#fff', borderRadius: 2, + backgroundImage: 'none', + border: '1px solid rgba(255,255,255,0.1)' } }} > - + - {t('php.extensions.ini_edit', { version: phpVersion })} - setIsRawEditorOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}> - + {t('php.extensions.ini_edit', { version: phpVersion })} + setIsRawEditorOpen(false)} size="small" sx={{ color: 'rgba(255,255,255,0.5)' }}> + = ({ open, onClose color: 'primary.main', wordBreak: 'break-all', cursor: 'pointer', + fontSize: '0.6rem', + opacity: 0.7, '&:hover': { textDecoration: 'underline', color: 'primary.light' } }} onClick={() => window.api.invoke('shell:open-path', configPath)} @@ -306,7 +312,7 @@ const PhpExtensionManager: React.FC = ({ open, onClose {configPath} - + = ({ open, onClose InputProps={{ startAdornment: ( - + ), sx: { color: '#fff', - bgcolor: 'rgba(255,255,255,0.05)', - '& fieldset': { borderColor: 'rgba(255,255,255,0.1)' } + bgcolor: 'rgba(255,255,255,0.03)', + fontSize: '0.75rem', + height: 32, + '& fieldset': { borderColor: 'rgba(255,255,255,0.05)' } } }} /> - + - - +
) diff --git a/src/renderer/src/components/ProjectManager/ProjectDetails.tsx b/src/renderer/src/components/ProjectManager/ProjectDetails.tsx index c58fe41..84f255a 100644 --- a/src/renderer/src/components/ProjectManager/ProjectDetails.tsx +++ b/src/renderer/src/components/ProjectManager/ProjectDetails.tsx @@ -57,6 +57,7 @@ interface ProjectDetailsProps { onOpenVSCode: () => void; onManageDb: () => void; onAddHost: (hostname: string) => void; + onRefreshProjects: () => void; isTransitioning?: boolean; setTransitioning?: (v: boolean) => void; } @@ -77,6 +78,7 @@ export default function ProjectDetails({ onOpenVSCode, onManageDb, onAddHost, + onRefreshProjects, isTransitioning = false, setTransitioning }: ProjectDetailsProps) { @@ -152,6 +154,7 @@ export default function ProjectDetails({ background: '#1e1e1e', color: '#fff' }); + onRefreshProjects(); } } catch (e: any) { Swal.fire({ @@ -187,6 +190,7 @@ export default function ProjectDetails({ background: '#1e1e1e', color: '#fff' }); + if (onRefreshProjects) onRefreshProjects(); } } catch (error: any) { Swal.fire({ @@ -243,9 +247,9 @@ export default function ProjectDetails({ @@ -285,13 +289,13 @@ export default function ProjectDetails({ {!isHostMapped && ( @@ -305,17 +309,17 @@ export default function ProjectDetails({ - + @@ -324,9 +328,9 @@ export default function ProjectDetails({ {/* Tabs */} setProjectDetailTab(v)} textColor="primary" indicatorColor="primary"> - - - + + + @@ -338,8 +342,8 @@ export default function ProjectDetails({ {/* Editable Form Side */} - - {t('projects.edit_details', 'Proje Detaylarını Düzenle')} + + {t('projects.edit_details', 'Proje Detaylarını Düzenle')} @@ -347,60 +351,62 @@ export default function ProjectDetails({ label={t('projects.site_name')} fullWidth variant="filled" + size="small" value={editForm.name} onChange={(e) => setEditForm({ ...editForm, name: e.target.value })} - sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }} + sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' }, '& .MuiInputLabel-root': { fontSize: '0.85rem' }, '& .MuiInputBase-input': { fontSize: '0.85rem' } }} /> setEditForm({ ...editForm, host: e.target.value })} helperText={t('projects.host_helper', 'Domain adı (örn: myproject)')} - sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }} + sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' }, '& .MuiInputLabel-root': { fontSize: '0.85rem' }, '& .MuiInputBase-input': { fontSize: '0.85rem' }, '& .MuiFormHelperText-root': { fontSize: '0.7rem' } }} /> - - {t('projects.web_server')} + + {t('projects.web_server')} - - {t('projects.php_version')} + + {t('projects.php_version')} - - {t('projects.mariadb_version')} + + {t('projects.mariadb_version')} @@ -408,11 +414,11 @@ export default function ProjectDetails({ @@ -424,8 +430,8 @@ export default function ProjectDetails({ {/* Status Monitor Side */} - - {t('dashboard.status')} + + {t('dashboard.status')} @@ -439,10 +445,10 @@ export default function ProjectDetails({ - + System Info - + {t('projects.manual_edit_help', 'Bu sayfadaki değişiklikler kaydedildiğinde servis yapılandırmaları ve hosts dosyası otomatik olarak yenilenecektir.')} @@ -458,22 +464,22 @@ export default function ProjectDetails({ - + - {t('projects.mariadb_version')} - MariaDB {p.mariadbVersion} + {t('projects.mariadb_version')} + MariaDB {p.mariaDbVersion} @@ -487,8 +493,8 @@ export default function ProjectDetails({ { label: 'Password', value: '(no password)', italic: true } ].map((item, idx) => ( - {item.label.toUpperCase()} - + {item.label.toUpperCase()} + {item.value} @@ -501,19 +507,19 @@ export default function ProjectDetails({ {projectDetailTab === 2 && ( - + - - + + {p.serverType} {t('projects.server_settings')} @@ -521,24 +527,24 @@ export default function ProjectDetails({ variant="outlined" size="small" color="primary" - startIcon={isConfigSaving ? : } + startIcon={isConfigSaving ? : } disabled={isConfigSaving || isConfigLoading} onClick={handleSaveConfig} - sx={{ px: 4, borderRadius: 2, fontWeight: 800 }} + sx={{ px: 3, height: 32, borderRadius: 1.5, fontWeight: 800, fontSize: '0.75rem' }} > {t('common.save').toUpperCase()} - + {t('projects.manual_edit_warning')} {isConfigLoading ? ( - + ) : null} void; onAddHost: (hostname: string) => void; onManageDb: () => void; + onRefreshProjects: () => void; } export default function ProjectManager({ @@ -47,7 +48,8 @@ export default function ProjectManager({ onOpenFolder, onOpenVSCode, onAddHost, - onManageDb + onManageDb, + onRefreshProjects }: ProjectManagerProps) { // Defensive check: find selected project @@ -89,6 +91,7 @@ export default function ProjectManager({ onOpenVSCode={() => onOpenVSCode(p.path)} onAddHost={onAddHost} onManageDb={onManageDb} + onRefreshProjects={onRefreshProjects} /> ); diff --git a/src/renderer/src/locales/en.json b/src/renderer/src/locales/en.json index 8f32f04..a8eafcf 100644 --- a/src/renderer/src/locales/en.json +++ b/src/renderer/src/locales/en.json @@ -60,6 +60,8 @@ "quick_look": "Quick Look", "start_all": "Start All", "stop_all": "Stop All", + "starting_all": "Starting All Services", + "stopping_all": "Stopping All Services", "all_started": "All services started.", "all_stopped": "All services stopped.", "overview": "Overview", diff --git a/src/renderer/src/locales/tr.json b/src/renderer/src/locales/tr.json index ae5526a..f28d239 100644 --- a/src/renderer/src/locales/tr.json +++ b/src/renderer/src/locales/tr.json @@ -61,6 +61,8 @@ "quick_look": "Hızlı Bakış", "start_all": "Tümünü Başlat", "stop_all": "Tümünü Durdur", + "starting_all": "Tüm Servisler Başlatılıyor", + "stopping_all": "Tüm Servisler Durduruluyor", "all_started": "Tüm servisler başlatıldı.", "all_stopped": "Tüm servisler durduruldu.", "overview": "Genel Bakış",