diff --git a/config/apache_project.conf.template b/config/apache_project.conf.template
index 85fb296..6244793 100644
--- a/config/apache_project.conf.template
+++ b/config/apache_project.conf.template
@@ -17,7 +17,7 @@ Alias /{{HOST}} "{{PATH}}"
# Directory index handling
DirectoryIndex index.php index.html
- # PHP Handling via mod_actions and CGI
+ # PHP Handling via mod_actions and CGI (EN STABİL YÖNTEM)
SetHandler application/x-httpd-php-alias-{{HOST}}
diff --git a/config/apache_project_alias.conf.template b/config/apache_project_alias.conf.template
index 86182ac..6763523 100644
--- a/config/apache_project_alias.conf.template
+++ b/config/apache_project_alias.conf.template
@@ -15,13 +15,12 @@ Alias /{{HOST}} "{{PATH}}"
# Directory index handling
DirectoryIndex index.php index.html
- # PHP Handling via mod_actions and CGI
+ # PHP Handling via mod_actions and CGI (EN STABİL YÖNTEM)
SetHandler application/x-httpd-php-alias-{{HOST}}
# Action maps the handler to the binary via the ScriptAlias URL
- # We use a unique handler name to avoid conflict with VirtualHost config
Action application/x-httpd-php-alias-{{HOST}} "{{PHP_BIN_URL}}/php-cgi.exe"
diff --git a/service_events.json b/service_events.json
index 8f0988e..029566b 100644
--- a/service_events.json
+++ b/service_events.json
@@ -1,4 +1,479 @@
[
+ {
+ "service": "php:7.4.33",
+ "action": "start",
+ "status": "success",
+ "id": "j8d2w3b0b",
+ "timestamp": 1775190043284
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "7znzrdg8z",
+ "timestamp": 1775190035992
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "stop",
+ "status": "info",
+ "id": "j8a2rse2x",
+ "timestamp": 1775190035979
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "wvqa01nia",
+ "timestamp": 1775189710583
+ },
+ {
+ "service": "nginx",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "cqyq3n6g6",
+ "timestamp": 1775189708710
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "8w0xss4vz",
+ "timestamp": 1775189708296
+ },
+ {
+ "service": "apache",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "hje4qf5if",
+ "timestamp": 1775189706744
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "aa18fbyov",
+ "timestamp": 1775189669988
+ },
+ {
+ "service": "apache",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "836elga8m",
+ "timestamp": 1775189668534
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "vcchnrqv4",
+ "timestamp": 1775189668094
+ },
+ {
+ "service": "nginx",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "86dmwkrb6",
+ "timestamp": 1775189666371
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "h9smf28xf",
+ "timestamp": 1775189575757
+ },
+ {
+ "service": "nginx",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "9f9tdutcl",
+ "timestamp": 1775189573903
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "n5cifasnf",
+ "timestamp": 1775189573509
+ },
+ {
+ "service": "apache",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "o0sv49py5",
+ "timestamp": 1775189571988
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "start",
+ "status": "success",
+ "id": "g2dcxneob",
+ "timestamp": 1775189512213
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "s4w0hso14",
+ "timestamp": 1775189511574
+ },
+ {
+ "service": "apache",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "60srbq5kj",
+ "timestamp": 1775189511560
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "9djtroc4u",
+ "timestamp": 1775189505056
+ },
+ {
+ "service": "apache",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "guzt36y9u",
+ "timestamp": 1775189503604
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "zbc1wz7ab",
+ "timestamp": 1775189503250
+ },
+ {
+ "service": "nginx",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: 1",
+ "id": "opsbvh3zz",
+ "timestamp": 1775189501545
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "jwinj3nx9",
+ "timestamp": 1775189460996
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "stop",
+ "status": "info",
+ "id": "4apqt056j",
+ "timestamp": 1775189460983
+ },
+ {
+ "service": "php:5.6.40",
+ "action": "start",
+ "status": "success",
+ "id": "gajw98k0v",
+ "timestamp": 1775189436336
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "start",
+ "status": "success",
+ "id": "5mm3ugn4m",
+ "timestamp": 1775189436319
+ },
+ {
+ "service": "php:8.2.30",
+ "action": "start",
+ "status": "success",
+ "id": "8aexj2zsk",
+ "timestamp": 1775189436300
+ },
+ {
+ "service": "php:8.5.4",
+ "action": "start",
+ "status": "success",
+ "id": "gsbcbtiip",
+ "timestamp": 1775189436282
+ },
+ {
+ "service": "mariadb:11.4.2",
+ "action": "start",
+ "status": "success",
+ "id": "wtiut8ui0",
+ "timestamp": 1775189436067
+ },
+ {
+ "service": "mariadb:10.11.8",
+ "action": "start",
+ "status": "success",
+ "id": "gilbat8fo",
+ "timestamp": 1775189436048
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "5e3zmv9cj",
+ "timestamp": 1775189435299
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "imro81imn",
+ "timestamp": 1775189435236
+ },
+ {
+ "service": "php:5.6.40",
+ "action": "start",
+ "status": "success",
+ "id": "13wv7rbx0",
+ "timestamp": 1775189327354
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "start",
+ "status": "success",
+ "id": "lh2mwoaqt",
+ "timestamp": 1775189327336
+ },
+ {
+ "service": "php:8.2.30",
+ "action": "start",
+ "status": "success",
+ "id": "r6yxuczum",
+ "timestamp": 1775189327316
+ },
+ {
+ "service": "php:8.5.4",
+ "action": "start",
+ "status": "success",
+ "id": "7kteqeipa",
+ "timestamp": 1775189327297
+ },
+ {
+ "service": "mariadb:11.4.2",
+ "action": "start",
+ "status": "success",
+ "id": "ow4rm4q2g",
+ "timestamp": 1775189327084
+ },
+ {
+ "service": "mariadb:10.11.8",
+ "action": "start",
+ "status": "success",
+ "id": "860heaux5",
+ "timestamp": 1775189327064
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "3spv9kr33",
+ "timestamp": 1775189326302
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "0ofwueil1",
+ "timestamp": 1775189326240
+ },
+ {
+ "service": "nginx",
+ "action": "start",
+ "status": "success",
+ "id": "poysy0w5h",
+ "timestamp": 1775189293667
+ },
+ {
+ "service": "apache",
+ "action": "start",
+ "status": "success",
+ "id": "pw764jton",
+ "timestamp": 1775189291498
+ },
+ {
+ "service": "php:5.6.40",
+ "action": "start",
+ "status": "success",
+ "id": "gqufxbkr6",
+ "timestamp": 1775189263980
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "start",
+ "status": "success",
+ "id": "ahje87h73",
+ "timestamp": 1775189263963
+ },
+ {
+ "service": "php:8.2.30",
+ "action": "start",
+ "status": "success",
+ "id": "lgx6crt2r",
+ "timestamp": 1775189263945
+ },
+ {
+ "service": "php:8.5.4",
+ "action": "start",
+ "status": "success",
+ "id": "1f4yp29hz",
+ "timestamp": 1775189263928
+ },
+ {
+ "service": "mariadb:11.4.2",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "c7t0f38mx",
+ "timestamp": 1775189261047
+ },
+ {
+ "service": "mariadb:10.11.8",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "wjumgnczg",
+ "timestamp": 1775189261040
+ },
+ {
+ "service": "php:5.6.40",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "13pa3vc9u",
+ "timestamp": 1775189261030
+ },
+ {
+ "service": "php:8.2.30",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "aq8u5y88i",
+ "timestamp": 1775189261021
+ },
+ {
+ "service": "php:8.5.4",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "31mnxniia",
+ "timestamp": 1775189261013
+ },
+ {
+ "service": "apache",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "qimhn6z16",
+ "timestamp": 1775189260993
+ },
+ {
+ "service": "nginx",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "s1qf1o0x9",
+ "timestamp": 1775189260990
+ },
+ {
+ "service": "mariadb:11.4.2",
+ "action": "stop",
+ "status": "info",
+ "id": "ay9u34n91",
+ "timestamp": 1775189260985
+ },
+ {
+ "service": "mariadb:10.11.8",
+ "action": "stop",
+ "status": "info",
+ "id": "ywurfx52h",
+ "timestamp": 1775189260978
+ },
+ {
+ "service": "php:5.6.40",
+ "action": "stop",
+ "status": "info",
+ "id": "dkyu7sbnb",
+ "timestamp": 1775189260970
+ },
+ {
+ "service": "php:8.2.30",
+ "action": "stop",
+ "status": "info",
+ "id": "u8sax0fs7",
+ "timestamp": 1775189260963
+ },
+ {
+ "service": "php:8.5.4",
+ "action": "stop",
+ "status": "info",
+ "id": "trebtxdcb",
+ "timestamp": 1775189260954
+ },
+ {
+ "service": "apache",
+ "action": "stop",
+ "status": "info",
+ "id": "1yc7n0aro",
+ "timestamp": 1775189260943
+ },
+ {
+ "service": "nginx",
+ "action": "stop",
+ "status": "info",
+ "id": "fnu72gjoj",
+ "timestamp": 1775189260941
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "hpzynrq5o",
+ "timestamp": 1775189223398
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "stop",
+ "status": "info",
+ "id": "dzl7dzb31",
+ "timestamp": 1775189223385
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "start",
+ "status": "success",
+ "id": "gwwjvpm9p",
+ "timestamp": 1775189213352
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "exit",
+ "status": "failure",
+ "details": "Exit code: null",
+ "id": "h0ckcojy3",
+ "timestamp": 1775189204176
+ },
+ {
+ "service": "php:7.4.33",
+ "action": "stop",
+ "status": "info",
+ "id": "54utk28a1",
+ "timestamp": 1775189204163
+ },
{
"service": "php:5.6.40",
"action": "start",
diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts
index 6572bd1..9022a06 100644
--- a/src/main/ipc/index.ts
+++ b/src/main/ipc/index.ts
@@ -706,6 +706,15 @@ export function registerIpcHandlers(): void {
}
})
+ ipcMain.handle('services:stop-all', async () => {
+ try {
+ await processManager.aggressiveKillAll()
+ return { success: true, message: 'Tüm servisler zorla durduruldu.' }
+ } catch (error: any) {
+ return { success: false, message: `Durdurma hatası: ${error.message}` }
+ }
+ })
+
ipcMain.handle('services:status', async () => {
return processManager.getStatuses()
})
diff --git a/src/main/services/ProcessManager.ts b/src/main/services/ProcessManager.ts
index 978289a..04fa3bd 100644
--- a/src/main/services/ProcessManager.ts
+++ b/src/main/services/ProcessManager.ts
@@ -235,6 +235,13 @@ export class ProcessManager {
// Also force kill lingering processes even if not in map
const result = await this.forceKillAll(name)
+
+ // Final cleanup: Kill anything on the port
+ const port = this.getServicePort(name)
+ if (port) {
+ await this.killByPort(name, port)
+ }
+
this.states.set(name, 'stopped')
return result
}
@@ -271,15 +278,33 @@ export class ProcessManager {
}
} else {
if (isWindows) {
- const executableName = name === 'nginx' ? 'nginx.exe' : name === 'apache' ? 'httpd.exe' : name === 'php' ? 'php-cgi.exe' : 'mariadbd.exe'
- spawn('taskkill', ['/F', '/IM', executableName, '/T'], { windowsHide: true })
- .on('close', () => resolve(true))
- .on('error', () => resolve(true))
+ let executableName = ''
+ if (name === 'nginx') executableName = 'nginx.exe'
+ else if (name === 'apache') executableName = 'httpd.exe'
+ else if (name.startsWith('php')) executableName = 'php-cgi.exe'
+ else if (name.startsWith('mariadb') || name.startsWith('mysql')) executableName = 'mariadbd.exe'
+
+ if (executableName) {
+ spawn('taskkill', ['/F', '/IM', executableName, '/T'], { windowsHide: true })
+ .on('close', () => resolve(true))
+ .on('error', () => resolve(true))
+ } else {
+ resolve(true)
+ }
} else {
- const processName = name === 'nginx' ? 'nginx' : name === 'apache' ? 'httpd' : name === 'php' ? 'php-cgi' : 'mariadbd'
- spawn('pkill', ['-f', processName], { windowsHide: true })
- .on('close', () => resolve(true))
- .on('error', () => resolve(true))
+ let processName = ''
+ if (name === 'nginx') processName = 'nginx'
+ else if (name === 'apache') processName = 'httpd'
+ else if (name.startsWith('php')) processName = 'php-cgi'
+ else if (name.startsWith('mariadb') || name.startsWith('mysql')) processName = 'mariadbd'
+
+ if (processName) {
+ spawn('pkill', ['-f', processName], { windowsHide: true })
+ .on('close', () => resolve(true))
+ .on('error', () => resolve(true))
+ } else {
+ resolve(true)
+ }
}
}
} else {
@@ -470,10 +495,53 @@ export class ProcessManager {
return Array.from(new Set(foundLogs)).slice(-count)
}
- stopAll(): void {
- for (const name of this.processes.keys()) {
- this.stopService(name)
+ async aggressiveKillAll(): Promise {
+ const isWindows = process.platform === 'win32';
+ const executables = ['nginx.exe', 'httpd.exe', 'php-cgi.exe', 'mariadbd.exe', 'mysqld.exe'];
+ const procNames = ['nginx', 'httpd', 'php-cgi', 'mariadbd', 'mysqld'];
+
+ this.addLog('system', 'Aggressive stop initiated. Killing all service processes...')
+
+ if (isWindows) {
+ for (const exe of executables) {
+ spawn('taskkill', ['/F', '/IM', exe, '/T'], { windowsHide: true })
+ }
+ } else {
+ for (const proc of procNames) {
+ spawn('pkill', ['-9', '-f', proc], { windowsHide: true })
+ }
}
+
+ // Kill all configured ports
+ const settings = configService.getSettings()
+ const portsToKill = new Set()
+
+ if (settings.nginxPort) portsToKill.add(parseInt(settings.nginxPort))
+ if (settings.apachePort) portsToKill.add(parseInt(settings.apachePort))
+ if (settings.phpPort) portsToKill.add(parseInt(settings.phpPort))
+ if (settings.mariaDbPort) portsToKill.add(parseInt(settings.mariaDbPort))
+
+ // Include versioned ports if any
+ if (settings.mariaDbPorts) {
+ Object.values(settings.mariaDbPorts).forEach((p: any) => portsToKill.add(parseInt(p)))
+ }
+
+ for (const port of portsToKill) {
+ if (!isNaN(port)) {
+ await this.killByPort('system', port)
+ }
+ }
+
+ this.processes.clear()
+ for (const key of this.states.keys()) {
+ this.states.set(key, 'stopped')
+ }
+
+ this.addLog('system', 'All services aggressively stopped.')
+ }
+
+ stopAll(): void {
+ this.aggressiveKillAll().catch(e => console.error('Failed to stop all services:', e))
}
}
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index b26bafd..a8103a8 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -488,19 +488,22 @@ function App(): JSX.Element {
const handleStopAll = async () => {
if (!window.api) return
- const criticalServices = ['nginx', 'apache']
- const phpToStop = Object.keys(status).filter(s => s.startsWith('php:') && (status[s]?.status === 'running' || status[s]?.status === 'starting'))
- const mariaDbToStop = Object.keys(status).filter(s => (s.startsWith('mariadb:') || s.startsWith('mysql:')) && (status[s]?.status === 'running' || status[s]?.status === 'starting'))
-
- await Promise.all([
- ...criticalServices.filter(s => status[s]?.status === 'running' || status[s]?.status === 'starting').map(s => window.api?.stopService(s)),
- ...phpToStop.map(s => window.api?.stopService(s)),
- ...mariaDbToStop.map(s => window.api?.stopService(s)),
- window.api?.stopService('mariadb') // Also stop default
- ])
+
+ // 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 result = await window.api.invoke('services:stop-all')
+
fetchStatus()
- setNotification({ open: true, message: t('dashboard.all_stopped'), severity: 'success' })
+ setNotification({
+ open: true,
+ message: result.success ? t('dashboard.all_stopped') : result.message,
+ severity: result.success ? 'success' : 'error'
+ })
}
const formatMemory = (bytes?: number) => {
@@ -1587,6 +1590,8 @@ function App(): JSX.Element {
projects={projects}
status={status}
settings={settings}
+ phpVersions={phpVersions}
+ mariaDbVersions={mariaDbVersions}
projectSearch={projectSearch}
setProjectSearch={setProjectSearch}
selectedProjectId={selectedProjectId}
diff --git a/src/renderer/src/components/Dashboard/DashboardDetails.tsx b/src/renderer/src/components/Dashboard/DashboardDetails.tsx
index bfea0d8..e906612 100644
--- a/src/renderer/src/components/Dashboard/DashboardDetails.tsx
+++ b/src/renderer/src/components/Dashboard/DashboardDetails.tsx
@@ -14,7 +14,10 @@ import {
Divider,
LinearProgress,
CircularProgress,
- Stack
+ Stack,
+ Tooltip,
+ Chip,
+ Avatar
} from '@mui/material'
import {
Refresh as RefreshIcon,
@@ -26,7 +29,10 @@ import {
Language as WebIcon,
Dns as ServerIcon,
Code as PhpIcon,
- Storage as DbIcon
+ Storage as DbIcon,
+ Settings as SettingsIcon,
+ Assignment as LogIcon,
+ Analytics as StatusIcon
} from '@mui/icons-material'
import { useState, useEffect, useRef } from 'react'
import 'prismjs/themes/prism-tomorrow.css'
@@ -58,9 +64,10 @@ export default function DashboardDetails({
onReloadService,
onSelectProject
}: DashboardDetailsProps) {
- const [activeTab, setActiveTab] = useState(0);
+ const [activeTab, setActiveTab] = useState('overview');
const [configContent, setConfigContent] = useState('');
const [projectConfigContent, setProjectConfigContent] = useState('');
+ const [liveConfigContent, setLiveConfigContent] = useState('');
const [isLoadingConfig, setIsLoadingConfig] = useState(false);
const [logs, setLogs] = useState([]);
const logEndRef = useRef(null);
@@ -100,17 +107,31 @@ export default function DashboardDetails({
if (res && res.success) {
setter(res.content || '');
} else {
- console.error('Failed to fetch config:', res?.message);
setter('');
}
} catch (error) {
- console.error('Error fetching config:', error);
setter('');
} finally {
setIsLoadingConfig(false);
}
};
+ const fetchLiveConfig = async (sid: string) => {
+ setIsLoadingConfig(true);
+ try {
+ const res = await window.api.invoke('config:service-file:get', sid);
+ if (res && res.success) {
+ setLiveConfigContent(res.content || '');
+ } else {
+ setLiveConfigContent('');
+ }
+ } catch (error) {
+ setLiveConfigContent('');
+ } finally {
+ setIsLoadingConfig(false);
+ }
+ };
+
const saveConfig = async (templateName: string, content: string) => {
const res = await window.api.invoke('config:template:save', { templateName, content });
if (res.success) {
@@ -118,14 +139,27 @@ export default function DashboardDetails({
title: t('common.success'),
text: res.message,
icon: 'success',
+ toast: true,
+ position: 'top-end',
+ showConfirmButton: false,
+ timer: 3000,
background: '#1e1e1e',
color: '#fff'
});
- } else {
+ }
+ };
+
+ const saveLiveConfig = async (sid: string, content: string) => {
+ const res = await window.api.invoke('config:service-file:save', { serviceId: sid, content });
+ if (res.success) {
Swal.fire({
- title: t('common.error'),
+ title: t('common.success'),
text: res.message,
- icon: 'error',
+ icon: 'success',
+ toast: true,
+ position: 'top-end',
+ showConfirmButton: false,
+ timer: 3000,
background: '#1e1e1e',
color: '#fff'
});
@@ -138,38 +172,41 @@ export default function DashboardDetails({
setLogs(res || []);
};
- const getHighlightLanguage = (templateName: string) => {
- if (!templateName) return Prism.languages.clike;
- if (templateName.includes('nginx')) return Prism.languages.nginx;
- if (templateName.includes('httpd') || templateName.includes('apache')) return Prism.languages.apacheconf;
- if (templateName.includes('.ini') || templateName.includes('.conf')) return Prism.languages.ini;
+ const getHighlightLanguage = (name: string) => {
+ if (!name) return Prism.languages.clike;
+ if (name.includes('nginx')) return Prism.languages.nginx;
+ if (name.includes('httpd') || name.includes('apache')) return Prism.languages.apacheconf;
+ if (name.includes('.ini') || name.includes('.conf')) return Prism.languages.ini;
return Prism.languages.clike;
};
- const getHighlightLabel = (templateName: string) => {
- if (!templateName) return 'clike';
- if (templateName.includes('nginx')) return 'nginx';
- if (templateName.includes('httpd') || templateName.includes('apache')) return 'apacheconf';
- if (templateName.includes('.ini') || templateName.includes('.conf')) return 'ini';
+ const getHighlightLabel = (name: string) => {
+ if (!name) return 'clike';
+ if (name.includes('nginx')) return 'nginx';
+ if (name.includes('httpd') || name.includes('apache')) return 'apacheconf';
+ if (name.includes('.ini') || name.includes('.conf')) return 'ini';
return 'clike';
};
useEffect(() => {
- if (activeTab === 1) fetchConfig(getTemplateName(), setConfigContent);
- if (activeTab === 2) {
+ if (activeTab === 'settings_template') fetchConfig(getTemplateName(), setConfigContent);
+ if (activeTab === 'projects_template') {
const templates = getProjectTemplateNames();
if (templates.length > 0) fetchConfig(templates[0], setProjectConfigContent);
}
- if (activeTab === 3) {
+ if (activeTab === 'logs') {
fetchLogs();
const interval = setInterval(fetchLogs, 3000);
return () => clearInterval(interval);
}
+ if (activeTab === 'settings_live' && serviceId) {
+ fetchLiveConfig(serviceId);
+ }
return undefined;
}, [activeTab, serviceId]);
useEffect(() => {
- if (activeTab === 3 && logEndRef.current) {
+ if (activeTab === 'logs' && logEndRef.current) {
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [logs, activeTab]);
@@ -186,7 +223,6 @@ export default function DashboardDetails({
const getPort = () => {
if (currentStatus.port) return currentStatus.port;
- // Fallback for stopped services or missing data
if (serviceId === 'nginx') return settings.nginxPort;
if (serviceId === 'apache') return settings.apachePort;
if (serviceType === 'php') {
@@ -210,109 +246,119 @@ export default function DashboardDetails({
if (serviceId === 'nginx') return !p.serverType || p.serverType === 'nginx';
if (serviceId === 'apache') return p.serverType === 'apache';
if (serviceType === 'php') return p.phpVersion === serviceId.split(':')[1];
- if (serviceType === 'mariadb') return true; // MariaDB is shared (or filter if multi-instance)
+ if (serviceType === 'mariadb') return true;
return false;
});
const ResourceMonitor = ({ cpu, memory }: { cpu?: number; memory?: number }) => (
-
+
- CPU
- {cpu || 0}%
+ CPU USAGE
+ {cpu || 0}%
-
+
- RAM
-
+ RAM USAGE
+
{memory ? `${Math.round(memory / (1024 * 1024))} MB` : '0 MB'}
-
+
);
+ const isPhp = serviceType === 'php';
+
return (
-
-
-
- {serviceType === 'nginx' && }
- {serviceType === 'apache' && }
- {serviceType === 'php' && }
- {serviceType === 'mariadb' && }
+
+
+
+ {serviceType === 'nginx' && }
+ {serviceType === 'apache' && }
+ {serviceType === 'php' && }
+ {serviceType === 'mariadb' && }
- {serviceId.toUpperCase()}
-
- {isStarting && {t('common.starting', 'Başlatılıyor...')}}
- {isStopping && {t('common.stopping', 'Durduruluyor...')}}
- {isRunning && {t('common.running', 'Çalışıyor')}}
- {!isRunning && !isStarting && !isStopping && {t('common.stopped', 'Durduruldu')}}
+ {serviceId.toUpperCase()}
+
+
+ {isStarting && {t('common.starting').toUpperCase()}}
+ {isStopping && {t('common.stopping').toUpperCase()}}
+ {isRunning && {t('common.running').toUpperCase()}}
+ {!isRunning && !isStarting && !isStopping && {t('common.stopped').toUpperCase()}}
-
+
{isRunning && !isTransitioning && (
- onReloadService(serviceId)} sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main' }}>
-
-
+
+ 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 }}>
+
+
+
)}
- : isRunning ? :
- }
+ startIcon={isTransitioning ? : isRunning ? : }
onClick={() => onToggleService(serviceId)}
- sx={{ borderRadius: 2, minWidth: 100 }}
+ sx={{ borderRadius: 3, px: 4, height: 48, fontWeight: 800, fontSize: '1rem', boxShadow: isRunning ? '0 4px 12px rgba(244, 67, 54, 0.2)' : '0 4px 12px rgba(76, 175, 80, 0.2)' }}
>
- {isStarting ? t('common.starting', '...') : (isStopping ? t('common.stopping', '...') : (isRunning ? t('common.stop', 'Durdur') : t('common.start', 'Başlat')))}
+ {isStarting ? t('common.starting') : (isStopping ? t('common.stopping') : (isRunning ? t('common.stop') : t('common.start')))}
-
- setActiveTab(v)}>
-
-
- {(serviceId === 'nginx' || serviceId === 'apache' || serviceType === 'php') && (
-
+
+ setActiveTab(v)} textColor="primary" indicatorColor="primary">
+
+
+ {isPhp && }
+ {(serviceId === 'nginx' || serviceId === 'apache' || isPhp) && (
+
)}
-
-
+
+
-
- {activeTab === 0 && (
+
+ {activeTab === 'overview' && (
- {t('dashboard.system_stats')}
-
+
+ {t('dashboard.system_stats')}
+
+
- {t('dashboard.service_info')}
-
-
-
- {t('common.port')}
-
- {getPort()}
-
+
+ {t('dashboard.service_info')}
+
+
+
+
+ {t('common.port').toUpperCase()}
+
+
+ {getPort()}
+
+
-
-
- {t('common.status')}
-
- {(isRunning ? t('common.running') : t('common.stopped')).toUpperCase()}
-
+
+
+ {t('common.status').toUpperCase()}
+
@@ -320,46 +366,34 @@ export default function DashboardDetails({
)}
- {activeTab === 1 && (
+ {(activeTab === 'settings_template' || activeTab === 'settings_live') && (
-
-
- {getTemplateName().toUpperCase()}
+
+
+ {activeTab === 'settings_live' ? 'php.ini (ACTIVE)' : getTemplateName().toUpperCase()}
}
- onClick={() => saveConfig(getTemplateName(), configContent)}
- sx={{ borderRadius: 2 }}
+ size="large"
+ startIcon={isLoadingConfig ? : }
+ disabled={isLoadingConfig}
+ onClick={() => activeTab === 'settings_live' ? saveLiveConfig(serviceId!, liveConfigContent) : saveConfig(getTemplateName(), configContent)}
+ sx={{ borderRadius: 2.5, px: 4, fontWeight: 800 }}
>
- {t('common.save', 'KAYDET')}
+ {t('common.save').toUpperCase()}
-
+
{isLoadingConfig ? (
) : (
Prism.highlight(code, getHighlightLanguage(getTemplateName()), getHighlightLabel(getTemplateName()))}
- padding={20}
- style={{
- fontFamily: '"Fira Code", "Consolas", monospace',
- fontSize: '14px',
- minHeight: '100%',
- outline: 'none'
- }}
+ value={activeTab === 'settings_live' ? liveConfigContent : configContent}
+ onValueChange={activeTab === 'settings_live' ? setLiveConfigContent : setConfigContent}
+ highlight={code => Prism.highlight(code, getHighlightLanguage(activeTab === 'settings_live' ? 'php.ini' : getTemplateName()), getHighlightLabel(activeTab === 'settings_live' ? 'php.ini' : getTemplateName()))}
+ padding={25}
+ style={{ fontFamily: '"Fira Code", monospace', fontSize: '15px', minHeight: '100%', outline: 'none' }}
textareaClassName="editor-textarea"
/>
@@ -368,31 +402,23 @@ export default function DashboardDetails({
)}
- {activeTab === 2 && (
+ {activeTab === 'projects_template' && (
-
-
+
+
{getProjectTemplateNames()[0]?.toUpperCase()}
}
onClick={() => saveConfig(getProjectTemplateNames()[0], projectConfigContent)}
- sx={{ borderRadius: 2 }}
+ sx={{ borderRadius: 2.5, px: 4, fontWeight: 800 }}
>
- {t('common.save', 'KAYDET')}
+ {t('common.save').toUpperCase()}
-
+
{isLoadingConfig ? (
) : (
@@ -401,13 +427,8 @@ export default function DashboardDetails({
value={projectConfigContent}
onValueChange={setProjectConfigContent}
highlight={code => Prism.highlight(code, getHighlightLanguage(getProjectTemplateNames()[0]), getHighlightLabel(getProjectTemplateNames()[0]))}
- padding={20}
- style={{
- fontFamily: '"Fira Code", "Consolas", monospace',
- fontSize: '14px',
- minHeight: '100%',
- outline: 'none'
- }}
+ padding={25}
+ style={{ fontFamily: '"Fira Code", monospace', fontSize: '15px', minHeight: '100%', outline: 'none' }}
textareaClassName="editor-textarea"
/>
@@ -416,44 +437,67 @@ export default function DashboardDetails({
)}
- {activeTab === 3 && (
-
- {logs.map((log, i) => (
-
- {log}
-
- ))}
+ {activeTab === 'logs' && (
+
+
+
+ REALTIME SERVICE LOGS
+
+ {logs.length === 0 ? (
+
+ {t('diagnostics.noLogs')}
+
+ ) : (
+ logs.map((log, i) => (
+
+ {log}
+
+ ))
+ )}
)}
- {activeTab === 4 && (
-
+ {activeTab === 'projects' && (
+
{serviceProjects.length === 0 ? (
- {t('projects.empty')}
+
+
+ {t('projects.empty')}
+
) : (
- serviceProjects.map((p, idx) => (
-
-
-
+ serviceProjects.map((p_item, idx) => (
+
+
+
+
+
- onSelectProject(p.id)} color="primary">
-
-
+
- {idx < serviceProjects.length - 1 && }
+ {idx < serviceProjects.length - 1 && }
))
)}
diff --git a/src/renderer/src/components/ProjectManager/ProjectDetails.tsx b/src/renderer/src/components/ProjectManager/ProjectDetails.tsx
index 314ef40..6d744c2 100644
--- a/src/renderer/src/components/ProjectManager/ProjectDetails.tsx
+++ b/src/renderer/src/components/ProjectManager/ProjectDetails.tsx
@@ -7,12 +7,17 @@ import {
Tabs,
Tab,
Grid,
- Chip,
Divider,
Stack,
Tooltip,
CircularProgress,
- Alert
+ Alert,
+ TextField,
+ MenuItem,
+ Select,
+ FormControl,
+ InputLabel,
+ Paper
} from '@mui/material'
import { useState, useEffect } from 'react'
import {
@@ -21,12 +26,15 @@ import {
Stop as StopIcon,
PlayArrow as StartIcon,
Launch as LaunchIcon,
- Edit as EditIcon,
Delete as DeleteIcon,
Tune as TuningIcon,
OpenInNew as OpenIcon,
Code as CodeIcon,
- Save as SaveIcon
+ Save as SaveIcon,
+ Settings as SettingsIcon,
+ Dns as ServerIcon,
+ Analytics as StatusIcon,
+ Storage as DbIcon
} from '@mui/icons-material'
import ProjectResourceMonitor from './ProjectResourceMonitor'
import ProjectConfigEditor from './ProjectConfigEditor'
@@ -36,6 +44,8 @@ interface ProjectDetailsProps {
project: any;
status: any;
settings: any;
+ phpVersions: any[];
+ mariaDbVersions: any[];
projectDetailTab: number;
setProjectDetailTab: (v: number) => void;
t: any;
@@ -55,10 +65,11 @@ export default function ProjectDetails({
project: p,
status,
settings,
+ phpVersions,
+ mariaDbVersions,
projectDetailTab,
setProjectDetailTab,
t,
- onEdit,
onDelete,
onToggleStatus,
onOpenSite,
@@ -73,6 +84,28 @@ export default function ProjectDetails({
const [configText, setConfigText] = useState('');
const [isConfigLoading, setIsConfigLoading] = useState(false);
const [isConfigSaving, setIsConfigSaving] = useState(false);
+ const [isSaving, setIsSaving] = useState(false);
+
+ // Editing State
+ const [editForm, setEditForm] = useState({
+ name: '',
+ host: '',
+ serverType: '',
+ phpVersion: '',
+ mariaDbVersion: ''
+ });
+
+ useEffect(() => {
+ if (p) {
+ setEditForm({
+ name: p.name || '',
+ host: p.host || '',
+ serverType: p.serverType || 'nginx',
+ phpVersion: p.phpVersion || '',
+ mariaDbVersion: p.mariaDbVersion || ''
+ });
+ }
+ }, [p]);
useEffect(() => {
if (p?.host) {
@@ -133,6 +166,41 @@ export default function ProjectDetails({
}
};
+ const handleProjectUpdate = async () => {
+ if (!p) return;
+ setIsSaving(true);
+ try {
+ const result = await window.api.updateProject({
+ ...p,
+ ...editForm
+ });
+
+ if (result && result.success) {
+ Swal.fire({
+ icon: 'success',
+ title: t('common.success'),
+ text: t('projects.updated'),
+ toast: true,
+ position: 'top-end',
+ showConfirmButton: false,
+ timer: 3000,
+ background: '#1e1e1e',
+ color: '#fff'
+ });
+ }
+ } catch (error: any) {
+ Swal.fire({
+ icon: 'error',
+ title: t('common.error'),
+ text: error.message,
+ background: '#1e1e1e',
+ color: '#fff'
+ });
+ } finally {
+ setIsSaving(false);
+ }
+ };
+
if (!p) {
return (
@@ -147,166 +215,238 @@ export default function ProjectDetails({
status[`php:${p.phpVersion}`]?.status === 'running'
);
+ const installedPhp = phpVersions.filter(v => v.status === 'installed');
+ const installedMariaDb = mariaDbVersions.filter(v => v.status === 'installed');
+
return (
-
-
-
-
+
+ {/* Header */}
+
+
+
-
- {p.name}
+
+ {p.name}
-
+
{p.path}
-
-
-
+
+
+
+
+
: (isRunning ? : )}
- color={isRunning ? 'error' : 'primary'}
+ startIcon={isTransitioning ? : (isRunning ? : )}
+ color={isRunning ? 'error' : 'success'}
onClick={async () => {
if (setTransitioning) setTransitioning(true);
try {
await onToggleStatus();
} finally {
if (setTransitioning) {
- // Short delay to ensure status update is reflected
setTimeout(() => setTransitioning(false), 500);
}
}
}}
- sx={{
- px: 2,
- height: 36,
- fontWeight: 'bold',
- transition: 'all 0.1s ease-in-out',
- '&:active': { transform: 'scale(0.96)' }
- }}
+ sx={{ px: 3, height: 44, borderRadius: 2.5, fontWeight: 800, boxShadow: isRunning ? '0 4px 12px rgba(244, 67, 54, 0.2)' : '0 4px 12px rgba(76, 175, 80, 0.2)' }}
>
- {isTransitioning ? t('common.processing') : (isRunning ? t('projects.stop_site') : t('projects.start_site'))}
+ {isTransitioning ? t('common.processing') : (isRunning ? t('projects.stop_site').toUpperCase() : t('projects.start_site').toUpperCase())}
+
}
onClick={() => onOpenSite(isHostMapped)}
- sx={{
- px: 2,
- height: 36,
- transition: 'all 0.1s ease-in-out',
- '&:active': { transform: 'scale(0.96)' }
- }}
+ sx={{ px: 3, height: 44, borderRadius: 2.5, fontWeight: 700, borderColor: 'rgba(255,255,255,0.1)', '&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(57, 167, 255, 0.05)' } }}
>
- {t('projects.open_site')}
+ {t('projects.open_site').toUpperCase()}
+
}
onClick={onOpenVSCode}
- sx={{
- px: 2,
- height: 36,
- borderColor: 'rgba(255,255,255,0.1)',
- color: 'rgba(255,255,255,0.7)',
- transition: 'all 0.1s ease-in-out',
- '&:active': { transform: 'scale(0.96)' }
- }}
+ sx={{ px: 3, height: 44, borderRadius: 2.5, borderColor: 'rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.6)', fontWeight: 700, '&:hover': { borderColor: '#007ACC', color: '#007ACC', bgcolor: 'rgba(0, 122, 204, 0.05)' } }}
>
- VS Code Aç
+ VS CODE AÇ
+
{!isHostMapped && (
)}
-
-
-
-
-
-
-
-
-
-
+
+ {/* Fixed Delete Button at top right */}
+
+
+
+
+
+
+
-
+ {/* Tabs */}
+
setProjectDetailTab(v)} textColor="primary" indicatorColor="primary">
-
-
-
+
+
+
+ {/* Content Area */}
{projectDetailTab === 0 && (
-
-
-
- {t('projects.site_domain')}
-
- {p.host}.local
-
-
-
+
+ {/* Editable Form Side */}
+
+
+
+ {t('projects.edit_details', 'Proje Detaylarını Düzenle')}
+
+
+
+ setEditForm({ ...editForm, name: e.target.value })}
+ sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
+ />
+
+ 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)' } }}
+ />
-
- {t('projects.web_server')}
-
- {p.serverType.toUpperCase()}
-
-
-
+
+
+
+ {t('projects.web_server')}
+
+
+
+
+
+ {t('projects.php_version')}
+
+
+
+
-
- {t('projects.php_version')}
-
- {p.phpVersion}
-
-
-
+
+ {t('projects.mariadb_version')}
+
+
+
+
+ : }
+ onClick={handleProjectUpdate}
+ disabled={isSaving}
+ sx={{ px: 6, py: 1.5, borderRadius: 3, fontWeight: 800, fontSize: '1rem', background: 'linear-gradient(45deg, #2196F3 30%, #21CBF3 90%)', boxShadow: '0 8px 20px rgba(33, 150, 243, 0.3)' }}
+ >
+ {t('common.save').toUpperCase()}
+
+
+
+
-
-
-
- {t('dashboard.status')}
+ {/* Status Monitor Side */}
+
+
+
+ {t('dashboard.status')}
-
-
+
+
+
+
+
+
+
+
+
+ System Info
+
+
+ {t('projects.manual_edit_help', 'Bu sayfadaki değişiklikler kaydedildiğinde servis yapılandırmaları ve hosts dosyası otomatik olarak yenilenecektir.')}
+
+
+
@@ -314,63 +454,66 @@ export default function ProjectDetails({
{projectDetailTab === 1 && (
-
-
-
-
- {t('projects.mariadb_version')}
- MariaDB {p.mariadbVersion}
+
+
+
+
+
+
+
+
+ {t('projects.mariadb_version')}
+ MariaDB {p.mariadbVersion}
+
}
onClick={onManageDb}
+ sx={{ borderRadius: 2.5, px: 3, height: 44, fontWeight: 700 }}
>
{t('mariadb_manager.title')}
-
+
-
-
- Host
- localhost
-
-
- Port
- {settings.mariaDbPort}
-
-
- Username
- root
-
-
- Password
- (no password)
-
-
-
+
+ {[
+ { label: 'Host', value: 'localhost' },
+ { label: 'Port', value: settings.mariaDbPort },
+ { label: 'Username', value: 'root' },
+ { label: 'Password', value: '(no password)', italic: true }
+ ].map((item, idx) => (
+
+ {item.label.toUpperCase()}
+
+ {item.value}
+
+
+ ))}
+
+
)}
{projectDetailTab === 2 && (
-
+
-
-
- {p.serverType.toUpperCase()} {t('projects.server_settings')}
+
+
+ {p.serverType} {t('projects.server_settings')}
-
+
}
onClick={loadConfig}
- sx={{ color: 'rgba(255,255,255,0.5)' }}
+ sx={{ color: 'rgba(255,255,255,0.5)', borderRadius: 2 }}
>
{t('common.refresh')}
@@ -378,17 +521,17 @@ export default function ProjectDetails({
variant="contained"
size="small"
color="primary"
- startIcon={isConfigSaving ? : }
+ startIcon={isConfigSaving ? : }
disabled={isConfigSaving || isConfigLoading}
onClick={handleSaveConfig}
- sx={{ px: 3 }}
+ sx={{ px: 4, borderRadius: 2, fontWeight: 800 }}
>
{t('common.save').toUpperCase()}
-
+
{t('projects.manual_edit_warning')}
diff --git a/src/renderer/src/components/ProjectManager/ProjectManager.tsx b/src/renderer/src/components/ProjectManager/ProjectManager.tsx
index 700ed61..178ecc5 100644
--- a/src/renderer/src/components/ProjectManager/ProjectManager.tsx
+++ b/src/renderer/src/components/ProjectManager/ProjectManager.tsx
@@ -6,6 +6,8 @@ interface ProjectManagerProps {
projects: any[];
status: any;
settings: any;
+ phpVersions: any[];
+ mariaDbVersions: any[];
projectSearch: string;
setProjectSearch: (v: string) => void;
selectedProjectId: string | null;
@@ -28,6 +30,8 @@ export default function ProjectManager({
projects,
status,
settings,
+ phpVersions,
+ mariaDbVersions,
projectSearch,
setProjectSearch,
selectedProjectId,
@@ -72,6 +76,8 @@ export default function ProjectManager({
project={p}
status={status}
settings={settings}
+ phpVersions={phpVersions}
+ mariaDbVersions={mariaDbVersions}
projectDetailTab={projectDetailTab}
setProjectDetailTab={setProjectDetailTab}
t={t}