feat: implement project management UI with configuration editing and service control capabilities
This commit is contained in:
@@ -17,7 +17,7 @@ Alias /{{HOST}} "{{PATH}}"
|
|||||||
# Directory index handling
|
# Directory index handling
|
||||||
DirectoryIndex index.php index.html
|
DirectoryIndex index.php index.html
|
||||||
|
|
||||||
# PHP Handling via mod_actions and CGI
|
# PHP Handling via mod_actions and CGI (EN STABİL YÖNTEM)
|
||||||
<FilesMatch "\.php$">
|
<FilesMatch "\.php$">
|
||||||
SetHandler application/x-httpd-php-alias-{{HOST}}
|
SetHandler application/x-httpd-php-alias-{{HOST}}
|
||||||
</FilesMatch>
|
</FilesMatch>
|
||||||
|
|||||||
@@ -15,13 +15,12 @@ Alias /{{HOST}} "{{PATH}}"
|
|||||||
# Directory index handling
|
# Directory index handling
|
||||||
DirectoryIndex index.php index.html
|
DirectoryIndex index.php index.html
|
||||||
|
|
||||||
# PHP Handling via mod_actions and CGI
|
# PHP Handling via mod_actions and CGI (EN STABİL YÖNTEM)
|
||||||
<FilesMatch "\.php$">
|
<FilesMatch "\.php$">
|
||||||
SetHandler application/x-httpd-php-alias-{{HOST}}
|
SetHandler application/x-httpd-php-alias-{{HOST}}
|
||||||
</FilesMatch>
|
</FilesMatch>
|
||||||
|
|
||||||
# Action maps the handler to the binary via the ScriptAlias URL
|
# 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"
|
Action application/x-httpd-php-alias-{{HOST}} "{{PHP_BIN_URL}}/php-cgi.exe"
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
"service": "php:5.6.40",
|
||||||
"action": "start",
|
"action": "start",
|
||||||
|
|||||||
@@ -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 () => {
|
ipcMain.handle('services:status', async () => {
|
||||||
return processManager.getStatuses()
|
return processManager.getStatuses()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -235,6 +235,13 @@ export class ProcessManager {
|
|||||||
|
|
||||||
// Also force kill lingering processes even if not in map
|
// Also force kill lingering processes even if not in map
|
||||||
const result = await this.forceKillAll(name)
|
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')
|
this.states.set(name, 'stopped')
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -271,15 +278,33 @@ export class ProcessManager {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
const executableName = name === 'nginx' ? 'nginx.exe' : name === 'apache' ? 'httpd.exe' : name === 'php' ? 'php-cgi.exe' : 'mariadbd.exe'
|
let executableName = ''
|
||||||
spawn('taskkill', ['/F', '/IM', executableName, '/T'], { windowsHide: true })
|
if (name === 'nginx') executableName = 'nginx.exe'
|
||||||
.on('close', () => resolve(true))
|
else if (name === 'apache') executableName = 'httpd.exe'
|
||||||
.on('error', () => resolve(true))
|
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 {
|
} else {
|
||||||
const processName = name === 'nginx' ? 'nginx' : name === 'apache' ? 'httpd' : name === 'php' ? 'php-cgi' : 'mariadbd'
|
let processName = ''
|
||||||
spawn('pkill', ['-f', processName], { windowsHide: true })
|
if (name === 'nginx') processName = 'nginx'
|
||||||
.on('close', () => resolve(true))
|
else if (name === 'apache') processName = 'httpd'
|
||||||
.on('error', () => resolve(true))
|
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 {
|
} else {
|
||||||
@@ -470,10 +495,53 @@ export class ProcessManager {
|
|||||||
return Array.from(new Set(foundLogs)).slice(-count)
|
return Array.from(new Set(foundLogs)).slice(-count)
|
||||||
}
|
}
|
||||||
|
|
||||||
stopAll(): void {
|
async aggressiveKillAll(): Promise<void> {
|
||||||
for (const name of this.processes.keys()) {
|
const isWindows = process.platform === 'win32';
|
||||||
this.stopService(name)
|
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<number>()
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-11
@@ -488,19 +488,22 @@ function App(): JSX.Element {
|
|||||||
|
|
||||||
const handleStopAll = async () => {
|
const handleStopAll = async () => {
|
||||||
if (!window.api) return
|
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'))
|
// Show immediate "stopping" UI feedback for all running services
|
||||||
const mariaDbToStop = Object.keys(status).filter(s => (s.startsWith('mariadb:') || s.startsWith('mysql:')) && (status[s]?.status === 'running' || status[s]?.status === 'starting'))
|
const updatedStatus = { ...status }
|
||||||
|
Object.keys(updatedStatus).forEach(s => {
|
||||||
await Promise.all([
|
if (updatedStatus[s].status === 'running') updatedStatus[s].status = 'stopping'
|
||||||
...criticalServices.filter(s => status[s]?.status === 'running' || status[s]?.status === 'starting').map(s => window.api?.stopService(s)),
|
})
|
||||||
...phpToStop.map(s => window.api?.stopService(s)),
|
setStatus(updatedStatus)
|
||||||
...mariaDbToStop.map(s => window.api?.stopService(s)),
|
|
||||||
window.api?.stopService('mariadb') // Also stop default
|
|
||||||
])
|
|
||||||
|
|
||||||
|
const result = await window.api.invoke('services:stop-all')
|
||||||
|
|
||||||
fetchStatus()
|
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) => {
|
const formatMemory = (bytes?: number) => {
|
||||||
@@ -1587,6 +1590,8 @@ function App(): JSX.Element {
|
|||||||
projects={projects}
|
projects={projects}
|
||||||
status={status}
|
status={status}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
|
phpVersions={phpVersions}
|
||||||
|
mariaDbVersions={mariaDbVersions}
|
||||||
projectSearch={projectSearch}
|
projectSearch={projectSearch}
|
||||||
setProjectSearch={setProjectSearch}
|
setProjectSearch={setProjectSearch}
|
||||||
selectedProjectId={selectedProjectId}
|
selectedProjectId={selectedProjectId}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Stack
|
Stack,
|
||||||
|
Tooltip,
|
||||||
|
Chip,
|
||||||
|
Avatar
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import {
|
import {
|
||||||
Refresh as RefreshIcon,
|
Refresh as RefreshIcon,
|
||||||
@@ -26,7 +29,10 @@ import {
|
|||||||
Language as WebIcon,
|
Language as WebIcon,
|
||||||
Dns as ServerIcon,
|
Dns as ServerIcon,
|
||||||
Code as PhpIcon,
|
Code as PhpIcon,
|
||||||
Storage as DbIcon
|
Storage as DbIcon,
|
||||||
|
Settings as SettingsIcon,
|
||||||
|
Assignment as LogIcon,
|
||||||
|
Analytics as StatusIcon
|
||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import { useState, useEffect, useRef } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import 'prismjs/themes/prism-tomorrow.css'
|
import 'prismjs/themes/prism-tomorrow.css'
|
||||||
@@ -58,9 +64,10 @@ export default function DashboardDetails({
|
|||||||
onReloadService,
|
onReloadService,
|
||||||
onSelectProject
|
onSelectProject
|
||||||
}: DashboardDetailsProps) {
|
}: DashboardDetailsProps) {
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState('overview');
|
||||||
const [configContent, setConfigContent] = useState('');
|
const [configContent, setConfigContent] = useState('');
|
||||||
const [projectConfigContent, setProjectConfigContent] = useState('');
|
const [projectConfigContent, setProjectConfigContent] = useState('');
|
||||||
|
const [liveConfigContent, setLiveConfigContent] = useState('');
|
||||||
const [isLoadingConfig, setIsLoadingConfig] = useState(false);
|
const [isLoadingConfig, setIsLoadingConfig] = useState(false);
|
||||||
const [logs, setLogs] = useState<string[]>([]);
|
const [logs, setLogs] = useState<string[]>([]);
|
||||||
const logEndRef = useRef<HTMLDivElement>(null);
|
const logEndRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -100,17 +107,31 @@ export default function DashboardDetails({
|
|||||||
if (res && res.success) {
|
if (res && res.success) {
|
||||||
setter(res.content || '');
|
setter(res.content || '');
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to fetch config:', res?.message);
|
|
||||||
setter('');
|
setter('');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching config:', error);
|
|
||||||
setter('');
|
setter('');
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingConfig(false);
|
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 saveConfig = async (templateName: string, content: string) => {
|
||||||
const res = await window.api.invoke('config:template:save', { templateName, content });
|
const res = await window.api.invoke('config:template:save', { templateName, content });
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
@@ -118,14 +139,27 @@ export default function DashboardDetails({
|
|||||||
title: t('common.success'),
|
title: t('common.success'),
|
||||||
text: res.message,
|
text: res.message,
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
|
toast: true,
|
||||||
|
position: 'top-end',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 3000,
|
||||||
background: '#1e1e1e',
|
background: '#1e1e1e',
|
||||||
color: '#fff'
|
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({
|
Swal.fire({
|
||||||
title: t('common.error'),
|
title: t('common.success'),
|
||||||
text: res.message,
|
text: res.message,
|
||||||
icon: 'error',
|
icon: 'success',
|
||||||
|
toast: true,
|
||||||
|
position: 'top-end',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 3000,
|
||||||
background: '#1e1e1e',
|
background: '#1e1e1e',
|
||||||
color: '#fff'
|
color: '#fff'
|
||||||
});
|
});
|
||||||
@@ -138,38 +172,41 @@ export default function DashboardDetails({
|
|||||||
setLogs(res || []);
|
setLogs(res || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getHighlightLanguage = (templateName: string) => {
|
const getHighlightLanguage = (name: string) => {
|
||||||
if (!templateName) return Prism.languages.clike;
|
if (!name) return Prism.languages.clike;
|
||||||
if (templateName.includes('nginx')) return Prism.languages.nginx;
|
if (name.includes('nginx')) return Prism.languages.nginx;
|
||||||
if (templateName.includes('httpd') || templateName.includes('apache')) return Prism.languages.apacheconf;
|
if (name.includes('httpd') || name.includes('apache')) return Prism.languages.apacheconf;
|
||||||
if (templateName.includes('.ini') || templateName.includes('.conf')) return Prism.languages.ini;
|
if (name.includes('.ini') || name.includes('.conf')) return Prism.languages.ini;
|
||||||
return Prism.languages.clike;
|
return Prism.languages.clike;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getHighlightLabel = (templateName: string) => {
|
const getHighlightLabel = (name: string) => {
|
||||||
if (!templateName) return 'clike';
|
if (!name) return 'clike';
|
||||||
if (templateName.includes('nginx')) return 'nginx';
|
if (name.includes('nginx')) return 'nginx';
|
||||||
if (templateName.includes('httpd') || templateName.includes('apache')) return 'apacheconf';
|
if (name.includes('httpd') || name.includes('apache')) return 'apacheconf';
|
||||||
if (templateName.includes('.ini') || templateName.includes('.conf')) return 'ini';
|
if (name.includes('.ini') || name.includes('.conf')) return 'ini';
|
||||||
return 'clike';
|
return 'clike';
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeTab === 1) fetchConfig(getTemplateName(), setConfigContent);
|
if (activeTab === 'settings_template') fetchConfig(getTemplateName(), setConfigContent);
|
||||||
if (activeTab === 2) {
|
if (activeTab === 'projects_template') {
|
||||||
const templates = getProjectTemplateNames();
|
const templates = getProjectTemplateNames();
|
||||||
if (templates.length > 0) fetchConfig(templates[0], setProjectConfigContent);
|
if (templates.length > 0) fetchConfig(templates[0], setProjectConfigContent);
|
||||||
}
|
}
|
||||||
if (activeTab === 3) {
|
if (activeTab === 'logs') {
|
||||||
fetchLogs();
|
fetchLogs();
|
||||||
const interval = setInterval(fetchLogs, 3000);
|
const interval = setInterval(fetchLogs, 3000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}
|
}
|
||||||
|
if (activeTab === 'settings_live' && serviceId) {
|
||||||
|
fetchLiveConfig(serviceId);
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [activeTab, serviceId]);
|
}, [activeTab, serviceId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeTab === 3 && logEndRef.current) {
|
if (activeTab === 'logs' && logEndRef.current) {
|
||||||
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
}, [logs, activeTab]);
|
}, [logs, activeTab]);
|
||||||
@@ -186,7 +223,6 @@ export default function DashboardDetails({
|
|||||||
|
|
||||||
const getPort = () => {
|
const getPort = () => {
|
||||||
if (currentStatus.port) return currentStatus.port;
|
if (currentStatus.port) return currentStatus.port;
|
||||||
// Fallback for stopped services or missing data
|
|
||||||
if (serviceId === 'nginx') return settings.nginxPort;
|
if (serviceId === 'nginx') return settings.nginxPort;
|
||||||
if (serviceId === 'apache') return settings.apachePort;
|
if (serviceId === 'apache') return settings.apachePort;
|
||||||
if (serviceType === 'php') {
|
if (serviceType === 'php') {
|
||||||
@@ -210,109 +246,119 @@ export default function DashboardDetails({
|
|||||||
if (serviceId === 'nginx') return !p.serverType || p.serverType === 'nginx';
|
if (serviceId === 'nginx') return !p.serverType || p.serverType === 'nginx';
|
||||||
if (serviceId === 'apache') return p.serverType === 'apache';
|
if (serviceId === 'apache') return p.serverType === 'apache';
|
||||||
if (serviceType === 'php') return p.phpVersion === serviceId.split(':')[1];
|
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;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const ResourceMonitor = ({ cpu, memory }: { cpu?: number; memory?: number }) => (
|
const ResourceMonitor = ({ cpu, memory }: { cpu?: number; memory?: number }) => (
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Box sx={{ mb: 1 }}>
|
<Box sx={{ mb: 1.5 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', fontWeight: 'bold' }}>CPU</Typography>
|
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', fontWeight: 800 }}>CPU USAGE</Typography>
|
||||||
<Typography variant="caption" sx={{ color: 'primary.light', fontWeight: 'bold' }}>{cpu || 0}%</Typography>
|
<Typography variant="caption" sx={{ color: 'primary.main', fontWeight: 800 }}>{cpu || 0}%</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<LinearProgress variant="determinate" value={cpu || 0} sx={{ height: 6, borderRadius: 3, bgcolor: 'rgba(255,255,255,0.05)' }} />
|
<LinearProgress variant="determinate" value={cpu || 0} sx={{ height: 8, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.05)', '.MuiLinearProgress-bar': { borderRadius: 4, background: 'linear-gradient(90deg, #2196F3, #21CBF3)' } }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', fontWeight: 'bold' }}>RAM</Typography>
|
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', fontWeight: 800 }}>RAM USAGE</Typography>
|
||||||
<Typography variant="caption" sx={{ color: 'secondary.light', fontWeight: 'bold' }}>
|
<Typography variant="caption" sx={{ color: 'secondary.main', fontWeight: 800 }}>
|
||||||
{memory ? `${Math.round(memory / (1024 * 1024))} MB` : '0 MB'}
|
{memory ? `${Math.round(memory / (1024 * 1024))} MB` : '0 MB'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<LinearProgress variant="determinate" value={Math.min(((memory || 0) / (1024 * 1024) / 1024) * 100, 100)} sx={{ height: 6, borderRadius: 3, bgcolor: 'rgba(255,255,255,0.05)', '.MuiLinearProgress-bar': { bgcolor: 'secondary.main' } }} />
|
<LinearProgress variant="determinate" value={Math.min(((memory || 0) / (1024 * 1024) / 1024) * 100, 100)} sx={{ height: 8, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.05)', '.MuiLinearProgress-bar': { borderRadius: 4, background: 'linear-gradient(90deg, #9C27B0, #E91E63)' } }} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isPhp = serviceType === 'php';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', bgcolor: 'background.default' }}>
|
||||||
<Box sx={{ p: 3, borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ p: 4, borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', alignItems: 'center', gap: 3 }}>
|
||||||
<Paper sx={{ p: 1.5, bgcolor: 'rgba(255, 255, 255, 0.05)', borderRadius: 3 }}>
|
<Paper sx={{ p: 2, bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
{serviceType === 'nginx' && <WebIcon color="primary" fontSize="large" />}
|
{serviceType === 'nginx' && <WebIcon color="primary" sx={{ fontSize: 40 }} />}
|
||||||
{serviceType === 'apache' && <ServerIcon color="primary" fontSize="large" />}
|
{serviceType === 'apache' && <ServerIcon color="primary" sx={{ fontSize: 40 }} />}
|
||||||
{serviceType === 'php' && <PhpIcon color="primary" fontSize="large" />}
|
{serviceType === 'php' && <PhpIcon color="primary" sx={{ fontSize: 40 }} />}
|
||||||
{serviceType === 'mariadb' && <DbIcon color="primary" fontSize="large" />}
|
{serviceType === 'mariadb' && <DbIcon color="primary" sx={{ fontSize: 40 }} />}
|
||||||
</Paper>
|
</Paper>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Typography variant="h5" sx={{ fontWeight: 800 }}>{serviceId.toUpperCase()}</Typography>
|
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: '-1px' }}>{serviceId.toUpperCase()}</Typography>
|
||||||
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.4)' }}>
|
<Typography variant="subtitle2" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
{isStarting && <Box component="span" sx={{ color: 'warning.light' }}>{t('common.starting', 'Başlatılıyor...')}</Box>}
|
<Box sx={{ width: 8, height: 8, borderRadius: '50%', bgcolor: isRunning ? 'success.main' : 'error.main', boxShadow: isRunning ? '0 0 10px rgba(76, 175, 80, 0.5)' : 'none' }} />
|
||||||
{isStopping && <Box component="span" sx={{ color: 'warning.light' }}>{t('common.stopping', 'Durduruluyor...')}</Box>}
|
{isStarting && <Box component="span" sx={{ color: 'warning.light', fontWeight: 700 }}>{t('common.starting').toUpperCase()}</Box>}
|
||||||
{isRunning && <Box component="span" sx={{ color: 'success.light' }}>{t('common.running', 'Çalışıyor')}</Box>}
|
{isStopping && <Box component="span" sx={{ color: 'warning.light', fontWeight: 700 }}>{t('common.stopping').toUpperCase()}</Box>}
|
||||||
{!isRunning && !isStarting && !isStopping && <Box component="span" sx={{ opacity: 0.5 }}>{t('common.stopped', 'Durduruldu')}</Box>}
|
{isRunning && <Box component="span" sx={{ color: 'success.main', fontWeight: 700 }}>{t('common.running').toUpperCase()}</Box>}
|
||||||
|
{!isRunning && !isStarting && !isStopping && <Box component="span" sx={{ opacity: 0.5, fontWeight: 700 }}>{t('common.stopped').toUpperCase()}</Box>}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack direction="row" spacing={1}>
|
<Stack direction="row" spacing={2}>
|
||||||
{isRunning && !isTransitioning && (
|
{isRunning && !isTransitioning && (
|
||||||
<IconButton onClick={() => onReloadService(serviceId)} sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main' }}>
|
<Tooltip title={t('common.reload')}>
|
||||||
<RefreshIcon />
|
<IconButton onClick={() => 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 }}>
|
||||||
</IconButton>
|
<RefreshIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color={isRunning ? 'error' : 'success'}
|
color={isRunning ? 'error' : 'success'}
|
||||||
disabled={isTransitioning}
|
disabled={isTransitioning}
|
||||||
startIcon={
|
startIcon={isTransitioning ? <CircularProgress size={20} color="inherit" /> : isRunning ? <StopIcon /> : <StartIcon />}
|
||||||
isTransitioning
|
|
||||||
? <CircularProgress size={20} color="inherit" />
|
|
||||||
: isRunning ? <StopIcon /> : <StartIcon />
|
|
||||||
}
|
|
||||||
onClick={() => onToggleService(serviceId)}
|
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')))}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'rgba(255,255,255,0.05)', px: 3 }}>
|
<Box sx={{ borderBottom: 1, borderColor: 'rgba(255,255,255,0.05)', px: 4, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
||||||
<Tabs value={activeTab} onChange={(_e, v) => setActiveTab(v)}>
|
<Tabs value={activeTab} onChange={(_e, v) => setActiveTab(v)} textColor="primary" indicatorColor="primary">
|
||||||
<Tab label={t('dashboard.overview')} />
|
<Tab label={t('dashboard.overview')} value="overview" sx={{ fontWeight: 700, py: 2.5 }} />
|
||||||
<Tab label={t('dashboard.service_settings')} />
|
<Tab label={isPhp ? 'php.ini (Şablon)' : t('dashboard.service_settings')} value="settings_template" sx={{ fontWeight: 700, py: 2.5 }} />
|
||||||
{(serviceId === 'nginx' || serviceId === 'apache' || serviceType === 'php') && (
|
{isPhp && <Tab label="php.ini" value="settings_live" sx={{ fontWeight: 700, py: 2.5 }} />}
|
||||||
<Tab label={t('dashboard.project_settings')} />
|
{(serviceId === 'nginx' || serviceId === 'apache' || isPhp) && (
|
||||||
|
<Tab label={t('dashboard.project_settings')} value="projects_template" sx={{ fontWeight: 700, py: 2.5 }} />
|
||||||
)}
|
)}
|
||||||
<Tab label={t('diagnostics.logs')} />
|
<Tab label={t('diagnostics.logs')} value="logs" sx={{ fontWeight: 700, py: 2.5 }} />
|
||||||
<Tab label={t('projects.title')} />
|
<Tab label={t('projects.title')} value="projects" sx={{ fontWeight: 700, py: 2.5 }} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1, overflowY: 'auto', p: 3 }}>
|
<Box sx={{ flexGrow: 1, overflowY: 'auto', p: 4 }}>
|
||||||
{activeTab === 0 && (
|
{activeTab === 'overview' && (
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={4}>
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 800, mb: 2, color: 'primary.main' }}>{t('dashboard.system_stats')}</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 800, mb: 3, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||||
<Paper sx={{ p: 3, bgcolor: 'rgba(0,0,0,0.2)', borderRadius: 3, border: '1px solid rgba(255,255,255,0.05)' }}>
|
<StatusIcon /> {t('dashboard.system_stats')}
|
||||||
|
</Typography>
|
||||||
|
<Paper sx={{ p: 4, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
<ResourceMonitor cpu={currentStatus.cpu} memory={currentStatus.memory} />
|
<ResourceMonitor cpu={currentStatus.cpu} memory={currentStatus.memory} />
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 800, mb: 2, color: 'primary.main' }}>{t('dashboard.service_info')}</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 800, mb: 3, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||||
<Paper sx={{ p: 3, bgcolor: 'rgba(0,0,0,0.2)', borderRadius: 3, border: '1px solid rgba(255,255,255,0.05)' }}>
|
<SettingsIcon /> {t('dashboard.service_info')}
|
||||||
<Stack spacing={2}>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
<Paper sx={{ p: 4, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
<Typography variant="body2" sx={{ opacity: 0.5 }}>{t('common.port')}</Typography>
|
<Stack spacing={3}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
{getPort()}
|
<Typography variant="body1" sx={{ opacity: 0.5, fontWeight: 700 }}>{t('common.port').toUpperCase()}</Typography>
|
||||||
</Typography>
|
<Paper sx={{ px: 2, py: 0.5, borderRadius: 2, bgcolor: 'rgba(57, 167, 255, 0.1)', border: '1px solid rgba(57, 167, 255, 0.2)' }}>
|
||||||
|
<Typography variant="body1" sx={{ fontWeight: 800, color: 'primary.main', fontFamily: 'monospace' }}>
|
||||||
|
{getPort()}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider sx={{ borderColor: 'rgba(255,255,255,0.05)' }} />
|
<Divider sx={{ opacity: 0.05 }} />
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Typography variant="body2" sx={{ opacity: 0.5 }}>{t('common.status')}</Typography>
|
<Typography variant="body1" sx={{ opacity: 0.5, fontWeight: 700 }}>{t('common.status').toUpperCase()}</Typography>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, color: isRunning ? 'success.main' : 'error.main' }}>
|
<Chip
|
||||||
{(isRunning ? t('common.running') : t('common.stopped')).toUpperCase()}
|
label={(isRunning ? t('common.running') : t('common.stopped')).toUpperCase()}
|
||||||
</Typography>
|
color={isRunning ? 'success' : 'error'}
|
||||||
|
sx={{ fontWeight: 900, borderRadius: 2 }}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -320,46 +366,34 @@ export default function DashboardDetails({
|
|||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 1 && (
|
{(activeTab === 'settings_template' || activeTab === 'settings_live') && (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||||
<Typography variant="overline" sx={{ fontWeight: 800, color: 'primary.main', letterSpacing: 2 }}>
|
<Typography variant="overline" sx={{ fontWeight: 900, color: 'primary.main', letterSpacing: 2 }}>
|
||||||
{getTemplateName().toUpperCase()}
|
{activeTab === 'settings_live' ? 'php.ini (ACTIVE)' : getTemplateName().toUpperCase()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="large"
|
||||||
startIcon={<SaveIcon />}
|
startIcon={isLoadingConfig ? <CircularProgress size={20} color="inherit" /> : <SaveIcon />}
|
||||||
onClick={() => saveConfig(getTemplateName(), configContent)}
|
disabled={isLoadingConfig}
|
||||||
sx={{ borderRadius: 2 }}
|
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()}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper sx={{
|
<Paper sx={{ flexGrow: 1, bgcolor: '#1a1a1a', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)', overflow: 'hidden', display: 'flex', flexDirection: 'column', minHeight: '60vh' }}>
|
||||||
flexGrow: 1,
|
|
||||||
bgcolor: '#1e1e1e',
|
|
||||||
borderRadius: 3,
|
|
||||||
border: '1px solid rgba(255,255,255,0.05)',
|
|
||||||
overflow: 'hidden',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column'
|
|
||||||
}}>
|
|
||||||
{isLoadingConfig ? (
|
{isLoadingConfig ? (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexGrow: 1 }}><CircularProgress /></Box>
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexGrow: 1 }}><CircularProgress /></Box>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ flexGrow: 1, overflow: 'auto' }}>
|
<Box sx={{ flexGrow: 1, overflow: 'auto' }}>
|
||||||
<Editor
|
<Editor
|
||||||
value={configContent}
|
value={activeTab === 'settings_live' ? liveConfigContent : configContent}
|
||||||
onValueChange={setConfigContent}
|
onValueChange={activeTab === 'settings_live' ? setLiveConfigContent : setConfigContent}
|
||||||
highlight={code => Prism.highlight(code, getHighlightLanguage(getTemplateName()), getHighlightLabel(getTemplateName()))}
|
highlight={code => Prism.highlight(code, getHighlightLanguage(activeTab === 'settings_live' ? 'php.ini' : getTemplateName()), getHighlightLabel(activeTab === 'settings_live' ? 'php.ini' : getTemplateName()))}
|
||||||
padding={20}
|
padding={25}
|
||||||
style={{
|
style={{ fontFamily: '"Fira Code", monospace', fontSize: '15px', minHeight: '100%', outline: 'none' }}
|
||||||
fontFamily: '"Fira Code", "Consolas", monospace',
|
|
||||||
fontSize: '14px',
|
|
||||||
minHeight: '100%',
|
|
||||||
outline: 'none'
|
|
||||||
}}
|
|
||||||
textareaClassName="editor-textarea"
|
textareaClassName="editor-textarea"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -368,31 +402,23 @@ export default function DashboardDetails({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 2 && (
|
{activeTab === 'projects_template' && (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||||
<Typography variant="overline" sx={{ fontWeight: 800, color: 'primary.main', letterSpacing: 2 }}>
|
<Typography variant="overline" sx={{ fontWeight: 900, color: 'primary.main', letterSpacing: 2 }}>
|
||||||
{getProjectTemplateNames()[0]?.toUpperCase()}
|
{getProjectTemplateNames()[0]?.toUpperCase()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="large"
|
||||||
startIcon={<SaveIcon />}
|
startIcon={<SaveIcon />}
|
||||||
onClick={() => saveConfig(getProjectTemplateNames()[0], projectConfigContent)}
|
onClick={() => saveConfig(getProjectTemplateNames()[0], projectConfigContent)}
|
||||||
sx={{ borderRadius: 2 }}
|
sx={{ borderRadius: 2.5, px: 4, fontWeight: 800 }}
|
||||||
>
|
>
|
||||||
{t('common.save', 'KAYDET')}
|
{t('common.save').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper sx={{
|
<Paper sx={{ flexGrow: 1, bgcolor: '#1a1a1a', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)', overflow: 'hidden', display: 'flex', flexDirection: 'column', minHeight: '60vh' }}>
|
||||||
flexGrow: 1,
|
|
||||||
bgcolor: '#1e1e1e',
|
|
||||||
borderRadius: 3,
|
|
||||||
border: '1px solid rgba(255,255,255,0.05)',
|
|
||||||
overflow: 'hidden',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column'
|
|
||||||
}}>
|
|
||||||
{isLoadingConfig ? (
|
{isLoadingConfig ? (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexGrow: 1 }}><CircularProgress /></Box>
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexGrow: 1 }}><CircularProgress /></Box>
|
||||||
) : (
|
) : (
|
||||||
@@ -401,13 +427,8 @@ export default function DashboardDetails({
|
|||||||
value={projectConfigContent}
|
value={projectConfigContent}
|
||||||
onValueChange={setProjectConfigContent}
|
onValueChange={setProjectConfigContent}
|
||||||
highlight={code => Prism.highlight(code, getHighlightLanguage(getProjectTemplateNames()[0]), getHighlightLabel(getProjectTemplateNames()[0]))}
|
highlight={code => Prism.highlight(code, getHighlightLanguage(getProjectTemplateNames()[0]), getHighlightLabel(getProjectTemplateNames()[0]))}
|
||||||
padding={20}
|
padding={25}
|
||||||
style={{
|
style={{ fontFamily: '"Fira Code", monospace', fontSize: '15px', minHeight: '100%', outline: 'none' }}
|
||||||
fontFamily: '"Fira Code", "Consolas", monospace',
|
|
||||||
fontSize: '14px',
|
|
||||||
minHeight: '100%',
|
|
||||||
outline: 'none'
|
|
||||||
}}
|
|
||||||
textareaClassName="editor-textarea"
|
textareaClassName="editor-textarea"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -416,44 +437,67 @@ export default function DashboardDetails({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 3 && (
|
{activeTab === 'logs' && (
|
||||||
<Box sx={{ bgcolor: '#000', borderRadius: 2, p: 2, height: '60vh', overflowY: 'auto' }}>
|
<Box sx={{ bgcolor: '#0b0e11', borderRadius: 4, p: 3, height: '65vh', overflowY: 'auto', border: '1px solid rgba(255,255,255,0.05)', boxShadow: 'inset 0 0 20px rgba(0,0,0,0.5)' }}>
|
||||||
{logs.map((log, i) => (
|
<Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1.5, color: 'rgba(255,255,255,0.3)' }}>
|
||||||
<Typography key={i} sx={{
|
<LogIcon fontSize="small" />
|
||||||
fontFamily: 'monospace',
|
<Typography variant="caption" sx={{ fontWeight: 800, letterSpacing: 1 }}>REALTIME SERVICE LOGS</Typography>
|
||||||
fontSize: '0.8rem',
|
</Box>
|
||||||
color: log.toLowerCase().includes('error') ? '#f44336' : (log.toLowerCase().includes('warn') ? '#ff9800' : '#33d9b2'),
|
{logs.length === 0 ? (
|
||||||
mb: 0.5
|
<Box sx={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: 0.2 }}>
|
||||||
}}>
|
<Typography variant="body2" sx={{ fontStyle: 'italic' }}>{t('diagnostics.noLogs')}</Typography>
|
||||||
{log}
|
</Box>
|
||||||
</Typography>
|
) : (
|
||||||
))}
|
logs.map((log, i) => (
|
||||||
|
<Typography key={i} sx={{
|
||||||
|
fontFamily: '"Fira Code", monospace',
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
color: log.toLowerCase().includes('error') ? '#ff5252' : (log.toLowerCase().includes('warn') ? '#ffb142' : '#33d9b2'),
|
||||||
|
mb: 0.8,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
opacity: 0.9
|
||||||
|
}}>
|
||||||
|
{log}
|
||||||
|
</Typography>
|
||||||
|
))
|
||||||
|
)}
|
||||||
<div ref={logEndRef} />
|
<div ref={logEndRef} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 4 && (
|
{activeTab === 'projects' && (
|
||||||
<Paper sx={{ bgcolor: 'rgba(0,0,0,0.2)', borderRadius: 3, border: '1px solid rgba(255,255,255,0.05)', overflow: 'hidden' }}>
|
<Paper sx={{ bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)', overflow: 'hidden' }}>
|
||||||
<List disablePadding>
|
<List disablePadding>
|
||||||
{serviceProjects.length === 0 ? (
|
{serviceProjects.length === 0 ? (
|
||||||
<Box sx={{ p: 4, textAlign: 'center', opacity: 0.3 }}><Typography>{t('projects.empty')}</Typography></Box>
|
<Box sx={{ p: 8, textAlign: 'center', opacity: 0.2 }}>
|
||||||
|
<ProjectIcon sx={{ fontSize: 64, mb: 2 }} />
|
||||||
|
<Typography variant="h6">{t('projects.empty')}</Typography>
|
||||||
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
serviceProjects.map((p, idx) => (
|
serviceProjects.map((p_item, idx) => (
|
||||||
<Box key={p.id}>
|
<Box key={p_item.id}>
|
||||||
<ListItem>
|
<ListItem sx={{ py: 3, px: 4, '&:hover': { bgcolor: 'rgba(255,255,255,0.02)' } }}>
|
||||||
<Paper sx={{ p: 1, mr: 2, bgcolor: 'rgba(255,255,255,0.03)' }}><ProjectIcon sx={{ opacity: 0.5 }} /></Paper>
|
<Avatar sx={{ mr: 3, bgcolor: 'rgba(255,255,255,0.03)', color: 'primary.main', borderRadius: 2 }}>
|
||||||
|
<ProjectIcon />
|
||||||
|
</Avatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={p.name}
|
primary={p_item.name}
|
||||||
secondary={`${p.host} • PHP ${p.phpVersion}`}
|
secondary={`${p_item.host} • PHP ${p_item.phpVersion}`}
|
||||||
primaryTypographyProps={{ fontWeight: 700 }}
|
primaryTypographyProps={{ fontWeight: 800, fontSize: '1.1rem' }}
|
||||||
|
secondaryTypographyProps={{ sx: { opacity: 0.5, mt: 0.5, fontFamily: 'monospace' } }}
|
||||||
/>
|
/>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<IconButton onClick={() => onSelectProject(p.id)} color="primary">
|
<Button
|
||||||
<OpenIcon fontSize="small" />
|
variant="outlined"
|
||||||
</IconButton>
|
onClick={() => onSelectProject(p_item.id)}
|
||||||
|
startIcon={<OpenIcon fontSize="small" />}
|
||||||
|
sx={{ borderRadius: 2, fontWeight: 700 }}
|
||||||
|
>
|
||||||
|
{t('common.open')}
|
||||||
|
</Button>
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{idx < serviceProjects.length - 1 && <Divider sx={{ borderColor: 'rgba(255,255,255,0.05)' }} />}
|
{idx < serviceProjects.length - 1 && <Divider sx={{ opacity: 0.05 }} />}
|
||||||
</Box>
|
</Box>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,12 +7,17 @@ import {
|
|||||||
Tabs,
|
Tabs,
|
||||||
Tab,
|
Tab,
|
||||||
Grid,
|
Grid,
|
||||||
Chip,
|
|
||||||
Divider,
|
Divider,
|
||||||
Stack,
|
Stack,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Alert
|
Alert,
|
||||||
|
TextField,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
Paper
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import {
|
import {
|
||||||
@@ -21,12 +26,15 @@ import {
|
|||||||
Stop as StopIcon,
|
Stop as StopIcon,
|
||||||
PlayArrow as StartIcon,
|
PlayArrow as StartIcon,
|
||||||
Launch as LaunchIcon,
|
Launch as LaunchIcon,
|
||||||
Edit as EditIcon,
|
|
||||||
Delete as DeleteIcon,
|
Delete as DeleteIcon,
|
||||||
Tune as TuningIcon,
|
Tune as TuningIcon,
|
||||||
OpenInNew as OpenIcon,
|
OpenInNew as OpenIcon,
|
||||||
Code as CodeIcon,
|
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'
|
} from '@mui/icons-material'
|
||||||
import ProjectResourceMonitor from './ProjectResourceMonitor'
|
import ProjectResourceMonitor from './ProjectResourceMonitor'
|
||||||
import ProjectConfigEditor from './ProjectConfigEditor'
|
import ProjectConfigEditor from './ProjectConfigEditor'
|
||||||
@@ -36,6 +44,8 @@ interface ProjectDetailsProps {
|
|||||||
project: any;
|
project: any;
|
||||||
status: any;
|
status: any;
|
||||||
settings: any;
|
settings: any;
|
||||||
|
phpVersions: any[];
|
||||||
|
mariaDbVersions: any[];
|
||||||
projectDetailTab: number;
|
projectDetailTab: number;
|
||||||
setProjectDetailTab: (v: number) => void;
|
setProjectDetailTab: (v: number) => void;
|
||||||
t: any;
|
t: any;
|
||||||
@@ -55,10 +65,11 @@ export default function ProjectDetails({
|
|||||||
project: p,
|
project: p,
|
||||||
status,
|
status,
|
||||||
settings,
|
settings,
|
||||||
|
phpVersions,
|
||||||
|
mariaDbVersions,
|
||||||
projectDetailTab,
|
projectDetailTab,
|
||||||
setProjectDetailTab,
|
setProjectDetailTab,
|
||||||
t,
|
t,
|
||||||
onEdit,
|
|
||||||
onDelete,
|
onDelete,
|
||||||
onToggleStatus,
|
onToggleStatus,
|
||||||
onOpenSite,
|
onOpenSite,
|
||||||
@@ -73,6 +84,28 @@ export default function ProjectDetails({
|
|||||||
const [configText, setConfigText] = useState<string>('');
|
const [configText, setConfigText] = useState<string>('');
|
||||||
const [isConfigLoading, setIsConfigLoading] = useState<boolean>(false);
|
const [isConfigLoading, setIsConfigLoading] = useState<boolean>(false);
|
||||||
const [isConfigSaving, setIsConfigSaving] = useState<boolean>(false);
|
const [isConfigSaving, setIsConfigSaving] = useState<boolean>(false);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
// Editing State
|
||||||
|
const [editForm, setEditForm] = useState<any>({
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if (p?.host) {
|
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) {
|
if (!p) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', opacity: 0.3 }}>
|
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', opacity: 0.3 }}>
|
||||||
@@ -147,166 +215,238 @@ export default function ProjectDetails({
|
|||||||
status[`php:${p.phpVersion}`]?.status === 'running'
|
status[`php:${p.phpVersion}`]?.status === 'running'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const installedPhp = phpVersions.filter(v => v.status === 'installed');
|
||||||
|
const installedMariaDb = mariaDbVersions.filter(v => v.status === 'installed');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', bgcolor: 'rgba(0,0,0,0.1)', height: '100%', overflow: 'hidden' }}>
|
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', bgcolor: 'rgba(255,255,255,0.01)', height: '100%', overflow: 'hidden', position: 'relative' }}>
|
||||||
<Box sx={{ p: 3, borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
{/* Header */}
|
||||||
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main', width: 48, height: 48, mt: 0.5 }}>
|
<Box sx={{ p: 4, borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', alignItems: 'flex-start', gap: 3, position: 'relative' }}>
|
||||||
<ProjectIcon />
|
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main', width: 64, height: 64, borderRadius: 3, border: '1px solid rgba(57, 167, 255, 0.2)' }}>
|
||||||
|
<ProjectIcon sx={{ fontSize: 32 }} />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2.5 }}>
|
||||||
<Typography variant="h5" sx={{ fontWeight: 700 }}>{p.name}</Typography>
|
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: '-0.02em', mb: 0.5 }}>{p.name}</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', fontFamily: 'monospace' }}>
|
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.4)', fontFamily: 'monospace' }}>
|
||||||
{p.path}
|
{p.path}
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton size="small" onClick={onOpenFolder} sx={{ color: 'rgba(255,255,255,0.2)', p: 0.5 }}>
|
<Tooltip title={t('projects.open_folder')}>
|
||||||
<FolderOpenIcon sx={{ fontSize: '0.9rem' }} />
|
<IconButton size="small" onClick={onOpenFolder} sx={{ color: 'rgba(255,255,255,0.2)', p: 0.5, '&:hover': { color: 'primary.main', bgcolor: 'rgba(57, 167, 255, 0.1)' } }}>
|
||||||
</IconButton>
|
<FolderOpenIcon sx={{ fontSize: '1rem' }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="contained"
|
||||||
size="small"
|
size="medium"
|
||||||
disabled={isTransitioning}
|
disabled={isTransitioning}
|
||||||
startIcon={isTransitioning ? <CircularProgress size={16} /> : (isRunning ? <StopIcon /> : <StartIcon />)}
|
startIcon={isTransitioning ? <CircularProgress size={18} color="inherit" /> : (isRunning ? <StopIcon /> : <StartIcon />)}
|
||||||
color={isRunning ? 'error' : 'primary'}
|
color={isRunning ? 'error' : 'success'}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (setTransitioning) setTransitioning(true);
|
if (setTransitioning) setTransitioning(true);
|
||||||
try {
|
try {
|
||||||
await onToggleStatus();
|
await onToggleStatus();
|
||||||
} finally {
|
} finally {
|
||||||
if (setTransitioning) {
|
if (setTransitioning) {
|
||||||
// Short delay to ensure status update is reflected
|
|
||||||
setTimeout(() => setTransitioning(false), 500);
|
setTimeout(() => setTransitioning(false), 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sx={{
|
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)' }}
|
||||||
px: 2,
|
|
||||||
height: 36,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
transition: 'all 0.1s ease-in-out',
|
|
||||||
'&:active': { transform: 'scale(0.96)' }
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{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())}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="medium"
|
||||||
startIcon={<LaunchIcon />}
|
startIcon={<LaunchIcon />}
|
||||||
onClick={() => onOpenSite(isHostMapped)}
|
onClick={() => onOpenSite(isHostMapped)}
|
||||||
sx={{
|
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)' } }}
|
||||||
px: 2,
|
|
||||||
height: 36,
|
|
||||||
transition: 'all 0.1s ease-in-out',
|
|
||||||
'&:active': { transform: 'scale(0.96)' }
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{t('projects.open_site')}
|
{t('projects.open_site').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="medium"
|
||||||
startIcon={<CodeIcon />}
|
startIcon={<CodeIcon />}
|
||||||
onClick={onOpenVSCode}
|
onClick={onOpenVSCode}
|
||||||
sx={{
|
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)' } }}
|
||||||
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)' }
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
VS Code Aç
|
VS CODE AÇ
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{!isHostMapped && (
|
{!isHostMapped && (
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="contained"
|
||||||
size="small"
|
size="medium"
|
||||||
color="warning"
|
color="warning"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const finalHost = p.host.includes('.') ? p.host : `${p.host}.local`;
|
const finalHost = p.host.includes('.') ? p.host : `${p.host}.local`;
|
||||||
onAddHost(finalHost);
|
onAddHost(finalHost);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{ px: 3, height: 44, borderRadius: 2.5, fontWeight: 800, boxShadow: '0 4px 12px rgba(255, 152, 0, 0.2)' }}
|
||||||
px: 2,
|
|
||||||
height: 36,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
transition: 'all 0.1s ease-in-out',
|
|
||||||
'&:active': { transform: 'scale(0.96)' }
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Hosts Dosyasına Ekle
|
HOSTS DOSYASINA EKLE
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Tooltip title={t('common.edit')}>
|
|
||||||
<IconButton onClick={onEdit} sx={{ width: 36, height: 36, border: '1px solid rgba(255,255,255,0.1)', borderRadius: '50%' }}>
|
|
||||||
<EditIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={t('common.delete')}>
|
|
||||||
<IconButton onClick={onDelete} sx={{ width: 36, height: 36, border: '1px solid rgba(255,255,255,0.1)', borderRadius: '50%', '&:hover': { color: 'error.main', borderColor: 'error.main' } }}>
|
|
||||||
<DeleteIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Fixed Delete Button at top right */}
|
||||||
|
<Box sx={{ position: 'absolute', top: 32, right: 32 }}>
|
||||||
|
<Tooltip title={t('common.delete')}>
|
||||||
|
<IconButton
|
||||||
|
onClick={onDelete}
|
||||||
|
sx={{
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
bgcolor: 'rgba(255, 68, 68, 0.05)',
|
||||||
|
color: 'error.main',
|
||||||
|
border: '1px solid rgba(255, 68, 68, 0.1)',
|
||||||
|
borderRadius: 3,
|
||||||
|
'&:hover': { bgcolor: 'error.main', color: '#fff', transform: 'scale(1.05) rotate(5deg)', boxShadow: '0 4px 12px rgba(255, 68, 68, 0.3)' },
|
||||||
|
transition: 'all 0.2s'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'rgba(255,255,255,0.05)', px: 3 }}>
|
{/* Tabs */}
|
||||||
|
<Box sx={{ borderBottom: 1, borderColor: 'rgba(255,255,255,0.05)', px: 4, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
||||||
<Tabs value={projectDetailTab} onChange={(_e, v) => setProjectDetailTab(v)} textColor="primary" indicatorColor="primary">
|
<Tabs value={projectDetailTab} onChange={(_e, v) => setProjectDetailTab(v)} textColor="primary" indicatorColor="primary">
|
||||||
<Tab label={t('projects.overview')} />
|
<Tab label={t('projects.overview')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120 }} />
|
||||||
<Tab label={t('projects.database')} />
|
<Tab label={t('projects.database')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120 }} />
|
||||||
<Tab label={t('projects.server_settings')} />
|
<Tab label={t('projects.server_settings')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120 }} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Content Area */}
|
||||||
<Box sx={{ p: 0, flexGrow: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ p: 0, flexGrow: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
||||||
{projectDetailTab === 0 && (
|
{projectDetailTab === 0 && (
|
||||||
<Box sx={{ p: 4, overflowY: 'auto', flexGrow: 1 }}>
|
<Box sx={{ p: 4, overflowY: 'auto', flexGrow: 1 }}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={4}>
|
||||||
<Grid item xs={12} md={6}>
|
{/* Editable Form Side */}
|
||||||
<Box sx={{ mb: 4 }}>
|
<Grid item xs={12} md={7}>
|
||||||
<Typography variant="overline" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 700 }}>{t('projects.site_domain')}</Typography>
|
<Paper sx={{ p: 3, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 0.5 }}>
|
<Typography variant="h6" sx={{ mb: 3, fontWeight: 800, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||||
<Typography sx={{ fontWeight: 500 }}>{p.host}.local</Typography>
|
<SettingsIcon /> {t('projects.edit_details', 'Proje Detaylarını Düzenle')}
|
||||||
<Chip label="CHANGE" size="small" variant="outlined" sx={{ cursor: 'pointer', height: 20, fontSize: '0.65rem' }} />
|
</Typography>
|
||||||
</Box>
|
|
||||||
</Box>
|
<Stack spacing={3}>
|
||||||
|
<TextField
|
||||||
|
label={t('projects.site_name')}
|
||||||
|
fullWidth
|
||||||
|
variant="filled"
|
||||||
|
value={editForm.name}
|
||||||
|
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
||||||
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label={t('projects.site_domain')}
|
||||||
|
fullWidth
|
||||||
|
variant="filled"
|
||||||
|
value={editForm.host}
|
||||||
|
onChange={(e) => 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)' } }}
|
||||||
|
/>
|
||||||
|
|
||||||
<Box sx={{ mb: 4 }}>
|
<Grid container spacing={2}>
|
||||||
<Typography variant="overline" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 700 }}>{t('projects.web_server')}</Typography>
|
<Grid item xs={6}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 0.5 }}>
|
<FormControl fullWidth variant="filled">
|
||||||
<Typography sx={{ fontWeight: 500 }}>{p.serverType.toUpperCase()}</Typography>
|
<InputLabel>{t('projects.web_server')}</InputLabel>
|
||||||
<Chip label="CHANGE" size="small" variant="outlined" component="button" onClick={onEdit} sx={{ cursor: 'pointer', height: 20, fontSize: '0.65rem' }} />
|
<Select
|
||||||
</Box>
|
value={editForm.serverType}
|
||||||
</Box>
|
onChange={(e) => setEditForm({ ...editForm, serverType: e.target.value })}
|
||||||
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
||||||
|
>
|
||||||
|
<MenuItem value="nginx">Nginx</MenuItem>
|
||||||
|
<MenuItem value="apache">Apache</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<FormControl fullWidth variant="filled">
|
||||||
|
<InputLabel>{t('projects.php_version')}</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={editForm.phpVersion}
|
||||||
|
onChange={(e) => setEditForm({ ...editForm, phpVersion: e.target.value })}
|
||||||
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
||||||
|
>
|
||||||
|
{installedPhp.map(v => (
|
||||||
|
<MenuItem key={v.version} value={v.version}>PHP {v.version}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Box sx={{ mb: 4 }}>
|
<FormControl fullWidth variant="filled">
|
||||||
<Typography variant="overline" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 700 }}>{t('projects.php_version')}</Typography>
|
<InputLabel>{t('projects.mariadb_version')}</InputLabel>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 0.5 }}>
|
<Select
|
||||||
<Typography sx={{ fontWeight: 500 }}>{p.phpVersion}</Typography>
|
value={editForm.mariaDbVersion}
|
||||||
<Chip label="CHANGE" size="small" variant="outlined" component="button" onClick={onEdit} sx={{ cursor: 'pointer', height: 20, fontSize: '0.65rem' }} />
|
onChange={(e) => setEditForm({ ...editForm, mariaDbVersion: e.target.value })}
|
||||||
</Box>
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
||||||
</Box>
|
>
|
||||||
|
{installedMariaDb.map(v => (
|
||||||
|
<MenuItem key={v.version} value={v.version}>MariaDB {v.version}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<Box sx={{ pt: 1, display: 'flex', justifyContent: 'flex-start' }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
startIcon={isSaving ? <CircularProgress size={20} color="inherit" /> : <SaveIcon />}
|
||||||
|
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()}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} md={6}>
|
{/* Status Monitor Side */}
|
||||||
<Box sx={{ p: 3, border: '1px solid rgba(255,255,255,0.05)', borderRadius: 2, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
<Grid item xs={12} md={5}>
|
||||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Paper sx={{ p: 3, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)', height: '100%' }}>
|
||||||
<TuningIcon fontSize="small" color="primary" /> {t('dashboard.status')}
|
<Typography variant="subtitle1" sx={{ mb: 4, fontWeight: 800, display: 'flex', alignItems: 'center', gap: 1.5, color: 'rgba(255,255,255,0.6)' }}>
|
||||||
|
<StatusIcon sx={{ color: 'secondary.main' }} /> {t('dashboard.status')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<ProjectResourceMonitor
|
|
||||||
serverType={p.serverType === 'apache' ? 'Apache' : 'Nginx'}
|
<Box sx={{ mb: 4 }}>
|
||||||
serverStats={p.serverType === 'apache' ? status.apache : status.nginx}
|
<ProjectResourceMonitor
|
||||||
phpStats={status[`php:${p.phpVersion}`]}
|
serverType={p.serverType === 'apache' ? 'Apache' : 'Nginx'}
|
||||||
/>
|
serverStats={p.serverType === 'apache' ? status.apache : status.nginx}
|
||||||
</Box>
|
phpStats={status[`php:${p.phpVersion}`]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider sx={{ mb: 3, opacity: 0.1 }} />
|
||||||
|
|
||||||
|
<Box sx={{ p: 2.5, borderRadius: 3, bgcolor: 'rgba(57, 167, 255, 0.03)', border: '1px solid rgba(57, 167, 255, 0.1)' }}>
|
||||||
|
<Typography variant="caption" sx={{ color: 'primary.light', fontWeight: 800, display: 'flex', alignItems: 'center', gap: 1, mb: 1.5, textTransform: 'uppercase', letterSpacing: 1 }}>
|
||||||
|
<CodeIcon fontSize="small" /> System Info
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ opacity: 0.5, lineHeight: 1.6 }}>
|
||||||
|
{t('projects.manual_edit_help', 'Bu sayfadaki değişiklikler kaydedildiğinde servis yapılandırmaları ve hosts dosyası otomatik olarak yenilenecektir.')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -314,63 +454,66 @@ export default function ProjectDetails({
|
|||||||
|
|
||||||
{projectDetailTab === 1 && (
|
{projectDetailTab === 1 && (
|
||||||
<Box sx={{ p: 4, overflowY: 'auto', flexGrow: 1 }}>
|
<Box sx={{ p: 4, overflowY: 'auto', flexGrow: 1 }}>
|
||||||
<Box sx={{ maxWidth: 600 }}>
|
<Box sx={{ maxWidth: 700 }}>
|
||||||
<Box sx={{ p: 3, border: '1px solid rgba(255,255,255,0.05)', borderRadius: 2, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
<Paper sx={{ p: 4, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
|
||||||
<Box>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
<Typography variant="overline" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 700 }}>{t('projects.mariadb_version')}</Typography>
|
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'secondary.main' }}>
|
||||||
<Typography sx={{ mt: 0.5, fontWeight: 600 }}>MariaDB {p.mariadbVersion}</Typography>
|
<DbIcon />
|
||||||
|
</Avatar>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="overline" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 800, letterSpacing: 1 }}>{t('projects.mariadb_version')}</Typography>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: 700 }}>MariaDB {p.mariadbVersion}</Typography>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="medium"
|
||||||
startIcon={<OpenIcon />}
|
startIcon={<OpenIcon />}
|
||||||
onClick={onManageDb}
|
onClick={onManageDb}
|
||||||
|
sx={{ borderRadius: 2.5, px: 3, height: 44, fontWeight: 700 }}
|
||||||
>
|
>
|
||||||
{t('mariadb_manager.title')}
|
{t('mariadb_manager.title')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider sx={{ mb: 3, opacity: 0.1 }} />
|
<Divider sx={{ mb: 4, opacity: 0.1 }} />
|
||||||
|
|
||||||
<Stack spacing={2}>
|
<Grid container spacing={3}>
|
||||||
<Box>
|
{[
|
||||||
<Typography variant="caption" color="text.secondary">Host</Typography>
|
{ label: 'Host', value: 'localhost' },
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>localhost</Typography>
|
{ label: 'Port', value: settings.mariaDbPort },
|
||||||
</Box>
|
{ label: 'Username', value: 'root' },
|
||||||
<Box>
|
{ label: 'Password', value: '(no password)', italic: true }
|
||||||
<Typography variant="caption" color="text.secondary">Port</Typography>
|
].map((item, idx) => (
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{settings.mariaDbPort}</Typography>
|
<Grid item xs={6} key={idx}>
|
||||||
</Box>
|
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 700, display: 'block', mb: 0.5 }}>{item.label.toUpperCase()}</Typography>
|
||||||
<Box>
|
<Typography variant="body1" sx={{ fontWeight: 600, color: item.italic ? 'rgba(255,255,255,0.3)' : '#fff', fontStyle: item.italic ? 'italic' : 'normal' }}>
|
||||||
<Typography variant="caption" color="text.secondary">Username</Typography>
|
{item.value}
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>root</Typography>
|
</Typography>
|
||||||
</Box>
|
</Grid>
|
||||||
<Box>
|
))}
|
||||||
<Typography variant="caption" color="text.secondary">Password</Typography>
|
</Grid>
|
||||||
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.3)', fontStyle: 'italic' }}>(no password)</Typography>
|
</Paper>
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{projectDetailTab === 2 && (
|
{projectDetailTab === 2 && (
|
||||||
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
<Box sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.02)', borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<Box sx={{ p: 2.5, bgcolor: 'rgba(255,255,255,0.02)', borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
<CodeIcon color="primary" />
|
<ServerIcon color="primary" />
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
|
<Typography variant="subtitle2" sx={{ fontWeight: 800, textTransform: 'uppercase', letterSpacing: 1 }}>
|
||||||
{p.serverType.toUpperCase()} {t('projects.server_settings')}
|
{p.serverType} {t('projects.server_settings')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1.5 }}>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
startIcon={<TuningIcon />}
|
startIcon={<TuningIcon />}
|
||||||
onClick={loadConfig}
|
onClick={loadConfig}
|
||||||
sx={{ color: 'rgba(255,255,255,0.5)' }}
|
sx={{ color: 'rgba(255,255,255,0.5)', borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
{t('common.refresh')}
|
{t('common.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -378,17 +521,17 @@ export default function ProjectDetails({
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
color="primary"
|
||||||
startIcon={isConfigSaving ? <CircularProgress size={16} /> : <SaveIcon />}
|
startIcon={isConfigSaving ? <CircularProgress size={16} color="inherit" /> : <SaveIcon />}
|
||||||
disabled={isConfigSaving || isConfigLoading}
|
disabled={isConfigSaving || isConfigLoading}
|
||||||
onClick={handleSaveConfig}
|
onClick={handleSaveConfig}
|
||||||
sx={{ px: 3 }}
|
sx={{ px: 4, borderRadius: 2, fontWeight: 800 }}
|
||||||
>
|
>
|
||||||
{t('common.save').toUpperCase()}
|
{t('common.save').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Alert severity="info" sx={{ borderRadius: 0, py: 0.5, bgcolor: 'rgba(57, 167, 255, 0.05)', color: 'primary.light', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
|
<Alert severity="info" sx={{ borderRadius: 0, py: 1, bgcolor: 'rgba(57, 167, 255, 0.05)', color: 'primary.light', borderBottom: '1px solid rgba(255,255,255,0.05)', fontWeight: 600 }}>
|
||||||
{t('projects.manual_edit_warning')}
|
{t('projects.manual_edit_warning')}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ interface ProjectManagerProps {
|
|||||||
projects: any[];
|
projects: any[];
|
||||||
status: any;
|
status: any;
|
||||||
settings: any;
|
settings: any;
|
||||||
|
phpVersions: any[];
|
||||||
|
mariaDbVersions: any[];
|
||||||
projectSearch: string;
|
projectSearch: string;
|
||||||
setProjectSearch: (v: string) => void;
|
setProjectSearch: (v: string) => void;
|
||||||
selectedProjectId: string | null;
|
selectedProjectId: string | null;
|
||||||
@@ -28,6 +30,8 @@ export default function ProjectManager({
|
|||||||
projects,
|
projects,
|
||||||
status,
|
status,
|
||||||
settings,
|
settings,
|
||||||
|
phpVersions,
|
||||||
|
mariaDbVersions,
|
||||||
projectSearch,
|
projectSearch,
|
||||||
setProjectSearch,
|
setProjectSearch,
|
||||||
selectedProjectId,
|
selectedProjectId,
|
||||||
@@ -72,6 +76,8 @@ export default function ProjectManager({
|
|||||||
project={p}
|
project={p}
|
||||||
status={status}
|
status={status}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
|
phpVersions={phpVersions}
|
||||||
|
mariaDbVersions={mariaDbVersions}
|
||||||
projectDetailTab={projectDetailTab}
|
projectDetailTab={projectDetailTab}
|
||||||
setProjectDetailTab={setProjectDetailTab}
|
setProjectDetailTab={setProjectDetailTab}
|
||||||
t={t}
|
t={t}
|
||||||
|
|||||||
Reference in New Issue
Block a user