feat: implement multi-service management dashboard with project, database, and PHP extension support
This commit is contained in:
@@ -1,4 +1,639 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "5cna280nd",
|
||||||
|
"timestamp": 1775215993040
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "16dhybbv5",
|
||||||
|
"timestamp": 1775215993039
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "hilyatq39",
|
||||||
|
"timestamp": 1775215993031
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "hbnrqkyoy",
|
||||||
|
"timestamp": 1775215993023
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "m5oe9unpm",
|
||||||
|
"timestamp": 1775215978795
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "g9k4uuyb6",
|
||||||
|
"timestamp": 1775215978779
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "igj6t393e",
|
||||||
|
"timestamp": 1775215978760
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "bv7xjvoud",
|
||||||
|
"timestamp": 1775215978742
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:11.4.2",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "w225k0xie",
|
||||||
|
"timestamp": 1775215978291
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:10.11.8",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "x5k5b4mth",
|
||||||
|
"timestamp": 1775215978272
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "51ppyzb6e",
|
||||||
|
"timestamp": 1775215977753
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "yq4trg6v9",
|
||||||
|
"timestamp": 1775215977702
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:10.11.8",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "2cesewr4o",
|
||||||
|
"timestamp": 1775215972222
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:11.4.2",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "vrt0ac3xz",
|
||||||
|
"timestamp": 1775215972214
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "tz9lm7fhp",
|
||||||
|
"timestamp": 1775215972206
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "xrg2toecz",
|
||||||
|
"timestamp": 1775215972199
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "82hwbc4ly",
|
||||||
|
"timestamp": 1775215972191
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "2noq8lw9w",
|
||||||
|
"timestamp": 1775215972185
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "9oha4uilw",
|
||||||
|
"timestamp": 1775215972159
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "jrwvf5v1v",
|
||||||
|
"timestamp": 1775215972157
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "1czbs8x8z",
|
||||||
|
"timestamp": 1775215964037
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "0up4w85t2",
|
||||||
|
"timestamp": 1775215964021
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "gxnmtaz6o",
|
||||||
|
"timestamp": 1775215964004
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "dzb88em3m",
|
||||||
|
"timestamp": 1775215963987
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:11.4.2",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "bu12qdsrx",
|
||||||
|
"timestamp": 1775215963482
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:10.11.8",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "kpy95fkd1",
|
||||||
|
"timestamp": 1775215963459
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "6z4v7vlbz",
|
||||||
|
"timestamp": 1775215962983
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "hs28kfp1j",
|
||||||
|
"timestamp": 1775215962935
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "xbrt3pru2",
|
||||||
|
"timestamp": 1775215960741
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "a9dtwotqn",
|
||||||
|
"timestamp": 1775215960735
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "f1p0g2q9k",
|
||||||
|
"timestamp": 1775215960728
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "ybtvisrvv",
|
||||||
|
"timestamp": 1775215960706
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "p5uu02qoq",
|
||||||
|
"timestamp": 1775215960705
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "3l8qhvac1",
|
||||||
|
"timestamp": 1775215960695
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "rradgo2b9",
|
||||||
|
"timestamp": 1775215905340
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "7xbqz2ep0",
|
||||||
|
"timestamp": 1775215904095
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "439aj13y2",
|
||||||
|
"timestamp": 1775215901797
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "mmfuuw4ec",
|
||||||
|
"timestamp": 1775215900591
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "vdc11eywn",
|
||||||
|
"timestamp": 1775215869698
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "nvd414ghx",
|
||||||
|
"timestamp": 1775215868488
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "8acx9s7xg",
|
||||||
|
"timestamp": 1775215865230
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "sxtv8gp6o",
|
||||||
|
"timestamp": 1775215863969
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "7i2g4nqu5",
|
||||||
|
"timestamp": 1775215838776
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "5oab99keg",
|
||||||
|
"timestamp": 1775215837463
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "spcyyxy1w",
|
||||||
|
"timestamp": 1775215831998
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "vbhzxidnn",
|
||||||
|
"timestamp": 1775215796300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "gm6ejjr5a",
|
||||||
|
"timestamp": 1775215796279
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "i8npou62c",
|
||||||
|
"timestamp": 1775215796260
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "6ncfpzo9k",
|
||||||
|
"timestamp": 1775215796242
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "im5nypjfv",
|
||||||
|
"timestamp": 1775215795208
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:10.11.8",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "za9mzxy37",
|
||||||
|
"timestamp": 1775215792771
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:11.4.2",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "mhlcdoxmt",
|
||||||
|
"timestamp": 1775215792764
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "eycw4hxb5",
|
||||||
|
"timestamp": 1775215792756
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "2z7211041",
|
||||||
|
"timestamp": 1775215792749
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "jg1diypfu",
|
||||||
|
"timestamp": 1775215792743
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "6rg35x2f8",
|
||||||
|
"timestamp": 1775215792736
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "ydizt9o8j",
|
||||||
|
"timestamp": 1775215792717
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "afhcuxp1i",
|
||||||
|
"timestamp": 1775215792707
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "u0nzj9fmp",
|
||||||
|
"timestamp": 1775215787250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: null",
|
||||||
|
"id": "lakde3svs",
|
||||||
|
"timestamp": 1775215781259
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "stop",
|
||||||
|
"status": "info",
|
||||||
|
"id": "89gx0ty64",
|
||||||
|
"timestamp": 1775215781255
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "sj461rtvu",
|
||||||
|
"timestamp": 1775215438924
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "y8yibjkty",
|
||||||
|
"timestamp": 1775215438908
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "c2gx42hum",
|
||||||
|
"timestamp": 1775215438891
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "zzhvseonm",
|
||||||
|
"timestamp": 1775215438875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:11.4.2",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "j39z9a0js",
|
||||||
|
"timestamp": 1775215438399
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:10.11.8",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "wjz63bmwh",
|
||||||
|
"timestamp": 1775215438380
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "cl5wh9qzj",
|
||||||
|
"timestamp": 1775215437873
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "chvykaz6s",
|
||||||
|
"timestamp": 1775215437824
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:10.11.8",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "e1p660xfr",
|
||||||
|
"timestamp": 1775215430918
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:11.4.2",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "zcp93q2mf",
|
||||||
|
"timestamp": 1775215430911
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "9lsi8dk1j",
|
||||||
|
"timestamp": 1775215430903
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "g8a4x1i2q",
|
||||||
|
"timestamp": 1775215430896
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "tjrzyhopg",
|
||||||
|
"timestamp": 1775215430890
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "wu418ce9i",
|
||||||
|
"timestamp": 1775215430884
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "8tbns1luu",
|
||||||
|
"timestamp": 1775215430877
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "exit",
|
||||||
|
"status": "failure",
|
||||||
|
"details": "Exit code: 1",
|
||||||
|
"id": "bfecmmhs2",
|
||||||
|
"timestamp": 1775215430863
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:5.6.40",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "vhbul5hj6",
|
||||||
|
"timestamp": 1775215425999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:7.4.33",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "xhdvhe9v9",
|
||||||
|
"timestamp": 1775215425982
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.2.30",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "whbiv7hnd",
|
||||||
|
"timestamp": 1775215425964
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "php:8.5.4",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "njoudmced",
|
||||||
|
"timestamp": 1775215425945
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:10.11.8",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "fv15yavsp",
|
||||||
|
"timestamp": 1775215425449
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "mariadb:11.4.2",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "q646z3m24",
|
||||||
|
"timestamp": 1775215425427
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "apache",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "i9b435j7i",
|
||||||
|
"timestamp": 1775215424957
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"service": "nginx",
|
||||||
|
"action": "start",
|
||||||
|
"status": "success",
|
||||||
|
"id": "g9eb3ymy8",
|
||||||
|
"timestamp": 1775215424900
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"service": "php:5.6.40",
|
"service": "php:5.6.40",
|
||||||
"action": "start",
|
"action": "start",
|
||||||
|
|||||||
@@ -719,6 +719,10 @@ export function registerIpcHandlers(): void {
|
|||||||
return processManager.getStatuses()
|
return processManager.getStatuses()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('service:status', async (_event, name: string) => {
|
||||||
|
return processManager.getServiceState(name);
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('services:logs', async (_event, serviceName: string) => {
|
ipcMain.handle('services:logs', async (_event, serviceName: string) => {
|
||||||
return processManager.getLogs(serviceName)
|
return processManager.getLogs(serviceName)
|
||||||
})
|
})
|
||||||
|
|||||||
+220
-121
@@ -459,79 +459,130 @@ function App(): JSX.Element {
|
|||||||
setSettings(prev => ({ ...prev, language: newLang }))
|
setSettings(prev => ({ ...prev, language: newLang }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleStartAll = async () => {
|
const runServiceOperation = async (title: string, services: string[], operation: 'start' | 'stop') => {
|
||||||
if (!window.api) return
|
const results: Record<string, any> = {};
|
||||||
const criticalServices = ['nginx', 'apache']
|
const getDisplayLabel = (srv: string) => {
|
||||||
for (const s of criticalServices) {
|
if (srv === 'nginx') return 'Nginx';
|
||||||
if (status[s]?.status === 'stopped') setStatus(prev => ({ ...prev, [s]: { status: 'starting' } }))
|
if (srv === 'apache') return 'Apache';
|
||||||
|
if (srv.startsWith('php:')) return `PHP ${srv.split(':')[1]}`;
|
||||||
|
if (srv.startsWith('mariadb:')) return `MariaDB ${srv.split(':')[1]}`;
|
||||||
|
return srv;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSwal = (currentSrv?: string) => {
|
||||||
|
const html = `
|
||||||
|
<div style="text-align: left; font-family: 'Inter', sans-serif; padding-top: 10px;">
|
||||||
|
${services.map(srv => {
|
||||||
|
const label = getDisplayLabel(srv);
|
||||||
|
const res = results[srv];
|
||||||
|
let icon = '<span style="color: rgba(255,255,255,0.1); margin-right: 12px; font-size: 0.8rem;">○</span>';
|
||||||
|
let statusColor = 'rgba(255,255,255,0.4)';
|
||||||
|
let note = operation === 'start' ? t('common.ready') : t('common.running');
|
||||||
|
|
||||||
|
if (res?.success) {
|
||||||
|
icon = '<span style="color: #33d9b2; margin-right: 12px; font-size: 0.8rem;">●</span>';
|
||||||
|
statusColor = '#33d9b2';
|
||||||
|
note = operation === 'start' ? t('common.started') : t('common.stopped');
|
||||||
|
} else if (res?.error) {
|
||||||
|
icon = '<span style="color: #ff5252; margin-right: 12px; font-size: 0.8rem;">●</span>';
|
||||||
|
statusColor = '#ff5252';
|
||||||
|
note = t('common.error');
|
||||||
|
} else if (srv === currentSrv) {
|
||||||
|
icon = '<span style="color: #39A7FF; margin-right: 12px; font-size: 0.8rem;" class="swal2-loading-dots">●</span>';
|
||||||
|
statusColor = '#39A7FF';
|
||||||
|
note = operation === 'start' ? t('common.starting') : t('common.stopping');
|
||||||
}
|
}
|
||||||
|
|
||||||
const phpToStart = phpVersions.filter(v => v.status === 'installed').map(v => `php:${v.version}`)
|
return '<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; padding: 8px 14px; background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.03); border-radius: 8px; font-size: 0.8rem;">' +
|
||||||
for (const s of phpToStart) {
|
'<span style="font-weight: 600; display: flex; align-items: center;">' + icon + ' ' + label + '</span>' +
|
||||||
if ((status[s]?.status || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: { status: 'starting' } }))
|
'<span style="font-size: 0.65rem; color: ' + statusColor + '; font-weight: 800; letter-spacing: 0.5px;">' + note.toUpperCase() + '</span>' +
|
||||||
}
|
'</div>';
|
||||||
|
}).join('')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
Swal.update({ html });
|
||||||
|
};
|
||||||
|
|
||||||
const mariaDbToStart = mariaDbVersions.filter(v => v.status === 'installed').map(v => `mariadb:${v.version}`)
|
|
||||||
for (const s of mariaDbToStart) {
|
|
||||||
if ((status[s]?.status || 'stopped') === 'stopped') setStatus(prev => ({ ...prev, [s]: { status: 'starting' } }))
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await Promise.all([
|
|
||||||
...criticalServices.filter(s => status[s]?.status !== 'running').map(s => window.api?.startService(s).then((r: any) => ({ service: s, ...r }))),
|
|
||||||
...phpToStart.filter(s => (status[s]?.status || 'stopped') !== 'running').map(s => window.api?.startService(s).then((r: any) => ({ service: s, ...r }))),
|
|
||||||
...mariaDbToStart.filter(s => (status[s]?.status || 'stopped') !== 'running').map(s => window.api?.startService(s).then((r: any) => ({ service: s, ...r })))
|
|
||||||
])
|
|
||||||
|
|
||||||
const errors = results.filter(r => r && !r.success)
|
|
||||||
if (errors.length > 0) {
|
|
||||||
const errorList = errors.map(e => `<b>${e.service}:</b> ${e.message}`).join('<br/>')
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: t('common.error'),
|
title: title.toUpperCase(),
|
||||||
html: `<div style="text-align: left; font-size: 0.9rem;">${errorList}</div>`,
|
html: '<div></div>',
|
||||||
icon: 'error',
|
allowOutsideClick: false,
|
||||||
background: '#1e1e1e',
|
showConfirmButton: false,
|
||||||
color: '#fff'
|
background: '#1a1d21',
|
||||||
})
|
color: '#fff',
|
||||||
|
customClass: {
|
||||||
|
title: 'swal-title-small'
|
||||||
|
},
|
||||||
|
didOpen: () => {
|
||||||
|
updateSwal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const srv of services) {
|
||||||
|
updateSwal(srv);
|
||||||
|
try {
|
||||||
|
const res = operation === 'start' ? await window.api.startService(srv) : await window.api.stopService(srv);
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
// Poll for status confirmation
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 15; // ~7.5 seconds
|
||||||
|
let confirmed = false;
|
||||||
|
|
||||||
|
while (attempts < maxAttempts) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
const statusRes = await window.api.invoke('service:status', srv);
|
||||||
|
|
||||||
|
if (operation === 'start' && statusRes === 'running') {
|
||||||
|
confirmed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (operation === 'stop' && statusRes === 'stopped') {
|
||||||
|
confirmed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
results[srv] = { success: confirmed, error: !confirmed ? t('common.timeout') : null };
|
||||||
} else {
|
} else {
|
||||||
setNotification({ open: true, message: t('dashboard.all_started'), severity: 'success' })
|
results[srv] = { success: false, error: res.message };
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
results[srv] = { success: false, error: err.message };
|
||||||
|
}
|
||||||
|
updateSwal();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchStatus()
|
|
||||||
|
const anyError = Object.values(results).some(r => r.error);
|
||||||
|
|
||||||
|
Swal.update({
|
||||||
|
icon: anyError ? 'warning' : 'success',
|
||||||
|
showConfirmButton: true,
|
||||||
|
confirmButtonText: t('common.close').toUpperCase()
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no error, auto-close after 3s
|
||||||
|
if (!anyError) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (Swal.isVisible()) Swal.close();
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleStopAll = async () => {
|
fetchStatus();
|
||||||
if (!window.api) return
|
};
|
||||||
|
|
||||||
// Show immediate "stopping" UI feedback for all running services
|
const handleStartAll = () => {
|
||||||
const updatedStatus = { ...status }
|
const services = ['nginx', 'apache', ...phpVersions.filter(v => v.status === 'installed').map(v => `php:${v.version}`), ...mariaDbVersions.filter(v => v.status === 'installed').map(v => `mariadb:${v.version}`)];
|
||||||
Object.keys(updatedStatus).forEach(s => {
|
runServiceOperation(t('dashboard.starting_all'), services, 'start');
|
||||||
if (updatedStatus[s].status === 'running') updatedStatus[s].status = 'stopping'
|
};
|
||||||
})
|
|
||||||
setStatus(updatedStatus)
|
|
||||||
|
|
||||||
const result = await window.api.invoke('services:stop-all')
|
const handleStopAll = () => {
|
||||||
|
const services = ['nginx', 'apache', ...phpVersions.filter(v => v.status === 'installed').map(v => `php:${v.version}`), ...mariaDbVersions.filter(v => v.status === 'installed').map(v => `mariadb:${v.version}`)];
|
||||||
if (!result.success) {
|
runServiceOperation(t('dashboard.stopping_all'), services, 'stop');
|
||||||
Swal.fire({
|
};
|
||||||
title: t('common.error'),
|
|
||||||
text: result.message,
|
|
||||||
icon: 'error',
|
|
||||||
background: '#1e1e1e',
|
|
||||||
color: '#fff'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
setNotification({ open: true, message: t('dashboard.all_stopped'), severity: 'success' })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchStatus()
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatMemory = (bytes?: number) => {
|
|
||||||
if (!bytes) return ''
|
|
||||||
const mb = bytes / (1024 * 1024)
|
|
||||||
if (mb > 1024) return `${(mb / 1024).toFixed(1)} GB`
|
|
||||||
return `${Math.round(mb)} MB`
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAddProject = async () => {
|
const handleAddProject = async () => {
|
||||||
if (!window.api) return
|
if (!window.api) return
|
||||||
@@ -716,7 +767,6 @@ function App(): JSX.Element {
|
|||||||
setIsPhpExtensionsOpen(true)
|
setIsPhpExtensionsOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const fetchProcessList = async (id: string) => {
|
const fetchProcessList = async (id: string) => {
|
||||||
if (!window.api) return
|
if (!window.api) return
|
||||||
setIsProcessListLoading(true)
|
setIsProcessListLoading(true)
|
||||||
@@ -752,6 +802,7 @@ function App(): JSX.Element {
|
|||||||
|
|
||||||
const installedPhpVersions = phpVersions.filter(v => v.status === 'installed')
|
const installedPhpVersions = phpVersions.filter(v => v.status === 'installed')
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -841,6 +892,21 @@ function App(): JSX.Element {
|
|||||||
'.swal2-container': {
|
'.swal2-container': {
|
||||||
zIndex: '9999 !important'
|
zIndex: '9999 !important'
|
||||||
},
|
},
|
||||||
|
'.swal-title-small': {
|
||||||
|
fontSize: '1rem !important',
|
||||||
|
fontWeight: '800 !important',
|
||||||
|
letterSpacing: '1px !important',
|
||||||
|
color: '#fff !important',
|
||||||
|
marginTop: '10px !important'
|
||||||
|
},
|
||||||
|
'@keyframes swalLoadingDots': {
|
||||||
|
'0%': { opacity: 0.3, transform: 'scale(0.8)' },
|
||||||
|
'50%': { opacity: 1, transform: 'scale(1.2)' },
|
||||||
|
'100%': { opacity: 0.3, transform: 'scale(0.8)' }
|
||||||
|
},
|
||||||
|
'.swal2-loading-dots': {
|
||||||
|
animation: 'swalLoadingDots 1.4s infinite ease-in-out'
|
||||||
|
},
|
||||||
'.nginx-error-container': {
|
'.nginx-error-container': {
|
||||||
textAlign: 'left !important',
|
textAlign: 'left !important',
|
||||||
fontFamily: 'monospace !important',
|
fontFamily: 'monospace !important',
|
||||||
@@ -863,51 +929,84 @@ function App(): JSX.Element {
|
|||||||
backdropFilter: 'blur(10px)'
|
backdropFilter: 'blur(10px)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar sx={{ gap: 1 }}>
|
<Toolbar sx={{ gap: 1.5, minHeight: '56px !important' }}>
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
<Box sx={{ flexGrow: 1 }} />
|
||||||
<Tooltip title={t('dashboard.start_all')}>
|
|
||||||
<IconButton color="inherit" onClick={handleStartAll} sx={{ '&:hover': { color: 'success.main' } }}>
|
<Button
|
||||||
<StartIcon />
|
variant="outlined"
|
||||||
</IconButton>
|
size="small"
|
||||||
</Tooltip>
|
color="success"
|
||||||
<Tooltip title={t('dashboard.stop_all')}>
|
onClick={handleStartAll}
|
||||||
<IconButton color="inherit" onClick={handleStopAll} sx={{ '&:hover': { color: 'error.main' } }}>
|
startIcon={<StartIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
<StopIcon />
|
sx={{
|
||||||
</IconButton>
|
borderRadius: 1.5,
|
||||||
</Tooltip>
|
fontSize: '0.7rem',
|
||||||
<Divider orientation="vertical" flexItem sx={{ mx: 1, borderColor: 'rgba(255,255,255,0.1)' }} />
|
fontWeight: 800,
|
||||||
<FormControl size="small" variant="outlined" sx={{ minWidth: 80 }}>
|
textTransform: 'none',
|
||||||
|
borderColor: 'rgba(76, 175, 80, 0.3)',
|
||||||
|
px: 1.5,
|
||||||
|
height: 28
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('dashboard.start_all')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
onClick={handleStopAll}
|
||||||
|
startIcon={<StopIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
|
sx={{
|
||||||
|
borderRadius: 1.5,
|
||||||
|
fontSize: '0.7rem',
|
||||||
|
fontWeight: 800,
|
||||||
|
textTransform: 'none',
|
||||||
|
borderColor: 'rgba(244, 67, 54, 0.3)',
|
||||||
|
px: 1.5,
|
||||||
|
height: 28
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('dashboard.stop_all')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Divider orientation="vertical" flexItem sx={{ mx: 0.5, borderColor: 'rgba(255,255,255,0.1)', height: 20, mt: 1.5 }} />
|
||||||
|
|
||||||
|
<FormControl size="small" variant="outlined" sx={{ minWidth: 70 }}>
|
||||||
<Select
|
<Select
|
||||||
value={i18n.language}
|
value={i18n.language}
|
||||||
onChange={handleLanguageChange}
|
onChange={handleLanguageChange}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
IconComponent={LanguageIcon}
|
size="small"
|
||||||
sx={{
|
sx={{
|
||||||
color: 'white',
|
color: 'white',
|
||||||
height: '35px',
|
height: '28px',
|
||||||
fontSize: '0.8rem',
|
fontSize: '0.7rem',
|
||||||
'.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.2)' },
|
fontWeight: 700,
|
||||||
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.4)' },
|
'.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.1)' },
|
||||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'primary.main' },
|
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.2)' },
|
||||||
'.MuiSelect-icon': { color: 'white' }
|
'.MuiSelect-icon': { color: 'rgba(255,255,255,0.5)', fontSize: '1rem' }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{supportedLngs.map(lang => (
|
{supportedLngs.map(lang => (
|
||||||
<MenuItem key={lang} value={lang}>
|
<MenuItem key={lang} value={lang} sx={{ fontSize: '0.75rem' }}>
|
||||||
{lang.toUpperCase()}
|
{lang.toUpperCase()}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<Tooltip title={t('mariadb_process_monitor.title')}>
|
<Tooltip title={t('mariadb_process_monitor.title')}>
|
||||||
<IconButton color="inherit" onClick={() => setActiveTab('db_monitor')} sx={{ '&:hover': { color: 'primary.main' } }}>
|
<IconButton size="small" color="inherit" onClick={() => setActiveTab('db_monitor')} sx={{ p: 0.5, opacity: 0.7, '&:hover': { opacity: 1, color: 'primary.main' } }}>
|
||||||
<TerminalIcon />
|
<TerminalIcon sx={{ fontSize: '1.2rem' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<IconButton color="inherit" onClick={() => setActiveTab('settings')} sx={{ ml: 1 }}>
|
|
||||||
<SettingsIcon />
|
<IconButton size="small" color="inherit" onClick={() => setActiveTab('settings')} sx={{ p: 0.5, opacity: 0.7, '&:hover': { opacity: 1 } }}>
|
||||||
|
<SettingsIcon sx={{ fontSize: '1.2rem' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
@@ -925,60 +1024,60 @@ function App(): JSX.Element {
|
|||||||
<Box sx={{ overflow: 'auto' }}>
|
<Box sx={{ overflow: 'auto' }}>
|
||||||
<List>
|
<List>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')}>
|
<ListItemButton selected={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><DashboardIcon color={activeTab === 'dashboard' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><DashboardIcon fontSize="small" color={activeTab === 'dashboard' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('dashboard.title')} />
|
<ListItemText primary={t('dashboard.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'projects'} onClick={() => setActiveTab('projects')}>
|
<ListItemButton selected={activeTab === 'projects'} onClick={() => setActiveTab('projects')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><ProjectIcon color={activeTab === 'projects' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><ProjectIcon fontSize="small" color={activeTab === 'projects' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('projects.title')} />
|
<ListItemText primary={t('projects.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'tuning'} onClick={() => setActiveTab('tuning')}>
|
<ListItemButton selected={activeTab === 'tuning'} onClick={() => setActiveTab('tuning')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><TuningIcon color={activeTab === 'tuning' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><TuningIcon fontSize="small" color={activeTab === 'tuning' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('tuning.title')} />
|
<ListItemText primary={t('tuning.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'tools'} onClick={() => setActiveTab('tools')}>
|
<ListItemButton selected={activeTab === 'tools'} onClick={() => setActiveTab('tools')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><ToolsIcon color={activeTab === 'tools' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><ToolsIcon fontSize="small" color={activeTab === 'tools' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('common.tools')} />
|
<ListItemText primary={t('common.tools')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'db_manager'} onClick={() => setActiveTab('db_manager')}>
|
<ListItemButton selected={activeTab === 'db_manager'} onClick={() => setActiveTab('db_manager')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><DbIcon color={activeTab === 'db_manager' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><DbIcon fontSize="small" color={activeTab === 'db_manager' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('mariadb_manager.title')} />
|
<ListItemText primary={t('mariadb_manager.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'db_monitor'} onClick={() => setActiveTab('db_monitor')}>
|
<ListItemButton selected={activeTab === 'db_monitor'} onClick={() => setActiveTab('db_monitor')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><TerminalIcon color={activeTab === 'db_monitor' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><TerminalIcon fontSize="small" color={activeTab === 'db_monitor' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('mariadb_process_monitor.title')} />
|
<ListItemText primary={t('mariadb_process_monitor.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
<Divider />
|
<Divider sx={{ opacity: 0.05 }} />
|
||||||
<List>
|
<List>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'user_guide'} onClick={() => setActiveTab('user_guide')}>
|
<ListItemButton selected={activeTab === 'user_guide'} onClick={() => setActiveTab('user_guide')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><GuideIcon color={activeTab === 'user_guide' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><GuideIcon fontSize="small" color={activeTab === 'user_guide' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('common.user_guide')} />
|
<ListItemText primary={t('common.user_guide')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'settings'} onClick={() => setActiveTab('settings')}>
|
<ListItemButton selected={activeTab === 'settings'} onClick={() => setActiveTab('settings')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><SettingsIcon color={activeTab === 'settings' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><SettingsIcon fontSize="small" color={activeTab === 'settings' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('settings.general')} />
|
<ListItemText primary={t('settings.general')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton selected={activeTab === 'event_monitor'} onClick={() => setActiveTab('event_monitor')}>
|
<ListItemButton selected={activeTab === 'event_monitor'} onClick={() => setActiveTab('event_monitor')} sx={{ py: 0.5 }}>
|
||||||
<ListItemIcon><HistoryIcon color={activeTab === 'event_monitor' ? 'primary' : 'inherit'} /></ListItemIcon>
|
<ListItemIcon sx={{ minWidth: 40 }}><HistoryIcon fontSize="small" color={activeTab === 'event_monitor' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||||
<ListItemText primary={t('service_monitor.title')} />
|
<ListItemText primary={t('service_monitor.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
@@ -1402,9 +1501,11 @@ function App(): JSX.Element {
|
|||||||
t={t}
|
t={t}
|
||||||
onFetchStatus={fetchStatus}
|
onFetchStatus={fetchStatus}
|
||||||
onSelectProject={handleSelectProject}
|
onSelectProject={handleSelectProject}
|
||||||
|
runServiceOperation={runServiceOperation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{activeTab === 'db_monitor' && (
|
{activeTab === 'db_monitor' && (
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Paper sx={{ p: 3, bgcolor: 'rgba(255,255,255,0.03)', borderRadius: 2 }}>
|
<Paper sx={{ p: 3, bgcolor: 'rgba(255,255,255,0.03)', borderRadius: 2 }}>
|
||||||
@@ -1634,13 +1735,10 @@ function App(): JSX.Element {
|
|||||||
status[`php:${p.phpVersion}`]?.status === 'running'
|
status[`php:${p.phpVersion}`]?.status === 'running'
|
||||||
);
|
);
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
await window.api.stopService(p.serverType === 'apache' ? 'apache' : 'nginx');
|
runServiceOperation(t('projects.stop_site'), [p.serverType === 'apache' ? 'apache' : 'nginx', `php:${p.phpVersion}`], 'stop');
|
||||||
await window.api.stopService(`php:${p.phpVersion}`);
|
|
||||||
} else {
|
} else {
|
||||||
await window.api.startService(p.serverType === 'apache' ? 'apache' : 'nginx');
|
runServiceOperation(t('projects.start_site'), [p.serverType === 'apache' ? 'apache' : 'nginx', `php:${p.phpVersion}`], 'start');
|
||||||
await window.api.startService(`php:${p.phpVersion}`);
|
|
||||||
}
|
}
|
||||||
fetchStatus();
|
|
||||||
}}
|
}}
|
||||||
onOpenSite={(p: any, isHostMapped: boolean) => {
|
onOpenSite={(p: any, isHostMapped: boolean) => {
|
||||||
const port = p.serverType === 'apache' ? settings.apachePort : settings.nginxPort
|
const port = p.serverType === 'apache' ? settings.apachePort : settings.nginxPort
|
||||||
@@ -1665,6 +1763,7 @@ function App(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onManageDb={() => setActiveTab('db_manager')}
|
onManageDb={() => setActiveTab('db_manager')}
|
||||||
|
onRefreshProjects={fetchProjects}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -284,22 +284,22 @@ export default function DashboardDetails({
|
|||||||
{serviceType === 'mariadb' && <DbIcon color="primary" sx={{ fontSize: 40 }} />}
|
{serviceType === 'mariadb' && <DbIcon color="primary" sx={{ fontSize: 40 }} />}
|
||||||
</Paper>
|
</Paper>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Typography variant="h5" sx={{ fontWeight: 800, letterSpacing: '-0.5px', color: 'primary.main' }}>
|
<Typography variant="h6" sx={{ fontWeight: 800, letterSpacing: '-0.5px', color: 'primary.main', fontSize: '1.25rem' }}>
|
||||||
{serviceId.replace(':', ' ').replace('php', 'PHP').replace('mariadb', 'MariaDB').replace('nginx', 'Nginx').replace('apache', 'Apache')}
|
{serviceId.replace(':', ' ').replace('php', 'PHP').replace('mariadb', 'MariaDB').replace('nginx', 'Nginx').replace('apache', 'Apache')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 0.5 }}>
|
<Typography variant="caption" sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 0.2 }}>
|
||||||
<Box sx={{ width: 6, height: 6, borderRadius: '50%', bgcolor: isRunning ? 'success.main' : 'error.main', boxShadow: isRunning ? '0 0 10px rgba(76, 175, 80, 0.5)' : 'none' }} />
|
<Box sx={{ width: 6, height: 6, borderRadius: '50%', bgcolor: isRunning ? 'success.main' : 'error.main', boxShadow: isRunning ? '0 0 10px rgba(76, 175, 80, 0.5)' : 'none' }} />
|
||||||
{isStarting && <Box component="span" sx={{ color: 'warning.light', fontWeight: 700, letterSpacing: 0.5 }}>{t('common.starting')}</Box>}
|
{isStarting && <Box component="span" sx={{ color: 'warning.light', fontWeight: 700, letterSpacing: 0.5, fontSize: '0.7rem' }}>{t('common.starting')}</Box>}
|
||||||
{isStopping && <Box component="span" sx={{ color: 'warning.light', fontWeight: 700, letterSpacing: 0.5 }}>{t('common.stopping')}</Box>}
|
{isStopping && <Box component="span" sx={{ color: 'warning.light', fontWeight: 700, letterSpacing: 0.5, fontSize: '0.7rem' }}>{t('common.stopping')}</Box>}
|
||||||
{isRunning && <Box component="span" sx={{ color: 'success.main', fontWeight: 700, letterSpacing: 0.5 }}>{t('common.running')}</Box>}
|
{isRunning && <Box component="span" sx={{ color: 'success.main', fontWeight: 700, letterSpacing: 0.5, fontSize: '0.7rem' }}>{t('common.running')}</Box>}
|
||||||
{!isRunning && !isStarting && !isStopping && <Box component="span" sx={{ opacity: 0.5, fontWeight: 700, letterSpacing: 0.5 }}>{t('common.stopped')}</Box>}
|
{!isRunning && !isStarting && !isStopping && <Box component="span" sx={{ opacity: 0.5, fontWeight: 700, letterSpacing: 0.5, fontSize: '0.7rem' }}>{t('common.stopped')}</Box>}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack direction="row" spacing={2}>
|
<Stack direction="row" spacing={1.5}>
|
||||||
{isRunning && !isTransitioning && (
|
{isRunning && !isTransitioning && (
|
||||||
<Tooltip title={t('common.reload')}>
|
<Tooltip title={t('common.reload')}>
|
||||||
<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 onClick={() => onReloadService(serviceId)} sx={{ bgcolor: 'rgba(57, 167, 255, 0.05)', color: 'primary.main', border: '1px solid rgba(57, 167, 255, 0.1)', width: 32, height: 32, borderRadius: 2 }}>
|
||||||
<RefreshIcon />
|
<RefreshIcon sx={{ fontSize: '1.1rem' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
@@ -307,9 +307,10 @@ export default function DashboardDetails({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color={isRunning ? 'error' : 'success'}
|
color={isRunning ? 'error' : 'success'}
|
||||||
disabled={isTransitioning}
|
disabled={isTransitioning}
|
||||||
startIcon={isTransitioning ? <CircularProgress size={20} color="inherit" /> : isRunning ? <StopIcon /> : <StartIcon />}
|
size="small"
|
||||||
|
startIcon={isTransitioning ? <CircularProgress size={14} color="inherit" /> : isRunning ? <StopIcon sx={{ fontSize: '1.1rem' }} /> : <StartIcon sx={{ fontSize: '1.1rem' }} />}
|
||||||
onClick={() => onToggleService(serviceId)}
|
onClick={() => onToggleService(serviceId)}
|
||||||
sx={{ borderRadius: 3, px: 4, height: 48, fontWeight: 800, fontSize: '1rem', boxShadow: 'none' }}
|
sx={{ borderRadius: 2, px: 2, height: 32, fontWeight: 800, fontSize: '0.75rem', boxShadow: 'none' }}
|
||||||
>
|
>
|
||||||
{isStarting ? t('common.starting') : (isStopping ? t('common.stopping') : (isRunning ? t('common.stop') : t('common.start')))}
|
{isStarting ? t('common.starting') : (isStopping ? t('common.stopping') : (isRunning ? t('common.stop') : t('common.start')))}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -318,7 +319,7 @@ export default function DashboardDetails({
|
|||||||
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'rgba(255,255,255,0.05)', px: 4, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
<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)} textColor="primary" indicatorColor="primary" sx={{
|
<Tabs value={activeTab} onChange={(_e, v) => setActiveTab(v)} textColor="primary" indicatorColor="primary" sx={{
|
||||||
'.MuiTab-root': { textTransform: 'none', minHeight: 64, fontWeight: 700, fontSize: '0.95rem' }
|
'.MuiTab-root': { textTransform: 'none', minHeight: 48, fontWeight: 700, fontSize: '0.8rem' }
|
||||||
}}>
|
}}>
|
||||||
<Tab label={t('dashboard.overview')} value="overview" />
|
<Tab label={t('dashboard.overview')} value="overview" />
|
||||||
<Tab label={isPhp ? 'php.ini (Şablon)' : t('dashboard.service_settings')} value="settings_template" />
|
<Tab label={isPhp ? 'php.ini (Şablon)' : t('dashboard.service_settings')} value="settings_template" />
|
||||||
@@ -331,38 +332,37 @@ export default function DashboardDetails({
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1, overflowY: 'auto', p: 4 }}>
|
<Box sx={{ flexGrow: 1, overflowY: 'auto', p: 3 }}>
|
||||||
{activeTab === 'overview' && (
|
{activeTab === 'overview' && (
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 800, mb: 3, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
<Typography variant="body2" sx={{ fontWeight: 800, mb: 1.5, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.8rem' }}>
|
||||||
<StatusIcon /> {t('dashboard.system_stats', 'Sistem İstatistikleri')}
|
<StatusIcon sx={{ fontSize: '1rem' }} /> {t('dashboard.system_stats', 'Sistem İstatistikleri')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paper sx={{ p: 4, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}>
|
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 3, 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="h6" sx={{ fontWeight: 800, mb: 3, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
<Typography variant="body2" sx={{ fontWeight: 800, mb: 1.5, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.8rem' }}>
|
||||||
<SettingsIcon /> {t('dashboard.service_info', 'Servis Bilgileri')}
|
<SettingsIcon sx={{ fontSize: '1rem' }} /> {t('dashboard.service_info', 'Servis Bilgileri')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paper sx={{ p: 4, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}>
|
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 3, border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={1.5}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Typography variant="body1" sx={{ opacity: 0.5, fontWeight: 700 }}>{t('common.port', 'Port')}</Typography>
|
<Typography variant="caption" sx={{ opacity: 0.5, fontWeight: 700, fontSize: '0.75rem' }}>{t('common.port', 'Port')}</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="body2" sx={{ fontWeight: 800, color: 'primary.main', fontFamily: 'monospace', fontSize: '0.75rem' }}>
|
||||||
<Typography variant="body1" sx={{ fontWeight: 800, color: 'primary.main', fontFamily: 'monospace' }}>
|
|
||||||
{getPort()}
|
{getPort()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Divider sx={{ opacity: 0.05 }} />
|
<Divider sx={{ opacity: 0.05 }} />
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Typography variant="body1" sx={{ opacity: 0.5, fontWeight: 700 }}>{t('common.status', 'Durum')}</Typography>
|
<Typography variant="caption" sx={{ opacity: 0.5, fontWeight: 700, fontSize: '0.75rem' }}>{t('common.status', 'Durum')}</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
label={isRunning ? t('common.running', 'Çalışıyor') : t('common.stopped', 'Durduruldu')}
|
label={isRunning ? t('common.running', 'Çalışıyor') : t('common.stopped', 'Durduruldu')}
|
||||||
|
size="small"
|
||||||
color={isRunning ? 'success' : 'error'}
|
color={isRunning ? 'success' : 'error'}
|
||||||
sx={{ fontWeight: 900, borderRadius: 2 }}
|
sx={{ fontWeight: 900, borderRadius: 1.5, height: 20, fontSize: '0.65rem' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -373,21 +373,22 @@ export default function DashboardDetails({
|
|||||||
|
|
||||||
{(activeTab === 'settings_template' || activeTab === 'settings_live') && (
|
{(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: 3 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
<Typography variant="caption" sx={{ fontWeight: 700, color: 'primary.main', letterSpacing: 1 }}>
|
<Typography variant="caption" sx={{ fontWeight: 700, color: 'primary.main', letterSpacing: 1, fontSize: '0.7rem' }}>
|
||||||
{activeTab === 'settings_live' ? 'php.ini (Aktif Konfigürasyon)' : getTemplateName()}
|
{activeTab === 'settings_live' ? 'php.ini (Aktif Konfigürasyon)' : getTemplateName()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="small"
|
||||||
startIcon={isLoadingConfig ? <CircularProgress size={20} color="inherit" /> : <SaveIcon />}
|
startIcon={isLoadingConfig ? <CircularProgress size={16} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem' }} />}
|
||||||
disabled={isLoadingConfig}
|
disabled={isLoadingConfig}
|
||||||
onClick={() => activeTab === 'settings_live' ? saveLiveConfig(serviceId!, liveConfigContent) : saveConfig(getTemplateName(), configContent)}
|
onClick={() => activeTab === 'settings_live' ? saveLiveConfig(serviceId!, liveConfigContent) : saveConfig(getTemplateName(), configContent)}
|
||||||
sx={{ borderRadius: 2.5, px: 4, fontWeight: 800 }}
|
sx={{ borderRadius: 2, px: 2, fontWeight: 800, fontSize: '0.75rem' }}
|
||||||
>
|
>
|
||||||
{t('common.save').toUpperCase()}
|
{t('common.save').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<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' }}>
|
<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' }}>
|
||||||
{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>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ interface DashboardManagerProps {
|
|||||||
t: any;
|
t: any;
|
||||||
onFetchStatus: () => void;
|
onFetchStatus: () => void;
|
||||||
onSelectProject: (projectId: string) => void;
|
onSelectProject: (projectId: string) => void;
|
||||||
|
runServiceOperation: (title: string, services: string[], operation: 'start' | 'stop') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DashboardManager({
|
export default function DashboardManager({
|
||||||
@@ -23,53 +24,20 @@ export default function DashboardManager({
|
|||||||
mariaDbVersions,
|
mariaDbVersions,
|
||||||
t,
|
t,
|
||||||
onFetchStatus,
|
onFetchStatus,
|
||||||
onSelectProject
|
onSelectProject,
|
||||||
|
runServiceOperation
|
||||||
}: DashboardManagerProps) {
|
}: DashboardManagerProps) {
|
||||||
const [selectedServiceId, setSelectedServiceId] = useState<string | null>('nginx');
|
const [selectedServiceId, setSelectedServiceId] = useState<string | null>('nginx');
|
||||||
const [isToggling, setIsToggling] = useState<string | null>(null);
|
|
||||||
const [toggledStates, setToggledStates] = useState<Record<string, string>>({});
|
|
||||||
|
|
||||||
const handleToggleService = async (service: string) => {
|
const handleToggleService = async (service: string) => {
|
||||||
if (!window.api) return;
|
|
||||||
if (isToggling) return;
|
|
||||||
|
|
||||||
const currentStatus = status[service]?.status || 'stopped';
|
const currentStatus = status[service]?.status || 'stopped';
|
||||||
const isRunning = currentStatus === 'running';
|
const isRunning = currentStatus === 'running';
|
||||||
|
const title = isRunning ? t('common.stopping') : t('common.starting');
|
||||||
|
const operation = isRunning ? 'stop' : 'start';
|
||||||
|
|
||||||
setIsToggling(service);
|
runServiceOperation(title, [service], operation);
|
||||||
// Optimistically set transition state if not already there
|
|
||||||
setToggledStates(prev => ({ ...prev, [service]: isRunning ? 'stopping' : 'starting' }));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = isRunning
|
|
||||||
? await window.api.stopService(service)
|
|
||||||
: await window.api.startService(service);
|
|
||||||
|
|
||||||
if (result && !result.success) {
|
|
||||||
Swal.fire({
|
|
||||||
title: t('common.error'),
|
|
||||||
text: result.message,
|
|
||||||
icon: 'error',
|
|
||||||
background: '#1e1e1e',
|
|
||||||
color: '#fff'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsToggling(null);
|
|
||||||
setToggledStates(prev => {
|
|
||||||
const updated = { ...prev };
|
|
||||||
delete updated[service];
|
|
||||||
return updated;
|
|
||||||
});
|
|
||||||
onFetchStatus();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merge actual status with our local transition states
|
|
||||||
const mergedStatus = { ...status };
|
|
||||||
Object.keys(toggledStates).forEach(service => {
|
|
||||||
mergedStatus[service] = { ...mergedStatus[service], status: toggledStates[service] };
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleReloadService = async (service: string) => {
|
const handleReloadService = async (service: string) => {
|
||||||
if (!window.api) return;
|
if (!window.api) return;
|
||||||
@@ -89,7 +57,7 @@ export default function DashboardManager({
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', height: 'calc(100vh - 120px)', bgcolor: 'background.default', overflow: 'hidden', borderRadius: 4, border: '1px solid rgba(255, 255, 255, 0.05)', boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}>
|
<Box sx={{ display: 'flex', height: 'calc(100vh - 120px)', bgcolor: 'background.default', overflow: 'hidden', borderRadius: 4, border: '1px solid rgba(255, 255, 255, 0.05)', boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}>
|
||||||
<DashboardSidebar
|
<DashboardSidebar
|
||||||
status={mergedStatus}
|
status={status}
|
||||||
phpVersions={phpVersions}
|
phpVersions={phpVersions}
|
||||||
mariaDbVersions={mariaDbVersions}
|
mariaDbVersions={mariaDbVersions}
|
||||||
selectedServiceId={selectedServiceId}
|
selectedServiceId={selectedServiceId}
|
||||||
@@ -98,7 +66,7 @@ export default function DashboardManager({
|
|||||||
/>
|
/>
|
||||||
<DashboardDetails
|
<DashboardDetails
|
||||||
serviceId={selectedServiceId}
|
serviceId={selectedServiceId}
|
||||||
status={mergedStatus}
|
status={status}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
t={t}
|
t={t}
|
||||||
|
|||||||
@@ -190,64 +190,65 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
|
|||||||
<DbIcon color="primary" />
|
<DbIcon color="primary" />
|
||||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>{t('mariadb_manager.title')}</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>{t('mariadb_manager.title')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack direction="row" spacing={2}>
|
<Stack direction="row" spacing={1.5}>
|
||||||
<FormControl size="small" sx={{ minWidth: 200 }}>
|
<FormControl size="small" sx={{ minWidth: 160 }}>
|
||||||
<InputLabel id="version-select-label">{t('mariadb.version')}</InputLabel>
|
<InputLabel id="version-select-label" sx={{ fontSize: '0.75rem' }}>{t('mariadb.version')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId="version-select-label"
|
labelId="version-select-label"
|
||||||
value={selectedVersion}
|
value={selectedVersion}
|
||||||
label={t('mariadb.version')}
|
label={t('mariadb.version')}
|
||||||
onChange={(e) => setSelectedVersion(e.target.value)}
|
onChange={(e) => setSelectedVersion(e.target.value)}
|
||||||
|
sx={{ fontSize: '0.75rem', height: 32 }}
|
||||||
>
|
>
|
||||||
{installedVersions.map(v => (
|
{installedVersions.map(v => (
|
||||||
<MenuItem key={v.id} value={v.id}>MariaDB {v.version}</MenuItem>
|
<MenuItem key={v.id} value={v.id} sx={{ fontSize: '0.75rem' }}>MariaDB {v.version}</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button startIcon={<RefreshIcon />} onClick={fetchData} disabled={loading}>
|
<Button variant="outlined" size="small" startIcon={<RefreshIcon sx={{ fontSize: '1rem' }} />} onClick={fetchData} disabled={loading} sx={{ fontSize: '0.7rem', height: 32, borderRadius: 2 }}>
|
||||||
{t('common.refresh')}
|
{t('common.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{!selectedVersion && (
|
{!selectedVersion && (
|
||||||
<Alert severity="info" sx={{ mt: 2 }}>{t('mariadb_manager.no_active_version')}</Alert>
|
<Alert severity="info" sx={{ mt: 2, fontSize: '0.75rem' }}>{t('mariadb_manager.no_active_version')}</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedVersion && (
|
{selectedVersion && (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={3}>
|
||||||
{/* Databases Section */}
|
{/* Databases Section */}
|
||||||
<Box>
|
<Box>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}>
|
||||||
<Typography variant="subtitle1" sx={{ fontWeight: 800, color: 'primary.light' }}>
|
<Typography variant="subtitle2" sx={{ fontWeight: 800, color: 'primary.light', fontSize: '0.8rem' }}>
|
||||||
{t('mariadb_manager.databases_title')}
|
{t('mariadb_manager.databases_title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button variant="outlined" size="small" startIcon={<AddIcon />} onClick={handleCreateDb}>
|
<Button variant="outlined" size="small" startIcon={<AddIcon sx={{ fontSize: '1rem' }} />} onClick={handleCreateDb} sx={{ fontSize: '0.7rem', height: 28, borderRadius: 1.5 }}>
|
||||||
{t('mariadb_manager.create_db_btn')}
|
{t('mariadb_manager.create_db_btn')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<TableContainer component={Paper} variant="outlined" sx={{ bgcolor: 'rgba(0,0,0,0.2)', maxHeight: 400 }}>
|
<TableContainer component={Paper} variant="outlined" sx={{ bgcolor: 'rgba(0,0,0,0.2)', maxHeight: 300, borderRadius: 2 }}>
|
||||||
<Table stickyHeader size="small">
|
<Table stickyHeader size="small">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold' }}>{t('mariadb_manager.db_name')}</TableCell>
|
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.db_name')}</TableCell>
|
||||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold' }}>{t('mariadb_manager.db_size')}</TableCell>
|
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.db_size')}</TableCell>
|
||||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold', textAlign: 'right' }}>{t('common.actions')}</TableCell>
|
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1, textAlign: 'right' }}>{t('common.actions')}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<TableRow><TableCell colSpan={3} align="center"><CircularProgress size={24} sx={{ my: 2 }} /></TableCell></TableRow>
|
<TableRow><TableCell colSpan={3} align="center"><CircularProgress size={20} sx={{ my: 1 }} /></TableCell></TableRow>
|
||||||
) : databases.length === 0 ? (
|
) : databases.length === 0 ? (
|
||||||
<TableRow><TableCell colSpan={3} align="center" sx={{ py: 3, color: 'text.secondary' }}>{t('common.no_data')}</TableCell></TableRow>
|
<TableRow><TableCell colSpan={3} align="center" sx={{ py: 2, color: 'text.secondary', fontSize: '0.7rem' }}>{t('common.no_data')}</TableCell></TableRow>
|
||||||
) : databases.map(db => (
|
) : databases.map(db => (
|
||||||
<TableRow key={db.name}>
|
<TableRow key={db.name}>
|
||||||
<TableCell sx={{ fontWeight: 'bold' }}>{db.name}</TableCell>
|
<TableCell sx={{ fontWeight: 600, fontSize: '0.75rem' }}>{db.name}</TableCell>
|
||||||
<TableCell>{db.size.toFixed(2)} MB</TableCell>
|
<TableCell sx={{ fontSize: '0.7rem', opacity: 0.8 }}>{db.size.toFixed(2)} MB</TableCell>
|
||||||
<TableCell align="right">
|
<TableCell align="right" sx={{ py: 0.5 }}>
|
||||||
<Tooltip title={t('common.delete')}>
|
<Tooltip title={t('common.delete')}>
|
||||||
<IconButton color="error" size="small" onClick={() => handleDeleteDb(db.name)}>
|
<IconButton color="error" size="small" onClick={() => handleDeleteDb(db.name)} sx={{ p: 0.5 }}>
|
||||||
<DeleteIcon fontSize="small" />
|
<DeleteIcon sx={{ fontSize: '1rem' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -258,50 +259,50 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider sx={{ opacity: 0.1 }} />
|
<Divider sx={{ opacity: 0.05 }} />
|
||||||
|
|
||||||
{/* Users Section */}
|
{/* Users Section */}
|
||||||
<Box>
|
<Box>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}>
|
||||||
<Typography variant="subtitle1" sx={{ fontWeight: 800, color: 'secondary.light' }}>
|
<Typography variant="subtitle2" sx={{ fontWeight: 800, color: 'secondary.light', fontSize: '0.8rem' }}>
|
||||||
{t('mariadb_manager.users_title')}
|
{t('mariadb_manager.users_title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button variant="outlined" color="secondary" size="small" startIcon={<AddIcon />} onClick={handleCreateUser}>
|
<Button variant="outlined" color="secondary" size="small" startIcon={<AddIcon sx={{ fontSize: '1rem' }} />} onClick={handleCreateUser} sx={{ fontSize: '0.7rem', height: 28, borderRadius: 1.5 }}>
|
||||||
{t('mariadb_manager.create_user_btn')}
|
{t('mariadb_manager.create_user_btn')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<TableContainer component={Paper} variant="outlined" sx={{ bgcolor: 'rgba(0,0,0,0.2)', maxHeight: 400 }}>
|
<TableContainer component={Paper} variant="outlined" sx={{ bgcolor: 'rgba(0,0,0,0.2)', maxHeight: 300, borderRadius: 2 }}>
|
||||||
<Table stickyHeader size="small">
|
<Table stickyHeader size="small">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold' }}>{t('mariadb_manager.user_label')}</TableCell>
|
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.user_label')}</TableCell>
|
||||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold' }}>Host</TableCell>
|
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>Host</TableCell>
|
||||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold', textAlign: 'right' }}>{t('common.actions')}</TableCell>
|
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1, textAlign: 'right' }}>{t('common.actions')}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<TableRow><TableCell colSpan={3} align="center"><CircularProgress size={24} sx={{ my: 2 }} /></TableCell></TableRow>
|
<TableRow><TableCell colSpan={3} align="center"><CircularProgress size={20} sx={{ my: 1 }} /></TableCell></TableRow>
|
||||||
) : users.length === 0 ? (
|
) : users.length === 0 ? (
|
||||||
<TableRow><TableCell colSpan={3} align="center" sx={{ py: 3, color: 'text.secondary' }}>{t('common.no_data')}</TableCell></TableRow>
|
<TableRow><TableCell colSpan={3} align="center" sx={{ py: 2, color: 'text.secondary', fontSize: '0.7rem' }}>{t('common.no_data')}</TableCell></TableRow>
|
||||||
) : users.map(user => (
|
) : users.map(user => (
|
||||||
<TableRow key={`${user.user}-${user.host}`}>
|
<TableRow key={`${user.user}-${user.host}`}>
|
||||||
<TableCell sx={{ fontWeight: 'bold' }}>
|
<TableCell sx={{ fontWeight: 600, fontSize: '0.75rem' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<UserIcon fontSize="inherit" sx={{ opacity: 0.5 }} />
|
<UserIcon sx={{ opacity: 0.5, fontSize: '0.9rem' }} />
|
||||||
{user.user}
|
{user.user}
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell sx={{ fontSize: '0.7rem', opacity: 0.8 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<HostIcon fontSize="inherit" sx={{ opacity: 0.5 }} />
|
<HostIcon sx={{ opacity: 0.5, fontSize: '0.9rem' }} />
|
||||||
{user.host}
|
{user.host}
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right">
|
<TableCell align="right" sx={{ py: 0.5 }}>
|
||||||
<Tooltip title={t('common.delete')}>
|
<Tooltip title={t('common.delete')}>
|
||||||
<IconButton color="error" size="small" onClick={() => handleDeleteUser(user.user, user.host)}>
|
<IconButton color="error" size="small" onClick={() => handleDeleteUser(user.user, user.host)} sx={{ p: 0.5 }}>
|
||||||
<DeleteIcon fontSize="small" />
|
<DeleteIcon sx={{ fontSize: '1rem' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -311,6 +312,7 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
|
|||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -178,17 +178,17 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ m: 0, p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<DialogTitle sx={{ m: 0, p: 1.5, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6">{t('php.extensions.title', { version: phpVersion })}</Typography>
|
<Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{t('php.extensions.title', { version: phpVersion })}</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">{t('php.extensions.desc')}</Typography>
|
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.65rem' }}>{t('php.extensions.desc')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton onClick={onClose} sx={{ color: 'text.secondary' }}>
|
<IconButton onClick={onClose} size="small" sx={{ color: 'text.secondary' }}>
|
||||||
<CloseIcon />
|
<CloseIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent dividers sx={{ borderColor: 'rgba(255,255,255,0.1)', p: 0 }}>
|
<DialogContent dividers sx={{ borderColor: 'rgba(255,255,255,0.05)', p: 0 }}>
|
||||||
<Box sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.03)', display: 'flex', gap: 1 }}>
|
<Box sx={{ p: 1.5, bgcolor: 'rgba(255,255,255,0.02)', display: 'flex', gap: 1 }}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
size="small"
|
size="small"
|
||||||
@@ -198,33 +198,34 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: (
|
startAdornment: (
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
<SearchIcon sx={{ color: 'text.secondary' }} />
|
<SearchIcon sx={{ color: 'text.secondary', fontSize: '1rem' }} />
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
),
|
),
|
||||||
sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)' }
|
sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)', fontSize: '0.75rem', height: 32 }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<RawIcon />}
|
size="small"
|
||||||
|
startIcon={<RawIcon sx={{ fontSize: '1rem' }} />}
|
||||||
onClick={fetchRawConfig}
|
onClick={fetchRawConfig}
|
||||||
sx={{ whiteSpace: 'nowrap', borderColor: 'rgba(255,255,255,0.2)', color: '#fff' }}
|
sx={{ whiteSpace: 'nowrap', borderColor: 'rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.7)', fontSize: '0.7rem', height: 32, borderRadius: 1.5 }}
|
||||||
>
|
>
|
||||||
{t('php.extensions.raw_editor')}
|
{t('php.extensions.raw_editor')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||||
<CircularProgress />
|
<CircularProgress size={24} />
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<List sx={{ maxHeight: 400, overflow: 'auto' }}>
|
<List sx={{ maxHeight: 350, overflow: 'auto' }} dense>
|
||||||
{filteredExtensions.length === 0 ? (
|
{filteredExtensions.length === 0 ? (
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={searchTerm ? t('php.extensions.not_found') : t('php.extensions.none')}
|
primary={searchTerm ? t('php.extensions.not_found') : t('php.extensions.none')}
|
||||||
sx={{ textAlign: 'center', color: 'text.secondary' }}
|
primaryTypographyProps={{ sx: { textAlign: 'center', color: 'text.secondary', fontSize: '0.75rem' } }}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
) : (
|
) : (
|
||||||
@@ -232,14 +233,15 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
<ListItem
|
<ListItem
|
||||||
key={ext.name}
|
key={ext.name}
|
||||||
divider
|
divider
|
||||||
sx={{ borderColor: 'rgba(255,255,255,0.05)' }}
|
sx={{ borderColor: 'rgba(255,255,255,0.03)', py: 0.5 }}
|
||||||
secondaryAction={
|
secondaryAction={
|
||||||
saving === ext.name ? (
|
saving === ext.name ? (
|
||||||
<CircularProgress size={24} />
|
<CircularProgress size={16} />
|
||||||
) : (
|
) : (
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
checked={ext.enabled}
|
checked={ext.enabled}
|
||||||
|
size="small"
|
||||||
onChange={(e) => handleToggle(ext.name, e.target.checked)}
|
onChange={(e) => handleToggle(ext.name, e.target.checked)}
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
@@ -248,13 +250,13 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
>
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.75rem', fontWeight: 600 }}>
|
||||||
{ext.name}
|
{ext.name}
|
||||||
{ext.enabled && <Chip label={t('php.extensions.active')} size="small" color="success" sx={{ height: 18, fontSize: '0.65rem' }} />}
|
{ext.enabled && <Chip label={t('php.extensions.active')} size="small" color="success" sx={{ height: 16, fontSize: '0.55rem', fontWeight: 900 }} />}
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
secondary={ext.dll}
|
secondary={ext.dll}
|
||||||
secondaryTypographyProps={{ sx: { color: 'text.secondary', fontSize: '0.75rem' } }}
|
secondaryTypographyProps={{ sx: { color: 'text.secondary', fontSize: '0.65rem', opacity: 0.5 } }}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))
|
))
|
||||||
@@ -262,11 +264,11 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
</List>
|
</List>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.02)' }}>
|
<DialogActions sx={{ p: 1.5, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
||||||
<Button onClick={fetchExtensions} startIcon={<RefreshIcon />} sx={{ mr: 'auto' }}>
|
<Button onClick={fetchExtensions} size="small" startIcon={<RefreshIcon sx={{ fontSize: '0.9rem' }} />} sx={{ mr: 'auto', fontSize: '0.7rem' }}>
|
||||||
{t('common.refresh').toUpperCase()}
|
{t('common.refresh').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onClose} variant="outlined" color="primary">
|
<Button onClick={onClose} variant="outlined" size="small" sx={{ fontSize: '0.7rem', borderRadius: 1.5 }}>
|
||||||
{t('common.close').toUpperCase()}
|
{t('common.close').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
@@ -282,14 +284,16 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
bgcolor: '#1a1a1a',
|
bgcolor: '#1a1a1a',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
|
backgroundImage: 'none',
|
||||||
|
border: '1px solid rgba(255,255,255,0.1)'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, p: 2 }}>
|
<DialogTitle sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, p: 1.5 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Typography variant="h6">{t('php.extensions.ini_edit', { version: phpVersion })}</Typography>
|
<Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{t('php.extensions.ini_edit', { version: phpVersion })}</Typography>
|
||||||
<IconButton onClick={() => setIsRawEditorOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}>
|
<IconButton onClick={() => setIsRawEditorOpen(false)} size="small" sx={{ color: 'rgba(255,255,255,0.5)' }}>
|
||||||
<CloseIcon />
|
<CloseIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography
|
<Typography
|
||||||
@@ -298,6 +302,8 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
color: 'primary.main',
|
color: 'primary.main',
|
||||||
wordBreak: 'break-all',
|
wordBreak: 'break-all',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
fontSize: '0.6rem',
|
||||||
|
opacity: 0.7,
|
||||||
'&:hover': { textDecoration: 'underline', color: 'primary.light' }
|
'&:hover': { textDecoration: 'underline', color: 'primary.light' }
|
||||||
}}
|
}}
|
||||||
onClick={() => window.api.invoke('shell:open-path', configPath)}
|
onClick={() => window.api.invoke('shell:open-path', configPath)}
|
||||||
@@ -306,7 +312,7 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
{configPath}
|
{configPath}
|
||||||
</Typography>
|
</Typography>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<Box sx={{ px: 2, pb: 2, bgcolor: '#1a1a1a' }}>
|
<Box sx={{ px: 1.5, pb: 1.5, bgcolor: '#1a1a1a' }}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
size="small"
|
size="small"
|
||||||
@@ -316,37 +322,42 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
|||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: (
|
startAdornment: (
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
<SearchIcon sx={{ color: 'text.secondary', fontSize: '1.2rem' }} />
|
<SearchIcon sx={{ color: 'text.secondary', fontSize: '1rem' }} />
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
),
|
),
|
||||||
sx: {
|
sx: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
bgcolor: 'rgba(255,255,255,0.05)',
|
bgcolor: 'rgba(255,255,255,0.03)',
|
||||||
'& fieldset': { borderColor: 'rgba(255,255,255,0.1)' }
|
fontSize: '0.75rem',
|
||||||
|
height: 32,
|
||||||
|
'& fieldset': { borderColor: 'rgba(255,255,255,0.05)' }
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<DialogContent sx={{ p: 0, minHeight: 400, borderTop: '1px solid rgba(255,255,255,0.1)' }}>
|
<DialogContent sx={{ p: 0, minHeight: 350, borderTop: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
<PhpIniCodeEditor
|
<PhpIniCodeEditor
|
||||||
value={rawConfig}
|
value={rawConfig}
|
||||||
onChange={setRawConfig}
|
onChange={setRawConfig}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ p: 2, bgcolor: 'rgba(0,0,0,0.2)' }}>
|
<DialogActions sx={{ p: 1.5, bgcolor: 'rgba(0,0,0,0.1)' }}>
|
||||||
<Button onClick={() => setIsRawEditorOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}>
|
<Button onClick={() => setIsRawEditorOpen(false)} size="small" sx={{ color: 'rgba(255,255,255,0.4)', fontSize: '0.7rem' }}>
|
||||||
{t('common.cancel').toUpperCase()}
|
{t('common.cancel').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSaveRaw}
|
onClick={handleSaveRaw}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
startIcon={isSavingRaw ? <CircularProgress size={18} color="inherit" /> : <SaveIcon />}
|
size="small"
|
||||||
|
startIcon={isSavingRaw ? <CircularProgress size={14} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem' }} />}
|
||||||
disabled={isSavingRaw}
|
disabled={isSavingRaw}
|
||||||
|
sx={{ fontSize: '0.7rem', borderRadius: 1.5, px: 2 }}
|
||||||
>
|
>
|
||||||
{isSavingRaw ? t('php.extensions.saving_btn') : t('php.extensions.save_restart_btn')}
|
{isSavingRaw ? t('php.extensions.saving_btn') : t('php.extensions.save_restart_btn')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ interface ProjectDetailsProps {
|
|||||||
onOpenVSCode: () => void;
|
onOpenVSCode: () => void;
|
||||||
onManageDb: () => void;
|
onManageDb: () => void;
|
||||||
onAddHost: (hostname: string) => void;
|
onAddHost: (hostname: string) => void;
|
||||||
|
onRefreshProjects: () => void;
|
||||||
isTransitioning?: boolean;
|
isTransitioning?: boolean;
|
||||||
setTransitioning?: (v: boolean) => void;
|
setTransitioning?: (v: boolean) => void;
|
||||||
}
|
}
|
||||||
@@ -77,6 +78,7 @@ export default function ProjectDetails({
|
|||||||
onOpenVSCode,
|
onOpenVSCode,
|
||||||
onManageDb,
|
onManageDb,
|
||||||
onAddHost,
|
onAddHost,
|
||||||
|
onRefreshProjects,
|
||||||
isTransitioning = false,
|
isTransitioning = false,
|
||||||
setTransitioning
|
setTransitioning
|
||||||
}: ProjectDetailsProps) {
|
}: ProjectDetailsProps) {
|
||||||
@@ -152,6 +154,7 @@ export default function ProjectDetails({
|
|||||||
background: '#1e1e1e',
|
background: '#1e1e1e',
|
||||||
color: '#fff'
|
color: '#fff'
|
||||||
});
|
});
|
||||||
|
onRefreshProjects();
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
@@ -187,6 +190,7 @@ export default function ProjectDetails({
|
|||||||
background: '#1e1e1e',
|
background: '#1e1e1e',
|
||||||
color: '#fff'
|
color: '#fff'
|
||||||
});
|
});
|
||||||
|
if (onRefreshProjects) onRefreshProjects();
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
@@ -243,9 +247,9 @@ export default function ProjectDetails({
|
|||||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="medium"
|
size="small"
|
||||||
disabled={isTransitioning}
|
disabled={isTransitioning}
|
||||||
startIcon={isTransitioning ? <CircularProgress size={18} color="inherit" /> : (isRunning ? <StopIcon /> : <StartIcon />)}
|
startIcon={isTransitioning ? <CircularProgress size={16} color="inherit" /> : (isRunning ? <StopIcon sx={{ fontSize: '1rem !important' }} /> : <StartIcon sx={{ fontSize: '1rem !important' }} />)}
|
||||||
color={isRunning ? 'error' : 'success'}
|
color={isRunning ? 'error' : 'success'}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (setTransitioning) setTransitioning(true);
|
if (setTransitioning) setTransitioning(true);
|
||||||
@@ -257,27 +261,27 @@ export default function ProjectDetails({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sx={{ px: 3, height: 44, borderRadius: 2.5, fontWeight: 800, boxShadow: 'none' }}
|
sx={{ px: 2, height: 32, borderRadius: 1.5, fontWeight: 700, fontSize: '0.75rem', boxShadow: 'none' }}
|
||||||
>
|
>
|
||||||
{isTransitioning ? t('common.processing') : (isRunning ? t('projects.stop_site').toUpperCase() : t('projects.start_site').toUpperCase())}
|
{isTransitioning ? t('common.processing') : (isRunning ? t('projects.stop_site').toUpperCase() : t('projects.start_site').toUpperCase())}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="medium"
|
size="small"
|
||||||
startIcon={<LaunchIcon />}
|
startIcon={<LaunchIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
onClick={() => onOpenSite(isHostMapped)}
|
onClick={() => onOpenSite(isHostMapped)}
|
||||||
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)' } }}
|
sx={{ px: 2, height: 32, borderRadius: 1.5, fontWeight: 600, fontSize: '0.75rem', borderColor: 'rgba(255,255,255,0.1)', '&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(57, 167, 255, 0.05)' } }}
|
||||||
>
|
>
|
||||||
{t('projects.open_site').toUpperCase()}
|
{t('projects.open_site').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="medium"
|
size="small"
|
||||||
startIcon={<CodeIcon />}
|
startIcon={<CodeIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
onClick={onOpenVSCode}
|
onClick={onOpenVSCode}
|
||||||
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)' } }}
|
sx={{ px: 2, height: 32, borderRadius: 1.5, borderColor: 'rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.6)', fontWeight: 600, fontSize: '0.75rem', '&:hover': { borderColor: '#007ACC', color: '#007ACC', bgcolor: 'rgba(0, 122, 204, 0.05)' } }}
|
||||||
>
|
>
|
||||||
VS CODE AÇ
|
VS CODE AÇ
|
||||||
</Button>
|
</Button>
|
||||||
@@ -285,13 +289,13 @@ export default function ProjectDetails({
|
|||||||
{!isHostMapped && (
|
{!isHostMapped && (
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="medium"
|
size="small"
|
||||||
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={{ px: 3, height: 44, borderRadius: 2.5, fontWeight: 800, boxShadow: 'none' }}
|
sx={{ px: 2, height: 32, borderRadius: 1.5, fontWeight: 700, fontSize: '0.75rem', boxShadow: 'none' }}
|
||||||
>
|
>
|
||||||
HOSTS DOSYASINA EKLE
|
HOSTS DOSYASINA EKLE
|
||||||
</Button>
|
</Button>
|
||||||
@@ -305,17 +309,17 @@ export default function ProjectDetails({
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
sx={{
|
sx={{
|
||||||
width: 48,
|
width: 40,
|
||||||
height: 48,
|
height: 40,
|
||||||
bgcolor: 'rgba(255, 68, 68, 0.05)',
|
bgcolor: 'rgba(255, 68, 68, 0.05)',
|
||||||
color: 'error.main',
|
color: 'error.main',
|
||||||
border: '1px solid rgba(255, 68, 68, 0.1)',
|
border: '1px solid rgba(255, 68, 68, 0.1)',
|
||||||
borderRadius: 3,
|
borderRadius: 1.5,
|
||||||
'&:hover': { bgcolor: 'error.main', color: '#fff', transform: 'scale(1.05) rotate(5deg)', boxShadow: '0 4px 12px rgba(255, 68, 68, 0.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'
|
transition: 'all 0.2s'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon sx={{ fontSize: '1.25rem' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -324,9 +328,9 @@ export default function ProjectDetails({
|
|||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'rgba(255,255,255,0.05)', px: 4, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
<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')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120 }} />
|
<Tab label={t('projects.overview')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120, fontSize: '0.8rem' }} />
|
||||||
<Tab label={t('projects.database')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120 }} />
|
<Tab label={t('projects.database')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120, fontSize: '0.8rem' }} />
|
||||||
<Tab label={t('projects.server_settings')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120 }} />
|
<Tab label={t('projects.server_settings')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120, fontSize: '0.8rem' }} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -338,8 +342,8 @@ export default function ProjectDetails({
|
|||||||
{/* Editable Form Side */}
|
{/* Editable Form Side */}
|
||||||
<Grid item xs={12} md={7}>
|
<Grid item xs={12} md={7}>
|
||||||
<Paper sx={{ p: 3, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
|
<Paper sx={{ p: 3, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
<Typography variant="h6" sx={{ mb: 3, fontWeight: 800, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
<Typography variant="h6" sx={{ mb: 3, fontWeight: 800, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1.5, fontSize: '1rem' }}>
|
||||||
<SettingsIcon /> {t('projects.edit_details', 'Proje Detaylarını Düzenle')}
|
<SettingsIcon sx={{ fontSize: '1.25rem' }} /> {t('projects.edit_details', 'Proje Detaylarını Düzenle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
@@ -347,60 +351,62 @@ export default function ProjectDetails({
|
|||||||
label={t('projects.site_name')}
|
label={t('projects.site_name')}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="filled"
|
variant="filled"
|
||||||
|
size="small"
|
||||||
value={editForm.name}
|
value={editForm.name}
|
||||||
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
||||||
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' }, '& .MuiInputLabel-root': { fontSize: '0.85rem' }, '& .MuiInputBase-input': { fontSize: '0.85rem' } }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label={t('projects.site_domain')}
|
label={t('projects.site_domain')}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="filled"
|
variant="filled"
|
||||||
|
size="small"
|
||||||
value={editForm.host}
|
value={editForm.host}
|
||||||
onChange={(e) => setEditForm({ ...editForm, host: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, host: e.target.value })}
|
||||||
helperText={t('projects.host_helper', 'Domain adı (örn: myproject)')}
|
helperText={t('projects.host_helper', 'Domain adı (örn: myproject)')}
|
||||||
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' }, '& .MuiInputLabel-root': { fontSize: '0.85rem' }, '& .MuiInputBase-input': { fontSize: '0.85rem' }, '& .MuiFormHelperText-root': { fontSize: '0.7rem' } }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
<FormControl fullWidth variant="filled">
|
<FormControl fullWidth variant="filled" size="small">
|
||||||
<InputLabel>{t('projects.web_server')}</InputLabel>
|
<InputLabel sx={{ fontSize: '0.85rem' }}>{t('projects.web_server')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={editForm.serverType}
|
value={editForm.serverType}
|
||||||
onChange={(e) => setEditForm({ ...editForm, serverType: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, serverType: e.target.value })}
|
||||||
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' }, fontSize: '0.85rem' }}
|
||||||
>
|
>
|
||||||
<MenuItem value="nginx">Nginx</MenuItem>
|
<MenuItem value="nginx" sx={{ fontSize: '0.85rem' }}>Nginx</MenuItem>
|
||||||
<MenuItem value="apache">Apache</MenuItem>
|
<MenuItem value="apache" sx={{ fontSize: '0.85rem' }}>Apache</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
<FormControl fullWidth variant="filled">
|
<FormControl fullWidth variant="filled" size="small">
|
||||||
<InputLabel>{t('projects.php_version')}</InputLabel>
|
<InputLabel sx={{ fontSize: '0.85rem' }}>{t('projects.php_version')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={editForm.phpVersion}
|
value={editForm.phpVersion}
|
||||||
onChange={(e) => setEditForm({ ...editForm, phpVersion: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, phpVersion: e.target.value })}
|
||||||
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' }, fontSize: '0.85rem' }}
|
||||||
>
|
>
|
||||||
{installedPhp.map(v => (
|
{installedPhp.map(v => (
|
||||||
<MenuItem key={v.version} value={v.version}>PHP {v.version}</MenuItem>
|
<MenuItem key={v.version} value={v.version} sx={{ fontSize: '0.85rem' }}>PHP {v.version}</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<FormControl fullWidth variant="filled">
|
<FormControl fullWidth variant="filled" size="small">
|
||||||
<InputLabel>{t('projects.mariadb_version')}</InputLabel>
|
<InputLabel sx={{ fontSize: '0.85rem' }}>{t('projects.mariadb_version')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={editForm.mariaDbVersion}
|
value={editForm.mariaDbVersion}
|
||||||
onChange={(e) => setEditForm({ ...editForm, mariaDbVersion: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, mariaDbVersion: e.target.value })}
|
||||||
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
|
sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' }, fontSize: '0.85rem' }}
|
||||||
>
|
>
|
||||||
{installedMariaDb.map(v => (
|
{installedMariaDb.map(v => (
|
||||||
<MenuItem key={v.version} value={v.version}>MariaDB {v.version}</MenuItem>
|
<MenuItem key={v.version} value={v.version} sx={{ fontSize: '0.85rem' }}>MariaDB {v.version}</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -408,11 +414,11 @@ export default function ProjectDetails({
|
|||||||
<Box sx={{ pt: 1, display: 'flex', justifyContent: 'flex-start' }}>
|
<Box sx={{ pt: 1, display: 'flex', justifyContent: 'flex-start' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="small"
|
||||||
startIcon={isSaving ? <CircularProgress size={20} color="inherit" /> : <SaveIcon />}
|
startIcon={isSaving ? <CircularProgress size={16} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
onClick={handleProjectUpdate}
|
onClick={handleProjectUpdate}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
sx={{ px: 6, py: 1.5, borderRadius: 3, fontWeight: 800, fontSize: '1rem', color: 'primary.main', border: '1px solid rgba(33, 150, 243, 0.5)', '&:hover': { bgcolor: 'rgba(33, 150, 243, 0.05)', borderColor: 'primary.main' } }}
|
sx={{ px: 4, py: 1, borderRadius: 2, fontWeight: 700, fontSize: '0.75rem', color: 'primary.main', border: '1px solid rgba(33, 150, 243, 0.5)', '&:hover': { bgcolor: 'rgba(33, 150, 243, 0.05)', borderColor: 'primary.main' } }}
|
||||||
>
|
>
|
||||||
{t('common.save').toUpperCase()}
|
{t('common.save').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -424,8 +430,8 @@ export default function ProjectDetails({
|
|||||||
{/* Status Monitor Side */}
|
{/* Status Monitor Side */}
|
||||||
<Grid item xs={12} md={5}>
|
<Grid item xs={12} md={5}>
|
||||||
<Paper sx={{ p: 3, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)', height: '100%' }}>
|
<Paper sx={{ p: 3, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)', height: '100%' }}>
|
||||||
<Typography variant="subtitle1" sx={{ mb: 4, fontWeight: 800, display: 'flex', alignItems: 'center', gap: 1.5, color: 'rgba(255,255,255,0.6)' }}>
|
<Typography variant="subtitle1" sx={{ mb: 4, fontWeight: 800, display: 'flex', alignItems: 'center', gap: 1.5, color: 'rgba(255,255,255,0.6)', fontSize: '0.9rem' }}>
|
||||||
<StatusIcon sx={{ color: 'secondary.main' }} /> {t('dashboard.status')}
|
<StatusIcon sx={{ color: 'secondary.main', fontSize: '1.25rem' }} /> {t('dashboard.status')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ mb: 4 }}>
|
<Box sx={{ mb: 4 }}>
|
||||||
@@ -439,10 +445,10 @@ export default function ProjectDetails({
|
|||||||
<Divider sx={{ mb: 3, opacity: 0.1 }} />
|
<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)' }}>
|
<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 }}>
|
<Typography variant="caption" sx={{ color: 'primary.light', fontWeight: 800, display: 'flex', alignItems: 'center', gap: 1, mb: 1.5, textTransform: 'uppercase', letterSpacing: 1, fontSize: '0.7rem' }}>
|
||||||
<CodeIcon fontSize="small" /> System Info
|
<CodeIcon fontSize="small" /> System Info
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ opacity: 0.5, lineHeight: 1.6 }}>
|
<Typography variant="body2" sx={{ opacity: 0.5, lineHeight: 1.6, fontSize: '0.75rem' }}>
|
||||||
{t('projects.manual_edit_help', 'Bu sayfadaki değişiklikler kaydedildiğinde servis yapılandırmaları ve hosts dosyası otomatik olarak yenilenecektir.')}
|
{t('projects.manual_edit_help', 'Bu sayfadaki değişiklikler kaydedildiğinde servis yapılandırmaları ve hosts dosyası otomatik olarak yenilenecektir.')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -458,22 +464,22 @@ export default function ProjectDetails({
|
|||||||
<Paper sx={{ p: 4, borderRadius: 4, bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
|
<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', alignItems: 'center', mb: 4 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'secondary.main' }}>
|
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'secondary.main', width: 48, height: 48 }}>
|
||||||
<DbIcon />
|
<DbIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="overline" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 800, letterSpacing: 1 }}>{t('projects.mariadb_version')}</Typography>
|
<Typography variant="overline" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 800, letterSpacing: 1, fontSize: '0.65rem' }}>{t('projects.mariadb_version')}</Typography>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>MariaDB {p.mariadbVersion}</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 700, fontSize: '1rem' }}>MariaDB {p.mariaDbVersion}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="medium"
|
size="small"
|
||||||
startIcon={<OpenIcon />}
|
startIcon={<OpenIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
onClick={onManageDb}
|
onClick={onManageDb}
|
||||||
sx={{ borderRadius: 2.5, px: 3, height: 44, fontWeight: 700 }}
|
sx={{ borderRadius: 1.5, px: 2, height: 32, fontWeight: 700, fontSize: '0.75rem' }}
|
||||||
>
|
>
|
||||||
{t('mariadb_manager.title')}
|
{t('mariadb_manager.title').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -487,8 +493,8 @@ export default function ProjectDetails({
|
|||||||
{ label: 'Password', value: '(no password)', italic: true }
|
{ label: 'Password', value: '(no password)', italic: true }
|
||||||
].map((item, idx) => (
|
].map((item, idx) => (
|
||||||
<Grid item xs={6} key={idx}>
|
<Grid item xs={6} key={idx}>
|
||||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 700, display: 'block', mb: 0.5 }}>{item.label.toUpperCase()}</Typography>
|
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.3)', fontWeight: 700, display: 'block', mb: 0.5, fontSize: '0.65rem' }}>{item.label.toUpperCase()}</Typography>
|
||||||
<Typography variant="body1" sx={{ fontWeight: 600, color: item.italic ? 'rgba(255,255,255,0.3)' : '#fff', fontStyle: item.italic ? 'italic' : 'normal' }}>
|
<Typography variant="body1" sx={{ fontWeight: 600, color: item.italic ? 'rgba(255,255,255,0.3)' : '#fff', fontStyle: item.italic ? 'italic' : 'normal', fontSize: '0.85rem' }}>
|
||||||
{item.value}
|
{item.value}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -501,19 +507,19 @@ export default function ProjectDetails({
|
|||||||
|
|
||||||
{projectDetailTab === 2 && (
|
{projectDetailTab === 2 && (
|
||||||
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
<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={{ 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={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
<ServerIcon color="primary" />
|
<ServerIcon color="primary" sx={{ fontSize: '1.25rem' }} />
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 800, textTransform: 'uppercase', letterSpacing: 1 }}>
|
<Typography variant="subtitle2" sx={{ fontWeight: 800, textTransform: 'uppercase', letterSpacing: 1, fontSize: '0.75rem' }}>
|
||||||
{p.serverType} {t('projects.server_settings')}
|
{p.serverType} {t('projects.server_settings')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', gap: 1.5 }}>
|
<Box sx={{ display: 'flex', gap: 1.5 }}>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
startIcon={<TuningIcon />}
|
startIcon={<TuningIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
onClick={loadConfig}
|
onClick={loadConfig}
|
||||||
sx={{ color: 'rgba(255,255,255,0.5)', borderRadius: 2 }}
|
sx={{ color: 'rgba(255,255,255,0.5)', borderRadius: 1.5, fontSize: '0.75rem' }}
|
||||||
>
|
>
|
||||||
{t('common.refresh')}
|
{t('common.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -521,24 +527,24 @@ export default function ProjectDetails({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
color="primary"
|
||||||
startIcon={isConfigSaving ? <CircularProgress size={16} color="inherit" /> : <SaveIcon />}
|
startIcon={isConfigSaving ? <CircularProgress size={14} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
disabled={isConfigSaving || isConfigLoading}
|
disabled={isConfigSaving || isConfigLoading}
|
||||||
onClick={handleSaveConfig}
|
onClick={handleSaveConfig}
|
||||||
sx={{ px: 4, borderRadius: 2, fontWeight: 800 }}
|
sx={{ px: 3, height: 32, borderRadius: 1.5, fontWeight: 800, fontSize: '0.75rem' }}
|
||||||
>
|
>
|
||||||
{t('common.save').toUpperCase()}
|
{t('common.save').toUpperCase()}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<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 }}>
|
<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)', fontWeight: 600, '& .MuiAlert-message': { fontSize: '0.75rem' } }}>
|
||||||
{t('projects.manual_edit_warning')}
|
{t('projects.manual_edit_warning')}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1, position: 'relative', overflow: 'hidden' }}>
|
<Box sx={{ flexGrow: 1, position: 'relative', overflow: 'hidden' }}>
|
||||||
{isConfigLoading ? (
|
{isConfigLoading ? (
|
||||||
<Box sx={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', bgcolor: 'rgba(0,0,0,0.5)', zIndex: 10 }}>
|
<Box sx={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', bgcolor: 'rgba(0,0,0,0.5)', zIndex: 10 }}>
|
||||||
<CircularProgress />
|
<CircularProgress size={32} />
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
<ProjectConfigEditor
|
<ProjectConfigEditor
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ interface ProjectManagerProps {
|
|||||||
onOpenVSCode: (path: string) => void;
|
onOpenVSCode: (path: string) => void;
|
||||||
onAddHost: (hostname: string) => void;
|
onAddHost: (hostname: string) => void;
|
||||||
onManageDb: () => void;
|
onManageDb: () => void;
|
||||||
|
onRefreshProjects: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProjectManager({
|
export default function ProjectManager({
|
||||||
@@ -47,7 +48,8 @@ export default function ProjectManager({
|
|||||||
onOpenFolder,
|
onOpenFolder,
|
||||||
onOpenVSCode,
|
onOpenVSCode,
|
||||||
onAddHost,
|
onAddHost,
|
||||||
onManageDb
|
onManageDb,
|
||||||
|
onRefreshProjects
|
||||||
}: ProjectManagerProps) {
|
}: ProjectManagerProps) {
|
||||||
|
|
||||||
// Defensive check: find selected project
|
// Defensive check: find selected project
|
||||||
@@ -89,6 +91,7 @@ export default function ProjectManager({
|
|||||||
onOpenVSCode={() => onOpenVSCode(p.path)}
|
onOpenVSCode={() => onOpenVSCode(p.path)}
|
||||||
onAddHost={onAddHost}
|
onAddHost={onAddHost}
|
||||||
onManageDb={onManageDb}
|
onManageDb={onManageDb}
|
||||||
|
onRefreshProjects={onRefreshProjects}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,6 +60,8 @@
|
|||||||
"quick_look": "Quick Look",
|
"quick_look": "Quick Look",
|
||||||
"start_all": "Start All",
|
"start_all": "Start All",
|
||||||
"stop_all": "Stop All",
|
"stop_all": "Stop All",
|
||||||
|
"starting_all": "Starting All Services",
|
||||||
|
"stopping_all": "Stopping All Services",
|
||||||
"all_started": "All services started.",
|
"all_started": "All services started.",
|
||||||
"all_stopped": "All services stopped.",
|
"all_stopped": "All services stopped.",
|
||||||
"overview": "Overview",
|
"overview": "Overview",
|
||||||
|
|||||||
@@ -61,6 +61,8 @@
|
|||||||
"quick_look": "Hızlı Bakış",
|
"quick_look": "Hızlı Bakış",
|
||||||
"start_all": "Tümünü Başlat",
|
"start_all": "Tümünü Başlat",
|
||||||
"stop_all": "Tümünü Durdur",
|
"stop_all": "Tümünü Durdur",
|
||||||
|
"starting_all": "Tüm Servisler Başlatılıyor",
|
||||||
|
"stopping_all": "Tüm Servisler Durduruluyor",
|
||||||
"all_started": "Tüm servisler başlatıldı.",
|
"all_started": "Tüm servisler başlatıldı.",
|
||||||
"all_stopped": "Tüm servisler durduruldu.",
|
"all_stopped": "Tüm servisler durduruldu.",
|
||||||
"overview": "Genel Bakış",
|
"overview": "Genel Bakış",
|
||||||
|
|||||||
Reference in New Issue
Block a user