Compare commits

..

10 Commits

35 changed files with 1150 additions and 513 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

+12
View File
@@ -0,0 +1,12 @@
const fs = require('fs');
const { execSync } = require('child_process');
try {
console.log('Converting icon using npx png-to-ico...');
const buffer = execSync('npx -y png-to-ico logo/icon.png');
fs.writeFileSync('build/icon.ico', buffer);
console.log('Successfully created build/icon.ico (' + buffer.length + ' bytes)');
} catch (err) {
console.error('Conversion failed:', err.message);
process.exit(1);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+106 -4
View File
@@ -21,6 +21,8 @@
"@types/prismjs": "^1.26.6", "@types/prismjs": "^1.26.6",
"axios": "^1.14.0", "axios": "^1.14.0",
"decompress": "^4.2.1", "decompress": "^4.2.1",
"electron-log": "^5.4.3",
"electron-updater": "^6.8.3",
"i18next": "^23.1.0", "i18next": "^23.1.0",
"pidusage": "^4.0.1", "pidusage": "^4.0.1",
"prismjs": "^1.30.0", "prismjs": "^1.30.0",
@@ -3376,7 +3378,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0" "license": "Python-2.0"
}, },
"node_modules/array-buffer-byte-length": { "node_modules/array-buffer-byte-length": {
@@ -5207,6 +5208,15 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/electron-log": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz",
"integrity": "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/electron-publish": { "node_modules/electron-publish": {
"version": "24.13.1", "version": "24.13.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz",
@@ -5278,6 +5288,82 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/electron-updater": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz",
"integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==",
"license": "MIT",
"dependencies": {
"builder-util-runtime": "9.5.1",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "~7.7.3",
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron-updater/node_modules/builder-util-runtime": {
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
"integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/electron-updater/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/electron-updater/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/electron-updater/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/electron-updater/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/electron-vite": { "node_modules/electron-vite": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.3.0.tgz", "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.3.0.tgz",
@@ -7451,7 +7537,6 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@@ -7556,7 +7641,6 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lazystream": { "node_modules/lazystream": {
@@ -7676,6 +7760,12 @@
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
"license": "MIT"
},
"node_modules/lodash.flatten": { "node_modules/lodash.flatten": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
@@ -7684,6 +7774,13 @@
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lodash.isplainobject": { "node_modules/lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -9120,7 +9217,6 @@
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"engines": { "engines": {
"node": ">=11.0.0" "node": ">=11.0.0"
@@ -9854,6 +9950,12 @@
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/tiny-typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
"license": "MIT"
},
"node_modules/tmp": { "node_modules/tmp": {
"version": "0.2.5", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
+50 -5
View File
@@ -17,6 +17,8 @@
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir", "build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win", "build:win": "npm run build && electron-builder --win",
"dist": "npm run build && electron-builder --win",
"publish:win": "npm run build && electron-builder --win --publish always",
"build:mac": "npm run build && electron-builder --mac", "build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux" "build:linux": "npm run build && electron-builder --linux"
}, },
@@ -33,6 +35,8 @@
"@types/prismjs": "^1.26.6", "@types/prismjs": "^1.26.6",
"axios": "^1.14.0", "axios": "^1.14.0",
"decompress": "^4.2.1", "decompress": "^4.2.1",
"electron-log": "^5.4.3",
"electron-updater": "^6.8.3",
"i18next": "^23.1.0", "i18next": "^23.1.0",
"pidusage": "^4.0.1", "pidusage": "^4.0.1",
"prismjs": "^1.30.0", "prismjs": "^1.30.0",
@@ -63,19 +67,60 @@
"vite": "^5.3.1" "vite": "^5.3.1"
}, },
"build": { "build": {
"appId": "com.multiphp", "appId": "com.truncgil.multiphp",
"productName": "Multi-PHP", "productName": "Trunçgil MultiPHP",
"publish": [
{
"provider": "github",
"owner": "truncgil",
"repo": "multiphp"
}
],
"directories": { "directories": {
"output": "dist" "output": "dist"
}, },
"files": [
"out/**/*",
"resources/**/*",
"config/**/*",
"package.json"
],
"asar": true,
"win": { "win": {
"icon": "resources/icon.ico" "icon": "build/icon.ico",
"requestedExecutionLevel": "highestAvailable",
"target": [
"nsis"
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Trunçgil MultiPHP",
"installerIcon": "build/icon.ico",
"uninstallerIcon": "build/icon.ico",
"installerHeaderIcon": "build/icon.ico",
"perMachine": true,
"multiLanguageInstaller": true,
"installerLanguages": [
"tr_TR",
"en_US",
"de_DE",
"ru_RU"
]
}, },
"mac": { "mac": {
"icon": "resources/icon.png" "category": "public.app-category.developer-tools",
"target": [
"dmg"
]
}, },
"linux": { "linux": {
"icon": "resources/icon.png" "target": [
"AppImage"
]
} }
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

+58
View File
@@ -0,0 +1,58 @@
{
"mariadb_release_catalog": {
"generated_date": "2026-03-30",
"provider": "MariaDB Foundation",
"releases": [
{
"series": "11.4 (LTS)",
"version": "11.4.2",
"status": "Stable (LTS)",
"platforms": {
"win32": {
"x64": "https://archive.mariadb.org/mariadb-11.4.2/winx64-packages/mariadb-11.4.2-winx64.zip"
},
"darwin": {
"x64": "https://archive.mariadb.org/mariadb-11.4.2/bintar-macosx-x86_64/mariadb-11.4.2-macosx-x86_64.tar.gz",
"arm64": "https://archive.mariadb.org/mariadb-11.4.2/bintar-macosx-arm64/mariadb-11.4.2-macosx-arm64.tar.gz"
},
"linux": {
"x64": "https://archive.mariadb.org/mariadb-11.4.2/bintar-linux-systemd-x86_64/mariadb-11.4.2-linux-systemd-x86_64.tar.gz"
}
}
},
{
"series": "10.11 (LTS)",
"version": "10.11.8",
"status": "Stable (LTS)",
"platforms": {
"win32": {
"x64": "https://archive.mariadb.org/mariadb-10.11.8/winx64-packages/mariadb-10.11.8-winx64.zip"
},
"darwin": {
"x64": "https://archive.mariadb.org/mariadb-10.11.8/bintar-macosx-x86_64/mariadb-10.11.8-macosx-x86_64.tar.gz"
},
"linux": {
"x64": "https://archive.mariadb.org/mariadb-10.11.8/bintar-linux-systemd-x86_64/mariadb-10.11.8-linux-systemd-x86_64.tar.gz"
}
}
},
{
"series": "10.4 (LTS)",
"version": "10.4.34",
"status": "Old Stable (End of Life)",
"compatibility_note": "PHP 5.6 ve eski mysql/mysqli eklentileri için en uyumlu sürümdür.",
"platforms": {
"win32": {
"x64": "https://archive.mariadb.org/mariadb-10.4.34/winx64-packages/mariadb-10.4.34-winx64.zip"
},
"darwin": {
"x64": "https://archive.mariadb.org/mariadb-10.4.34/bintar-macosx-x86_64/mariadb-10.4.34-macosx-x86_64.tar.gz"
},
"linux": {
"x64": "https://archive.mariadb.org/mariadb-10.4.34/bintar-linux-systemd-x86_64/mariadb-10.4.34-linux-systemd-x86_64.tar.gz"
}
}
}
]
}
}
+5
View File
@@ -3,6 +3,7 @@ import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import { registerIpcHandlers } from './ipc' import { registerIpcHandlers } from './ipc'
import { cliAliasService } from './services/CliAliasService' import { cliAliasService } from './services/CliAliasService'
import { updateService } from './services/UpdateService'
let tray: Tray | null = null let tray: Tray | null = null
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
@@ -86,6 +87,10 @@ app.whenReady().then(() => {
registerIpcHandlers() registerIpcHandlers()
createWindow() createWindow()
if (mainWindow) {
updateService.setMainWindow(mainWindow)
updateService.init()
}
createTray() createTray()
// Generate CLI aliases on start // Generate CLI aliases on start
+23 -4
View File
@@ -949,6 +949,18 @@ export function registerIpcHandlers(): void {
return await shell.openPath(pathStr) return await shell.openPath(pathStr)
}) })
ipcMain.handle('www:open', async () => {
try {
const wwwPath = path.join(configService.getBasePath(), 'www')
if (!fs.existsSync(wwwPath)) fs.mkdirSync(wwwPath, { recursive: true })
await shell.openPath(wwwPath)
return { success: true }
} catch (error: any) {
console.error('Failed to open www path:', error)
return { success: false, message: error.message }
}
})
ipcMain.handle('open-vscode', async (_event, pathStr: string) => { ipcMain.handle('open-vscode', async (_event, pathStr: string) => {
return new Promise((resolve) => { return new Promise((resolve) => {
exec(`code "${pathStr}"`, (error) => { exec(`code "${pathStr}"`, (error) => {
@@ -1444,13 +1456,13 @@ export function registerIpcHandlers(): void {
ipcMain.handle('docs:open-user-guide', async (_event, locale: string) => { ipcMain.handle('docs:open-user-guide', async (_event, locale: string) => {
try { try {
const guidePath = path.join(process.cwd(), 'docs', 'user-guide', locale, 'index.md') const guidePath = path.join(configService.getAppPath(), 'docs', 'user-guide', locale, 'index.md')
if (fs.existsSync(guidePath)) { if (fs.existsSync(guidePath)) {
await shell.openPath(guidePath) await shell.openPath(guidePath)
return { success: true } return { success: true }
} else { } else {
// Fallback to directory or English if specific locale file doesn't exist // Fallback to directory or English if specific locale file doesn't exist
const langDir = path.join(process.cwd(), 'docs', 'user-guide', locale) const langDir = path.join(configService.getAppPath(), 'docs', 'user-guide', locale)
if (fs.existsSync(langDir)) { if (fs.existsSync(langDir)) {
await shell.openPath(langDir) await shell.openPath(langDir)
return { success: true } return { success: true }
@@ -1465,7 +1477,7 @@ export function registerIpcHandlers(): void {
ipcMain.handle('docs:list-topics', async (_event, locale: string) => { ipcMain.handle('docs:list-topics', async (_event, locale: string) => {
try { try {
const localeDir = path.join(process.cwd(), 'docs', 'user-guide', locale) const localeDir = path.join(configService.getAppPath(), 'docs', 'user-guide', locale)
if (!fs.existsSync(localeDir)) return [] if (!fs.existsSync(localeDir)) return []
const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.md')) const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.md'))
@@ -1493,7 +1505,7 @@ export function registerIpcHandlers(): void {
ipcMain.handle('docs:read-topic', async (_event, { locale, file }) => { ipcMain.handle('docs:read-topic', async (_event, { locale, file }) => {
try { try {
const filePath = path.join(process.cwd(), 'docs', 'user-guide', locale, file) const filePath = path.join(configService.getAppPath(), 'docs', 'user-guide', locale, file)
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
return fs.readFileSync(filePath, 'utf8') return fs.readFileSync(filePath, 'utf8')
} }
@@ -1503,4 +1515,11 @@ export function registerIpcHandlers(): void {
return '' return ''
} }
}) })
ipcMain.handle('app:get-permissions-status', () => {
return {
hasError: configService.hasPermissionError,
path: configService.failedPath
}
})
} }
+82 -7
View File
@@ -21,26 +21,101 @@ export class ConfigService {
mariaDbTuning: {} as Record<string, string> mariaDbTuning: {} as Record<string, string>
} }
public hasPermissionError = false
public failedPath = ''
constructor() { constructor() {
this.templateDir = path.join(app.getAppPath(), 'config') this.templateDir = path.join(app.getAppPath(), 'config')
const base = this.getBasePath() const base = this.getDataPath()
this.configDir = path.join(base, 'generated_configs') this.configDir = path.join(base, 'generated_configs')
this.settingsPath = path.join(base, 'settings.json') this.settingsPath = path.join(base, 'settings.json')
console.log(`[ConfigService] Base data path: ${base}`)
console.log(`[ConfigService] Config dir: ${this.configDir}`)
// Ensure directories exist
if (!fs.existsSync(base)) {
try {
fs.mkdirSync(base, { recursive: true })
} catch (e: any) {
if (e.code === 'EPERM' || e.code === 'EACCES') {
this.hasPermissionError = true
this.failedPath = base
}
console.error(`Failed to create base directory (${base}):`, e)
}
}
if (!fs.existsSync(this.configDir)) { if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, { recursive: true }) try {
fs.mkdirSync(this.configDir, { recursive: true })
} catch (e: any) {
if (e.code === 'EPERM' || e.code === 'EACCES') {
this.hasPermissionError = true
this.failedPath = this.configDir
}
console.error(`Failed to create config directory (${this.configDir}):`, e)
}
} }
if (!fs.existsSync(this.settingsPath)) { if (!fs.existsSync(this.settingsPath)) {
fs.writeFileSync(this.settingsPath, JSON.stringify(this.defaultSettings, null, 2)) try {
fs.writeFileSync(this.settingsPath, JSON.stringify(this.defaultSettings, null, 2))
} catch (e: any) {
if (e.code === 'EPERM' || e.code === 'EACCES') {
this.hasPermissionError = true
this.failedPath = this.settingsPath
}
console.error('Failed to create settings file:', e)
}
}
}
/**
* Returns the base path for writable data (configs, binaries, logs).
* If packaged, checks for write access to the executable directory.
* If not writable (e.g. Program Files), falls back to app.getPath('userData').
*/
getDataPath(): string {
const exeDir = path.dirname(app.getPath('exe'))
const isAsar = app.getAppPath().includes('app.asar')
// If not packaged and not running from asar, we are in development
if (!app.isPackaged && !isAsar) {
return process.cwd()
}
// We are in production/packaged mode.
// Priority 1: Do not even try to use Program Files if we have alternatives.
const winProgramFiles = (process.env.ProgramFiles || 'C:\\Program Files').toLowerCase()
const winProgramFilesX86 = (process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)').toLowerCase()
const currentExeDir = exeDir.toLowerCase()
if (currentExeDir.startsWith(winProgramFiles) || currentExeDir.startsWith(winProgramFilesX86)) {
return app.getPath('userData')
}
// Priority 2: Check for actual write access (Portable Mode check)
try {
const testFile = path.join(exeDir, `.write_test_${Date.now()}`)
fs.writeFileSync(testFile, 'test')
fs.unlinkSync(testFile)
return exeDir // Writable (Portable Mode)
} catch (e) {
// Not writable, use AppData
return app.getPath('userData')
} }
} }
getBasePath(): string { getBasePath(): string {
if (app.isPackaged) { return this.getDataPath()
return path.dirname(app.getPath('exe')) }
}
return process.cwd() /**
* Returns the read-only application path (where templates, docs, etc. are located).
*/
getAppPath(): string {
return app.getAppPath()
} }
async generateConfig(templateName: string, targetName: string, variables: Record<string, string>): Promise<string> { async generateConfig(templateName: string, targetName: string, variables: Record<string, string>): Promise<string> {
+13 -4
View File
@@ -22,10 +22,19 @@ export class MariaDbManagerService {
constructor() { constructor() {
this.binDir = path.join(configService.getBasePath(), 'bin') this.binDir = path.join(configService.getBasePath(), 'bin')
this.catalogPath = path.join(app.getAppPath(), 'src/main/resources/mariadb_catalog.json')
if (!fs.existsSync(this.catalogPath)) { // Path resolution for both development and production (packaged)
this.catalogPath = path.join(process.cwd(), 'src/main/resources/mariadb_catalog.json') const appPath = app.getAppPath()
} const possiblePaths = [
path.join(appPath, 'resources/mariadb_catalog.json'),
path.join(appPath, '../../resources/mariadb_catalog.json'),
path.join(process.resourcesPath, 'mariadb_catalog.json'),
path.join(process.resourcesPath, 'resources/mariadb_catalog.json'),
path.join(process.cwd(), 'resources/mariadb_catalog.json')
]
this.catalogPath = possiblePaths.find(p => fs.existsSync(p)) || possiblePaths[0]
this.init() this.init()
} }
-2
View File
@@ -191,7 +191,6 @@ export class PhpManagerService {
const content = fs.readFileSync(iniPath, 'utf8') const content = fs.readFileSync(iniPath, 'utf8')
const lines = content.split(/\r?\n/) const lines = content.split(/\r?\n/)
const essentialExts = ['openssl', 'curl', 'mbstring', 'fileinfo', 'zip', 'gd', 'pdo_mysql', 'mysqli'] const essentialExts = ['openssl', 'curl', 'mbstring', 'fileinfo', 'zip', 'gd', 'pdo_mysql', 'mysqli']
let changed = false
for (const ext of extensions) { for (const ext of extensions) {
// Check if already enabled // Check if already enabled
@@ -202,7 +201,6 @@ export class PhpManagerService {
if (!ext.enabled && essentialExts.includes(ext.name.toLowerCase())) { if (!ext.enabled && essentialExts.includes(ext.name.toLowerCase())) {
await this.toggleExtension(version, ext.name, true) await this.toggleExtension(version, ext.name, true)
ext.enabled = true ext.enabled = true
changed = true
} }
} }
} }
+80
View File
@@ -0,0 +1,80 @@
import { autoUpdater } from 'electron-updater'
import { ipcMain, BrowserWindow } from 'electron'
import log from 'electron-log'
// Configure logging
autoUpdater.logger = log
// @ts-ignore
autoUpdater.logger.transports.file.level = 'info'
export class UpdateService {
private mainWindow: BrowserWindow | null = null
constructor() {
this.setupHandlers()
}
setMainWindow(window: BrowserWindow) {
this.mainWindow = window
}
private setupHandlers() {
autoUpdater.on('checking-for-update', () => {
this.sendToRenderer('update-status', 'checking')
})
autoUpdater.on('update-available', (info) => {
this.sendToRenderer('update-status', 'available', info)
})
autoUpdater.on('update-not-available', (info) => {
this.sendToRenderer('update-status', 'not-available', info)
})
autoUpdater.on('error', (err) => {
let message = err.message
if (message.includes('No published versions on GitHub')) {
message = 'Henüz GitHub üzerinde yayınlanmış bir sürüm bulunmamaktadır. (No published versions on GitHub)'
}
this.sendToRenderer('update-status', 'error', message)
})
autoUpdater.on('download-progress', (progressObj) => {
this.sendToRenderer('update-progress', progressObj)
})
autoUpdater.on('update-downloaded', (info) => {
this.sendToRenderer('update-status', 'downloaded', info)
})
// IPC handlers for manual control
ipcMain.handle('check-for-updates', async () => {
try {
return await autoUpdater.checkForUpdatesAndNotify()
} catch (error: any) {
return { error: error.message }
}
})
ipcMain.handle('download-update', async () => {
return await autoUpdater.downloadUpdate()
})
ipcMain.handle('quit-and-install', () => {
autoUpdater.quitAndInstall()
})
}
private sendToRenderer(channel: string, ...args: any[]) {
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
this.mainWindow.webContents.send(channel, ...args)
}
}
init() {
// You can enable semi-automatic updates here if desired
// autoUpdater.checkForUpdatesAndNotify()
}
}
export const updateService = new UpdateService()
+14
View File
@@ -44,6 +44,20 @@ const api = {
electronAPI.ipcRenderer.on('mariadb:export:log', listener) electronAPI.ipcRenderer.on('mariadb:export:log', listener)
return () => electronAPI.ipcRenderer.removeListener('mariadb:export:log', listener) return () => electronAPI.ipcRenderer.removeListener('mariadb:export:log', listener)
}, },
checkForUpdates: () => electronAPI.ipcRenderer.invoke('check-for-updates'),
downloadUpdate: () => electronAPI.ipcRenderer.invoke('download-update'),
quitAndInstall: () => electronAPI.ipcRenderer.invoke('quit-and-install'),
onUpdateStatus: (callback: any) => {
const listener = (_event, status: string, info: any) => callback(status, info)
electronAPI.ipcRenderer.on('update-status', listener)
return () => electronAPI.ipcRenderer.removeListener('update-status', listener)
},
onUpdateProgress: (callback: any) => {
const listener = (_event, progress: any) => callback(progress)
electronAPI.ipcRenderer.on('update-progress', listener)
return () => electronAPI.ipcRenderer.removeListener('update-progress', listener)
},
getPermissionsStatus: () => electronAPI.ipcRenderer.invoke('app:get-permissions-status'),
invoke: (channel: string, ...args: any[]) => electronAPI.ipcRenderer.invoke(channel, ...args) invoke: (channel: string, ...args: any[]) => electronAPI.ipcRenderer.invoke(channel, ...args)
} }
+270 -138
View File
@@ -160,6 +160,10 @@ function App(): JSX.Element {
severity: 'success' severity: 'success'
}) })
const [updateStatus, setUpdateStatus] = useState<string>('idle')
const [updateInfo, setUpdateInfo] = useState<any>(null)
const [updateProgress, setUpdateProgress] = useState<any>(null)
// Log States // Log States
const [logDialogOpen, setLogDialogOpen] = useState(false) const [logDialogOpen, setLogDialogOpen] = useState(false)
const [selectedService] = useState('') const [selectedService] = useState('')
@@ -314,6 +318,23 @@ function App(): JSX.Element {
} }
} }
const checkPermissions = async () => {
if (window.api && window.api.getPermissionsStatus) {
const status = await window.api.getPermissionsStatus()
if (status.hasError) {
Swal.fire({
title: t('common.warning'),
text: t('common.admin_required'),
icon: 'warning',
confirmButtonText: t('common.ok'),
confirmButtonColor: '#3085d6',
background: '#1a1d21',
color: '#fff'
})
}
}
}
fetchStatus() fetchStatus()
fetchProjects() fetchProjects()
fetchPhpVersions() fetchPhpVersions()
@@ -323,6 +344,16 @@ function App(): JSX.Element {
fetchSettings() fetchSettings()
fetchCliConfig() fetchCliConfig()
fetchPmaStatus() fetchPmaStatus()
checkPermissions()
const removeUpdateStatusListener = window.api.onUpdateStatus ? window.api.onUpdateStatus((st: string, info: any) => {
setUpdateStatus(st)
if (info) setUpdateInfo(info)
}) : null
const removeUpdateProgressListener = window.api.onUpdateProgress ? window.api.onUpdateProgress((p: any) => {
setUpdateProgress(p)
}) : null
const removePhpListener = window.api.onBinaryProgress ? window.api.onBinaryProgress((data) => { const removePhpListener = window.api.onBinaryProgress ? window.api.onBinaryProgress((data) => {
if (!data) return if (!data) return
@@ -379,6 +410,8 @@ function App(): JSX.Element {
if (typeof removeNginxListener === 'function') removeNginxListener() if (typeof removeNginxListener === 'function') removeNginxListener()
if (typeof removeApacheListener === 'function') removeApacheListener() if (typeof removeApacheListener === 'function') removeApacheListener()
if (typeof removePmaListener === 'function') removePmaListener() if (typeof removePmaListener === 'function') removePmaListener()
if (typeof removeUpdateStatusListener === 'function') removeUpdateStatusListener()
if (typeof removeUpdateProgressListener === 'function') removeUpdateProgressListener()
} }
}, [logDialogOpen, selectedService]) }, [logDialogOpen, selectedService])
@@ -472,27 +505,27 @@ function App(): JSX.Element {
const html = ` const html = `
<div class="swal-service-list"> <div class="swal-service-list">
${services.map(srv => { ${services.map(srv => {
const label = getDisplayLabel(srv); const label = getDisplayLabel(srv);
const res = results[srv]; const res = results[srv];
let iconClass = 'swal-status-icon'; let iconClass = 'swal-status-icon';
let noteClass = 'swal-status-note'; let noteClass = 'swal-status-note';
let note = operation === 'start' ? t('common.ready') : t('common.running'); let note = operation === 'start' ? t('common.ready') : t('common.running');
if (res?.success) { if (res?.success) {
iconClass += ' success'; iconClass += ' success';
noteClass += ' success'; noteClass += ' success';
note = operation === 'start' ? t('common.started') : t('common.stopped'); note = operation === 'start' ? t('common.started') : t('common.stopped');
} else if (res?.error) { } else if (res?.error) {
iconClass += ' error'; iconClass += ' error';
noteClass += ' error'; noteClass += ' error';
note = t('common.error'); note = t('common.error');
} else if (srv === currentSrv) { } else if (srv === currentSrv) {
iconClass += ' loading swal2-loading-dots'; iconClass += ' loading swal2-loading-dots';
noteClass += ' loading'; noteClass += ' loading';
note = operation === 'start' ? t('common.starting') : t('common.stopping'); note = operation === 'start' ? t('common.starting') : t('common.stopping');
} }
return ` return `
<div class="swal-service-item"> <div class="swal-service-item">
<span class="swal-service-label"> <span class="swal-service-label">
<span class="${iconClass}">●</span> ${label} <span class="${iconClass}">●</span> ${label}
@@ -500,7 +533,7 @@ function App(): JSX.Element {
<span class="${noteClass}">${note.toUpperCase()}</span> <span class="${noteClass}">${note.toUpperCase()}</span>
</div> </div>
`; `;
}).join('')} }).join('')}
</div> </div>
`; `;
Swal.update({ html }); Swal.update({ html });
@@ -523,17 +556,17 @@ function App(): JSX.Element {
updateSwal(srv); updateSwal(srv);
try { try {
const res = operation === 'start' ? await window.api.startService(srv) : await window.api.stopService(srv); const res = operation === 'start' ? await window.api.startService(srv) : await window.api.stopService(srv);
if (res.success) { if (res.success) {
// Poll for status confirmation // Poll for status confirmation
let attempts = 0; let attempts = 0;
const maxAttempts = 15; // ~7.5 seconds const maxAttempts = 15; // ~7.5 seconds
let confirmed = false; let confirmed = false;
while (attempts < maxAttempts) { while (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise(resolve => setTimeout(resolve, 500));
const statusRes = await window.api.invoke('service:status', srv); const statusRes = await window.api.invoke('service:status', srv);
if (operation === 'start' && statusRes === 'running') { if (operation === 'start' && statusRes === 'running') {
confirmed = true; confirmed = true;
break; break;
@@ -545,7 +578,7 @@ function App(): JSX.Element {
attempts++; attempts++;
} }
results[srv] = { success: confirmed, error: !confirmed ? t('common.timeout') : null }; results[srv] = { success: confirmed, error: !confirmed ? t('common.timeout') : null };
} else { } else {
results[srv] = { success: false, error: res.message }; results[srv] = { success: false, error: res.message };
@@ -558,7 +591,7 @@ function App(): JSX.Element {
const anyError = Object.values(results).some(r => r.error); const anyError = Object.values(results).some(r => r.error);
Swal.update({ Swal.update({
icon: anyError ? 'warning' : 'success', icon: anyError ? 'warning' : 'success',
showConfirmButton: true, showConfirmButton: true,
@@ -571,7 +604,7 @@ function App(): JSX.Element {
if (Swal.isVisible()) Swal.close(); if (Swal.isVisible()) Swal.close();
}, 3000); }, 3000);
} }
fetchStatus(); fetchStatus();
}; };
@@ -624,10 +657,10 @@ function App(): JSX.Element {
const result = await window.api.invoke('nginx:config:test') const result = await window.api.invoke('nginx:config:test')
const errorInfo = !result.success ? parseNginxError(result.message) : null const errorInfo = !result.success ? parseNginxError(result.message) : null
const formattedMessage = result.message const formattedMessage = result.message
.replace(/\[emerg\]/g, '<span style="color: #ff5252; font-weight: bold;">[emerg]</span>') .replace(/\[emerg\]/g, '<span class="text-error font-bold">[emerg]</span>')
.replace(/\[warn\]/g, '<span style="color: #ffb142; font-weight: bold;">[warn]</span>') .replace(/\[warn\]/g, '<span class="text-warning font-bold">[warn]</span>')
.replace(/failed/g, '<span style="color: #ff5252;">failed</span>') .replace(/failed/g, '<span class="text-error">failed</span>')
.replace(/successful/g, '<span style="color: #33d9b2;">successful</span>') .replace(/successful/g, '<span class="text-success">successful</span>')
Swal.fire({ Swal.fire({
title: result.success ? 'Başarılı' : 'Yapılandırma Hatası', title: result.success ? 'Başarılı' : 'Yapılandırma Hatası',
@@ -636,8 +669,8 @@ function App(): JSX.Element {
${formattedMessage} ${formattedMessage}
</div> </div>
${errorInfo?.host ? ` ${errorInfo?.host ? `
<div style="margin-top: 15px; text-align: center;"> <div class="mt-4 text-center">
<p style="font-size: 0.9rem; color: #ffb142;"> <p class="text-sm text-warning">
<b>{t('common.error')}:</b> ${errorInfo.host} (Satır: ${errorInfo.line}) <b>{t('common.error')}:</b> ${errorInfo.host} (Satır: ${errorInfo.line})
</p> </p>
</div> </div>
@@ -779,47 +812,57 @@ function App(): JSX.Element {
} }
} }
const handleCheckForUpdates = async () => {
if (!window.api) return
setUpdateStatus('checking')
const result = await window.api.checkForUpdates()
if (result && result.error) {
setUpdateStatus('error')
setNotification({ open: true, message: result.error, severity: 'error' })
}
}
const installedPhpVersions = phpVersions.filter(v => v.status === 'installed') const installedPhpVersions = phpVersions.filter(v => v.status === 'installed')
return ( return (
<> <>
<Dialog <Dialog
open={!!selectedQuery} open={!!selectedQuery}
onClose={() => setSelectedQuery(null)} onClose={() => setSelectedQuery(null)}
maxWidth="lg" maxWidth="lg"
fullWidth fullWidth
PaperProps={{ PaperProps={{
className: 'mui-custom-dialog-paper' className: 'mui-custom-dialog-paper'
}} }}
> >
<DialogTitle sx={{ borderBottom: '1px solid rgba(255,255,255,0.1)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <DialogTitle sx={{ borderBottom: '1px solid var(--color-border-soft)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 800 }}>{t('mariadb_process_monitor.query_details')}</Typography> <Typography variant="h6" className="font-black">{t('mariadb_process_monitor.query_details')}</Typography>
<IconButton onClick={() => setSelectedQuery(null)} sx={{ color: 'rgba(255,255,255,0.5)' }}> <IconButton onClick={() => setSelectedQuery(null)} className="text-secondary">
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent sx={{ p: 0 }}> <DialogContent sx={{ p: 0 }}>
<Box sx={{ p: 3, maxHeight: '70vh', overflow: 'auto' }}> <Box sx={{ p: 3, maxHeight: '70vh', overflow: 'auto' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}> <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
<code <code
className="language-sql" className="language-sql"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: Prism.highlight( __html: Prism.highlight(
(selectedQuery || '') (selectedQuery || '')
.replace(/\\r\\n/g, '\n') .replace(/\\r\\n/g, '\n')
.replace(/\\n/g, '\n') .replace(/\\n/g, '\n')
.replace(/\\t/g, ' '), .replace(/\\t/g, ' '),
Prism.languages.sql, Prism.languages.sql,
'sql' 'sql'
) )
}} }}
/> />
</pre> </pre>
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions sx={{ borderTop: '1px solid rgba(255,255,255,0.1)', p: 2 }}> <DialogActions sx={{ borderTop: '1px solid var(--color-border-soft)', p: 2 }}>
<Button <Button
startIcon={<ContentCopyIcon />} startIcon={<ContentCopyIcon />}
onClick={() => { onClick={() => {
navigator.clipboard.writeText(selectedQuery || '') navigator.clipboard.writeText(selectedQuery || '')
@@ -848,23 +891,23 @@ function App(): JSX.Element {
width: { sm: `calc(100% - ${drawerWidth}px)` }, width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` }, ml: { sm: `${drawerWidth}px` },
zIndex: (theme) => theme.zIndex.drawer - 1, zIndex: (theme) => theme.zIndex.drawer - 1,
bgcolor: 'rgba(21, 62, 94, 0.85)', bgcolor: 'var(--color-bg-navbar)',
backdropFilter: 'blur(10px)' backdropFilter: 'blur(10px)'
}} }}
> >
<Toolbar sx={{ gap: 1.5, minHeight: '56px !important' }}> <Toolbar sx={{ gap: 1.5, minHeight: '56px !important' }}>
<Box sx={{ flexGrow: 1 }} /> <Box sx={{ flexGrow: 1 }} />
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
color="success" color="success"
onClick={handleStartAll} onClick={handleStartAll}
startIcon={<StartIcon sx={{ fontSize: '1rem !important' }} />} startIcon={<StartIcon sx={{ fontSize: 'var(--font-size-base) !important' }} />}
sx={{ sx={{
borderRadius: 1.5, borderRadius: 'var(--radius-md)',
fontSize: '0.7rem', fontSize: 'var(--font-size-xs)',
fontWeight: 800, fontWeight: 'var(--font-weight-black)',
textTransform: 'none', textTransform: 'none',
borderColor: 'rgba(76, 175, 80, 0.3)', borderColor: 'rgba(76, 175, 80, 0.3)',
px: 1.5, px: 1.5,
@@ -874,16 +917,16 @@ function App(): JSX.Element {
{t('dashboard.start_all')} {t('dashboard.start_all')}
</Button> </Button>
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
color="error" color="error"
onClick={handleStopAll} onClick={handleStopAll}
startIcon={<StopIcon sx={{ fontSize: '1rem !important' }} />} startIcon={<StopIcon sx={{ fontSize: 'var(--font-size-base) !important' }} />}
sx={{ sx={{
borderRadius: 1.5, borderRadius: 'var(--radius-md)',
fontSize: '0.7rem', fontSize: 'var(--font-size-xs)',
fontWeight: 800, fontWeight: 'var(--font-weight-black)',
textTransform: 'none', textTransform: 'none',
borderColor: 'rgba(244, 67, 54, 0.3)', borderColor: 'rgba(244, 67, 54, 0.3)',
px: 1.5, px: 1.5,
@@ -894,7 +937,7 @@ function App(): JSX.Element {
</Button> </Button>
<Divider orientation="vertical" flexItem sx={{ mx: 0.5, borderColor: 'rgba(255,255,255,0.1)', height: 20, mt: 1.5 }} /> <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 }}> <FormControl size="small" variant="outlined" sx={{ minWidth: 70 }}>
<Select <Select
value={i18n.language} value={i18n.language}
@@ -904,18 +947,18 @@ function App(): JSX.Element {
sx={{ sx={{
color: 'white', color: 'white',
height: '28px', height: '28px',
fontSize: '0.7rem', fontSize: 'var(--font-size-xs)',
fontWeight: 700, fontWeight: 'var(--font-weight-bold)',
'.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.1)' }, '.MuiOutlinedInput-notchedOutline': { borderColor: 'var(--color-border-soft)' },
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.2)' }, '&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'var(--color-border-medium)' },
'.MuiSelect-icon': { color: 'rgba(255,255,255,0.5)', fontSize: '1rem' } '.MuiSelect-icon': { color: 'var(--color-text-secondary)', fontSize: '1rem' }
}} }}
> >
{supportedLngs.map(lang => ( {supportedLngs.map(lang => (
<MenuItem key={lang} value={lang} sx={{ fontSize: '0.75rem' }}> <MenuItem key={lang} value={lang} className="text-sm">
{lang.toUpperCase()} {lang.toUpperCase()}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
</FormControl> </FormControl>
@@ -924,7 +967,7 @@ function App(): JSX.Element {
<TerminalIcon sx={{ fontSize: '1.2rem' }} /> <TerminalIcon sx={{ fontSize: '1.2rem' }} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<IconButton size="small" color="inherit" onClick={() => setActiveTab('settings')} sx={{ p: 0.5, opacity: 0.7, '&:hover': { opacity: 1 } }}> <IconButton size="small" color="inherit" onClick={() => setActiveTab('settings')} sx={{ p: 0.5, opacity: 0.7, '&:hover': { opacity: 1 } }}>
<SettingsIcon sx={{ fontSize: '1.2rem' }} /> <SettingsIcon sx={{ fontSize: '1.2rem' }} />
</IconButton> </IconButton>
@@ -937,7 +980,12 @@ function App(): JSX.Element {
sx={{ sx={{
width: drawerWidth, width: drawerWidth,
flexShrink: 0, flexShrink: 0,
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box', bgcolor: 'rgba(18, 18, 18, 0.95)', borderRight: '1px solid rgba(255, 255, 255, 0.05)' }, [`& .MuiDrawer-paper`]: {
width: drawerWidth,
boxSizing: 'border-box',
bgcolor: 'var(--color-bg-sidebar)',
borderRight: '1px solid var(--color-border-soft)'
},
}} }}
> >
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '120px' }}> <Box sx={{ p: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '120px' }}>
@@ -949,37 +997,47 @@ function App(): JSX.Element {
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><DashboardIcon fontSize="small" color={activeTab === 'dashboard' ? 'primary' : 'inherit'} /></ListItemIcon> <ListItemIcon sx={{ minWidth: 40 }}><DashboardIcon fontSize="small" color={activeTab === 'dashboard' ? 'primary' : 'inherit'} /></ListItemIcon>
<ListItemText primary={t('dashboard.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('dashboard.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'projects'} onClick={() => setActiveTab('projects')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'projects'} onClick={() => setActiveTab('projects')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><ProjectIcon fontSize="small" color={activeTab === 'projects' ? 'primary' : 'inherit'} /></ListItemIcon> <ListItemIcon sx={{ minWidth: 40 }}><ProjectIcon fontSize="small" color={activeTab === 'projects' ? 'primary' : 'inherit'} /></ListItemIcon>
<ListItemText primary={t('projects.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('projects.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'tuning'} onClick={() => setActiveTab('tuning')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'tuning'} onClick={() => setActiveTab('tuning')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><TuningIcon fontSize="small" color={activeTab === 'tuning' ? 'primary' : 'inherit'} /></ListItemIcon> <ListItemIcon sx={{ minWidth: 40 }}><TuningIcon fontSize="small" color={activeTab === 'tuning' ? 'primary' : 'inherit'} /></ListItemIcon>
<ListItemText primary={t('tuning.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('tuning.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'tools'} onClick={() => setActiveTab('tools')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'tools'} onClick={() => setActiveTab('tools')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><ToolsIcon fontSize="small" color={activeTab === 'tools' ? 'primary' : 'inherit'} /></ListItemIcon> <ListItemIcon sx={{ minWidth: 40 }}><ToolsIcon fontSize="small" color={activeTab === 'tools' ? 'primary' : 'inherit'} /></ListItemIcon>
<ListItemText primary={t('common.tools')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('common.tools')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'db_manager'} onClick={() => setActiveTab('db_manager')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'db_manager'} onClick={() => setActiveTab('db_manager')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><DbIcon fontSize="small" 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')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('mariadb_manager.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'db_monitor'} onClick={() => setActiveTab('db_monitor')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'db_monitor'} onClick={() => setActiveTab('db_monitor')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><TerminalIcon fontSize="small" 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')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('mariadb_process_monitor.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton>
</ListItem>
<Divider sx={{ opacity: 0.05, my: 0.5 }} />
<ListItem disablePadding>
<ListItemButton onClick={() => window.api.invoke('www:open')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><FolderOpenIcon fontSize="small" sx={{ color: 'var(--color-primary)' }} /></ListItemIcon>
<ListItemText
primary={t('common.open_www', 'WWW Klasörünü Aç')}
primaryTypographyProps={{ className: 'text-sm font-medium', sx: { color: 'var(--color-primary)' } }}
/>
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
</List> </List>
@@ -988,19 +1046,19 @@ function App(): JSX.Element {
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'user_guide'} onClick={() => setActiveTab('user_guide')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'user_guide'} onClick={() => setActiveTab('user_guide')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><GuideIcon fontSize="small" 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')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('common.user_guide')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'settings'} onClick={() => setActiveTab('settings')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'settings'} onClick={() => setActiveTab('settings')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><SettingsIcon fontSize="small" color={activeTab === 'settings' ? 'primary' : 'inherit'} /></ListItemIcon> <ListItemIcon sx={{ minWidth: 40 }}><SettingsIcon fontSize="small" color={activeTab === 'settings' ? 'primary' : 'inherit'} /></ListItemIcon>
<ListItemText primary={t('settings.general')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('settings.general')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton selected={activeTab === 'event_monitor'} onClick={() => setActiveTab('event_monitor')} sx={{ py: 0.5 }}> <ListItemButton selected={activeTab === 'event_monitor'} onClick={() => setActiveTab('event_monitor')} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 40 }}><HistoryIcon fontSize="small" 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')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} /> <ListItemText primary={t('service_monitor.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
</List> </List>
@@ -1024,6 +1082,7 @@ function App(): JSX.Element {
<Tab label="Apache" icon={<ServerIcon />} iconPosition="start" /> <Tab label="Apache" icon={<ServerIcon />} iconPosition="start" />
<Tab label={t('settings.server_settings')} icon={<SettingsIcon />} iconPosition="start" /> <Tab label={t('settings.server_settings')} icon={<SettingsIcon />} iconPosition="start" />
<Tab label={t('settings.cli_env')} icon={<TerminalIcon />} iconPosition="start" /> <Tab label={t('settings.cli_env')} icon={<TerminalIcon />} iconPosition="start" />
<Tab label={t('updates.title')} icon={<RefreshIcon />} iconPosition="start" />
</Tabs> </Tabs>
</Box> </Box>
@@ -1284,13 +1343,13 @@ function App(): JSX.Element {
<Box> <Box>
<FormControlLabel <FormControlLabel
control={ control={
<Switch <Switch
checked={cliConfig.enabled} checked={cliConfig.enabled}
onChange={async (e) => { onChange={async (e) => {
const newConf = { ...cliConfig, enabled: e.target.checked } const newConf = { ...cliConfig, enabled: e.target.checked }
setCliConfig(newConf) setCliConfig(newConf)
await window.api.invoke('cli:set-config', newConf) await window.api.invoke('cli:set-config', newConf)
}} }}
/> />
} }
label={ label={
@@ -1344,18 +1403,18 @@ function App(): JSX.Element {
<Divider /> <Divider />
<Box sx={{ p: 2, bgcolor: 'rgba(0,0,0,0.2)', borderRadius: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <Box sx={{ p: 2, bgcolor: 'var(--color-bg-dark)', borderRadius: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box> <Box>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', textTransform: 'uppercase', fontWeight: 800, mb: 0.5, display: 'block' }}> <Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', textTransform: 'uppercase', fontWeight: 800, mb: 0.5, display: 'block' }}>
{cliConfig.enabled ? t('settings.cli_path_found') : t('settings.cli_path_not_found')} {cliConfig.enabled ? t('settings.cli_path_found') : t('settings.cli_path_not_found')}
</Typography> </Typography>
<Typography variant="body2" sx={{ fontFamily: 'monospace', color: cliConfig.enabled ? '#33d9b2' : 'error.main' }}> <Typography variant="body2" sx={{ fontFamily: 'monospace', color: cliConfig.enabled ? 'var(--color-success)' : 'var(--color-error)' }}>
{cliConfig.binDir || '...'} {cliConfig.binDir || '...'}
</Typography> </Typography>
</Box> </Box>
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
startIcon={<RefreshIcon />} startIcon={<RefreshIcon />}
onClick={async () => { onClick={async () => {
await window.api.invoke('cli:sync') await window.api.invoke('cli:sync')
@@ -1383,9 +1442,9 @@ function App(): JSX.Element {
<Table stickyHeader size="small"> <Table stickyHeader size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold', color: 'rgba(255,255,255,0.4)', fontSize: '0.7rem', textTransform: 'uppercase' }}>{t('settings.cli_table_command')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-bold)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)', textTransform: 'uppercase' }}>{t('settings.cli_table_command')}</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold', color: 'rgba(255,255,255,0.4)', fontSize: '0.7rem', textTransform: 'uppercase' }}>{t('settings.cli_table_version')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-bold)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)', textTransform: 'uppercase' }}>{t('settings.cli_table_version')}</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 'bold', color: 'rgba(255,255,255,0.4)', fontSize: '0.7rem', textTransform: 'uppercase' }}>{t('settings.cli_table_path')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-bold)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)', textTransform: 'uppercase' }}>{t('settings.cli_table_path')}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -1416,11 +1475,82 @@ function App(): JSX.Element {
</Paper> </Paper>
</Box> </Box>
)} )}
{settingsTab === 6 && (
<Paper sx={{ p: 4, bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3, gap: 2 }}>
<RefreshIcon color="primary" sx={{ fontSize: 32 }} />
<Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>{t('updates.title')}</Typography>
<Typography variant="body2" color="text.secondary">v1.0.0</Typography>
</Box>
</Box>
<Box sx={{ p: 3, bgcolor: 'rgba(0,0,0,0.2)', borderRadius: 2, border: '1px solid rgba(255,255,255,0.05)', mb: 3 }}>
{updateStatus === 'idle' && (
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ mb: 2 }}>{t('updates.not_available')}</Typography>
<Button variant="contained" onClick={handleCheckForUpdates} startIcon={<RefreshIcon />}>
{t('updates.check_btn')}
</Button>
</Box>
)}
{updateStatus === 'checking' && (
<Box sx={{ textAlign: 'center', py: 2 }}>
<CircularProgress size={24} sx={{ mb: 2 }} />
<Typography variant="body1">{t('updates.checking')}</Typography>
</Box>
)}
{updateStatus === 'available' && (
<Box sx={{ textAlign: 'center', py: 2 }}>
<AvailableIcon color="primary" sx={{ fontSize: 48, mb: 1 }} />
<Typography variant="h6" sx={{ mb: 1 }}>{t('updates.available', { version: updateInfo?.version })}</Typography>
<Button variant="contained" onClick={() => window.api.downloadUpdate()} startIcon={<DownloadIcon />}>
{t('common.download')}
</Button>
</Box>
)}
{updateStatus === 'downloading' && (
<Box sx={{ py: 2 }}>
<Typography variant="body1" sx={{ mb: 1, textAlign: 'center' }}>{t('updates.downloading')}</Typography>
<LinearProgress variant="determinate" value={updateProgress?.percent || 0} sx={{ height: 10, borderRadius: 5, mb: 1 }} />
<Typography variant="caption" sx={{ display: 'block', textAlign: 'center' }}>
%{Math.round(updateProgress?.percent || 0)} - {Math.round((updateProgress?.bytesPerSecond || 0) / 1024)} KB/s
</Typography>
</Box>
)}
{updateStatus === 'downloaded' && (
<Box sx={{ textAlign: 'center', py: 2 }}>
<InstalledIcon color="success" sx={{ fontSize: 48, mb: 1 }} />
<Typography variant="body1" sx={{ mb: 2 }}>{t('updates.downloaded')}</Typography>
<Button variant="contained" color="success" onClick={() => window.api.quitAndInstall()} startIcon={<RefreshIcon />}>
{t('updates.install_btn')}
</Button>
</Box>
)}
{updateStatus === 'error' && (
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography color="error" sx={{ mb: 2 }}>{t('updates.error', { error: updateInfo })}</Typography>
<Button variant="outlined" onClick={handleCheckForUpdates}>{t('updates.check_btn')}</Button>
</Box>
)}
</Box>
<Alert severity="info" sx={{ bgcolor: 'rgba(2, 136, 209, 0.05)', color: '#91d5ff', '& .MuiAlert-icon': { color: '#91d5ff' } }}>
{t('settings.cli_restart_warning')}
</Alert>
</Paper>
)}
</Box> </Box>
)} )}
{activeTab === 'dashboard' && ( {activeTab === 'dashboard' && (
<DashboardManager <DashboardManager
status={status} status={status}
settings={settings} settings={settings}
projects={projects} projects={projects}
@@ -1453,7 +1583,7 @@ function App(): JSX.Element {
value={selectedMariaDbId} value={selectedMariaDbId}
onChange={(e) => setSelectedMariaDbId(e.target.value)} onChange={(e) => setSelectedMariaDbId(e.target.value)}
label={t('settings.mariadb_versions')} label={t('settings.mariadb_versions')}
sx={{ sx={{
color: '#fff', color: '#fff',
'.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.1)' } '.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.1)' }
}} }}
@@ -1471,16 +1601,16 @@ function App(): JSX.Element {
<Table stickyHeader size="small"> <Table stickyHeader size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>ID</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>ID</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>USER</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>USER</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>HOST</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>HOST</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>DB</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>DB</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>COMMAND</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>COMMAND</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>TIME</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>TIME</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>STATE</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>STATE</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>INFO</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>INFO</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem' }}>PROGRESS</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)' }}>PROGRESS</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, color: 'rgba(255,255,255,0.4)', fontSize: '0.75rem', textAlign: 'right' }}>ACTION</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', fontWeight: 'var(--font-weight-black)', color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-xs)', textAlign: 'right' }}>ACTION</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -1493,18 +1623,18 @@ function App(): JSX.Element {
) : ( ) : (
processList.map((proc) => ( processList.map((proc) => (
<TableRow key={proc.Id} hover sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> <TableRow key={proc.Id} hover sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)', fontFamily: 'monospace' }}>{proc.Id}</TableCell> <TableCell sx={{ borderColor: 'var(--color-border-soft)', color: 'var(--color-text-secondary)', fontFamily: 'monospace' }}>{proc.Id}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: '#39A7FF' }}>{proc.User}</TableCell> <TableCell sx={{ borderColor: 'var(--color-border-soft)', color: 'var(--color-primary)' }}>{proc.User}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)' }}>{proc.Host}</TableCell> <TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)' }}>{proc.Host}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: proc.db ? '#ff9f43' : 'rgba(255,255,255,0.3)' }}>{proc.db || 'system'}</TableCell> <TableCell sx={{ borderColor: 'var(--color-border-soft)', color: proc.db ? 'var(--color-warning)' : 'var(--color-text-tertiary)' }}>{proc.db || 'system'}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: '#33d9b2' }}>{proc.Command}</TableCell> <TableCell sx={{ borderColor: 'var(--color-border-soft)', color: 'var(--color-success)' }}>{proc.Command}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)' }}>{proc.Time}s</TableCell> <TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)' }}>{proc.Time}s</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: proc.State === 'Query' ? '#39A7FF' : 'rgba(255,255,255,0.5)' }}>{proc.State || '-'}</TableCell> <TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: proc.State === 'Query' ? '#39A7FF' : 'rgba(255,255,255,0.5)' }}>{proc.State || '-'}</TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)', maxWidth: 300, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)', maxWidth: 300, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<Tooltip title={t('mariadb_process_monitor.click_to_view_sql')}> <Tooltip title={t('mariadb_process_monitor.click_to_view_sql')}>
<Box <Box
onClick={() => setSelectedQuery(proc.Info)} onClick={() => setSelectedQuery(proc.Info)}
sx={{ sx={{
cursor: proc.Info ? 'pointer' : 'default', cursor: proc.Info ? 'pointer' : 'default',
'&:hover': { color: proc.Info ? '#39A7FF' : 'inherit' }, '&:hover': { color: proc.Info ? '#39A7FF' : 'inherit' },
transition: 'color 0.2s' transition: 'color 0.2s'
@@ -1635,7 +1765,7 @@ function App(): JSX.Element {
)} )}
{activeTab === 'projects' && ( {activeTab === 'projects' && (
<ProjectManager <ProjectManager
projects={projects} projects={projects}
status={status} status={status}
settings={settings} settings={settings}
@@ -1716,9 +1846,9 @@ function App(): JSX.Element {
fullWidth fullWidth
PaperProps={{ PaperProps={{
sx: { sx: {
bgcolor: '#1e1e1e', bgcolor: 'var(--color-bg-dark)',
color: '#d4d4d4', color: 'var(--color-text-primary)',
borderRadius: 2, borderRadius: 'var(--radius-lg)',
boxShadow: '0 8px 32px rgba(0,0,0,0.5)' boxShadow: '0 8px 32px rgba(0,0,0,0.5)'
} }
}} }}
@@ -1732,7 +1862,7 @@ function App(): JSX.Element {
}}> }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<TerminalIcon color="primary" /> <TerminalIcon color="primary" />
<Typography variant="h6" sx={{ fontWeight: 600, color: '#fff' }}> <Typography variant="h6" className="font-bold text-white">
{selectedService?.toUpperCase()} - {t('diagnostics.logs', 'Servis Logları')} {selectedService?.toUpperCase()} - {t('diagnostics.logs', 'Servis Logları')}
</Typography> </Typography>
</Box> </Box>
@@ -1740,14 +1870,14 @@ function App(): JSX.Element {
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent sx={{ p: 0, bgcolor: '#000' }}> <DialogContent sx={{ p: 0, bgcolor: 'black' }}>
<Box <Box
sx={{ sx={{
height: '400px', height: '400px',
overflowY: 'auto', overflowY: 'auto',
p: 2, p: 2,
fontFamily: '"Fira Code", "Consolas", monospace', fontFamily: '"Fira Code", "Consolas", monospace',
fontSize: '0.875rem', fontSize: 'var(--font-size-sm)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '2px' gap: '2px'
@@ -1805,7 +1935,8 @@ function App(): JSX.Element {
onClick={() => { onClick={() => {
navigator.clipboard.writeText(logs.join('\n')) navigator.clipboard.writeText(logs.join('\n'))
}} }}
sx={{ color: '#888', '&:hover': { color: '#fff' } }} className="text-tertiary"
sx={{ '&:hover': { color: 'white' } }}
> >
{t('common.copy', 'Kopyala')} {t('common.copy', 'Kopyala')}
</Button> </Button>
@@ -1931,9 +2062,9 @@ function App(): JSX.Element {
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', gap: 1 }}> <Box sx={{ display: 'flex', gap: 1 }}>
<Button <Button
size="small" size="small"
startIcon={isProcessListLoading ? <CircularProgress size={16} /> : <RefreshIcon />} startIcon={isProcessListLoading ? <CircularProgress size={16} /> : <RefreshIcon />}
onClick={() => selectedMariaDbId && fetchProcessList(selectedMariaDbId)} onClick={() => selectedMariaDbId && fetchProcessList(selectedMariaDbId)}
disabled={isProcessListLoading} disabled={isProcessListLoading}
> >
@@ -1949,16 +2080,16 @@ function App(): JSX.Element {
<Table stickyHeader size="small"> <Table stickyHeader size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.id')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.id')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.user')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.user')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.host')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.host')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.db')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.db')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.command')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.command')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.time')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.time')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.state')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.state')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.info')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.info')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem' }}>{t('mariadb_process_monitor.columns.progress')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)' }}>{t('mariadb_process_monitor.columns.progress')}</TableCell>
<TableCell sx={{ bgcolor: '#24292e', color: 'rgba(255,255,255,0.5)', fontWeight: 'bold', fontSize: '0.75rem', textAlign: 'right' }}>{t('common.actions')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-medium)', color: 'var(--color-text-tertiary)', fontWeight: 'var(--font-weight-bold)', fontSize: 'var(--font-size-xs)', textAlign: 'right' }}>{t('common.actions')}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -1987,10 +2118,10 @@ function App(): JSX.Element {
{p.Progress ? `${parseFloat(p.Progress).toFixed(2)}%` : '-'} {p.Progress ? `${parseFloat(p.Progress).toFixed(2)}%` : '-'}
</TableCell> </TableCell>
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', textAlign: 'right' }}> <TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', textAlign: 'right' }}>
<Button <Button
size="small" size="small"
color="error" color="error"
variant="text" variant="text"
onClick={() => selectedMariaDbId && handleKillProcess(selectedMariaDbId, p.Id)} onClick={() => selectedMariaDbId && handleKillProcess(selectedMariaDbId, p.Id)}
sx={{ fontSize: '0.65rem', p: 0, minWidth: 'auto' }} sx={{ fontSize: '0.65rem', p: 0, minWidth: 'auto' }}
> >
@@ -2035,7 +2166,8 @@ function App(): JSX.Element {
onClick={handleTestNginxConfig} onClick={handleTestNginxConfig}
variant="outlined" variant="outlined"
startIcon={<RefreshIcon />} startIcon={<RefreshIcon />}
sx={{ color: '#fff', borderColor: 'rgba(255,255,255,0.3)' }} className="text-white"
sx={{ borderColor: 'var(--color-border-medium)' }}
> >
{t('server.nginx.test_config')} {t('server.nginx.test_config')}
</Button> </Button>
@@ -39,7 +39,6 @@ import 'prismjs/themes/prism-tomorrow.css'
import 'prismjs/components/prism-nginx' import 'prismjs/components/prism-nginx'
import 'prismjs/components/prism-apacheconf' import 'prismjs/components/prism-apacheconf'
import 'prismjs/components/prism-ini' import 'prismjs/components/prism-ini'
import Editor from 'react-simple-code-editor'
import SearchableCodeEditor from '../SearchableCodeEditor' import SearchableCodeEditor from '../SearchableCodeEditor'
import Prism from 'prismjs' import Prism from 'prismjs'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
@@ -144,8 +143,8 @@ export default function DashboardDetails({
position: 'top-end', position: 'top-end',
showConfirmButton: false, showConfirmButton: false,
timer: 3000, timer: 3000,
background: '#1e1e1e', background: 'var(--color-bg-dark)',
color: '#fff' color: 'var(--color-text-primary)'
}); });
} }
}; };
@@ -161,8 +160,8 @@ export default function DashboardDetails({
position: 'top-end', position: 'top-end',
showConfirmButton: false, showConfirmButton: false,
timer: 3000, timer: 3000,
background: '#1e1e1e', background: 'var(--color-bg-dark)',
color: '#fff' color: 'var(--color-text-primary)'
}); });
} }
}; };
@@ -215,7 +214,7 @@ export default function DashboardDetails({
if (!serviceId) { if (!serviceId) {
return ( return (
<Box sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', opacity: 0.5 }}> <Box sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', opacity: 0.5 }}>
<Typography variant="h6">{t('dashboard.select_service', 'Lütfen bir servis seçin')}</Typography> <Typography variant="h6" className="font-semibold">{t('dashboard.select_service', 'Lütfen bir servis seçin')}</Typography>
</Box> </Box>
); );
} }
@@ -255,19 +254,19 @@ export default function DashboardDetails({
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
<Box sx={{ mb: 1.5 }}> <Box sx={{ mb: 1.5 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', fontWeight: 600, letterSpacing: 0.5 }}>İşlemci Kullanımı</Typography> <Typography variant="caption" className="text-secondary font-semibold" sx={{ letterSpacing: 0.5 }}>İşlemci Kullanımı</Typography>
<Typography variant="caption" sx={{ color: 'primary.main', fontWeight: 800 }}>{cpu || 0}%</Typography> <Typography variant="caption" color="primary.main" className="font-black">{cpu || 0}%</Typography>
</Box> </Box>
<LinearProgress variant="determinate" value={cpu || 0} sx={{ height: 6, borderRadius: 3, bgcolor: 'rgba(255,255,255,0.05)', '.MuiLinearProgress-bar': { borderRadius: 3, background: 'linear-gradient(90deg, #2196F3, #21CBF3)' } }} /> <LinearProgress variant="determinate" value={cpu || 0} sx={{ height: 6, borderRadius: 'var(--radius-xl)', bgcolor: 'var(--color-bg-soft)', '.MuiLinearProgress-bar': { borderRadius: 'var(--radius-xl)', background: 'linear-gradient(90deg, var(--color-primary), var(--color-secondary))' } }} />
</Box> </Box>
<Box> <Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', fontWeight: 600, letterSpacing: 0.5 }}>Bellek Kullanımı</Typography> <Typography variant="caption" className="text-secondary font-semibold" sx={{ letterSpacing: 0.5 }}>Bellek Kullanımı</Typography>
<Typography variant="caption" sx={{ color: 'secondary.main', fontWeight: 800 }}> <Typography variant="caption" color="secondary.main" className="font-black">
{memory ? `${Math.round(memory / (1024 * 1024))} MB` : '0 MB'} {memory ? `${Math.round(memory / (1024 * 1024))} MB` : '0 MB'}
</Typography> </Typography>
</Box> </Box>
<LinearProgress variant="determinate" value={Math.min(((memory || 0) / (1024 * 1024) / 1024) * 100, 100)} sx={{ height: 6, borderRadius: 3, bgcolor: 'rgba(255,255,255,0.05)', '.MuiLinearProgress-bar': { borderRadius: 3, background: 'linear-gradient(90deg, #9C27B0, #E91E63)' } }} /> <LinearProgress variant="determinate" value={Math.min(((memory || 0) / (1024 * 1024) / 1024) * 100, 100)} sx={{ height: 6, borderRadius: 'var(--radius-xl)', bgcolor: 'var(--color-bg-soft)', '.MuiLinearProgress-bar': { borderRadius: 'var(--radius-xl)', background: 'linear-gradient(90deg, var(--color-secondary), var(--color-error))' } }} />
</Box> </Box>
</Box> </Box>
); );
@@ -275,31 +274,31 @@ export default function DashboardDetails({
const isPhp = serviceType === 'php'; const isPhp = serviceType === 'php';
return ( return (
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', bgcolor: 'background.default' }}> <Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', bgcolor: 'var(--color-bg-dark)' }}>
<Box sx={{ p: 4, borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', alignItems: 'center', gap: 3 }}> <Box sx={{ p: 4, borderBottom: '1px solid var(--color-border-soft)', display: 'flex', alignItems: 'center', gap: 3 }}>
<Paper sx={{ p: 2, bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}> <Paper sx={{ p: 2, bgcolor: 'var(--color-bg-sidebar)', borderColor: 'var(--color-border-soft)' }} className="rounded-xl">
{serviceType === 'nginx' && <WebIcon color="primary" sx={{ fontSize: 40 }} />} {serviceType === 'nginx' && <WebIcon color="primary" sx={{ fontSize: 'var(--font-size-4xl)' }} />}
{serviceType === 'apache' && <ServerIcon color="primary" sx={{ fontSize: 40 }} />} {serviceType === 'apache' && <ServerIcon color="primary" sx={{ fontSize: 'var(--font-size-4xl)' }} />}
{serviceType === 'php' && <PhpIcon color="primary" sx={{ fontSize: 40 }} />} {serviceType === 'php' && <PhpIcon color="primary" sx={{ fontSize: 'var(--font-size-4xl)' }} />}
{serviceType === 'mariadb' && <DbIcon color="primary" sx={{ fontSize: 40 }} />} {serviceType === 'mariadb' && <DbIcon color="primary" sx={{ fontSize: 'var(--font-size-4xl)' }} />}
</Paper> </Paper>
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 800, letterSpacing: '-0.5px', color: 'primary.main', fontSize: '1.25rem' }}> <Typography variant="h6" className="font-black text-xl" sx={{ letterSpacing: '-0.5px', color: 'primary.main' }}>
{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.2 }}> <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, fontSize: '0.7rem' }}>{t('common.starting')}</Box>} {isStarting && <Box component="span" sx={{ color: 'warning.light', letterSpacing: 0.5 }} className="font-bold text-xs">{t('common.starting')}</Box>}
{isStopping && <Box component="span" sx={{ color: 'warning.light', fontWeight: 700, letterSpacing: 0.5, fontSize: '0.7rem' }}>{t('common.stopping')}</Box>} {isStopping && <Box component="span" sx={{ color: 'warning.light', letterSpacing: 0.5 }} className="font-bold text-xs">{t('common.stopping')}</Box>}
{isRunning && <Box component="span" sx={{ color: 'success.main', fontWeight: 700, letterSpacing: 0.5, fontSize: '0.7rem' }}>{t('common.running')}</Box>} {isRunning && <Box component="span" sx={{ color: 'success.main', letterSpacing: 0.5 }} className="font-bold text-xs">{t('common.running')}</Box>}
{!isRunning && !isStarting && !isStopping && <Box component="span" sx={{ opacity: 0.5, fontWeight: 700, letterSpacing: 0.5, fontSize: '0.7rem' }}>{t('common.stopped')}</Box>} {!isRunning && !isStarting && !isStopping && <Box component="span" sx={{ letterSpacing: 0.5 }} className="text-secondary font-bold text-xs">{t('common.stopped')}</Box>}
</Typography> </Typography>
</Box> </Box>
<Stack direction="row" spacing={1.5}> <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: 32, height: 32, borderRadius: 2 }}> <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 }} className="rounded-md">
<RefreshIcon sx={{ fontSize: '1.1rem' }} /> <RefreshIcon sx={{ fontSize: 'var(--font-size-lg)' }} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}
@@ -308,19 +307,20 @@ export default function DashboardDetails({
color={isRunning ? 'error' : 'success'} color={isRunning ? 'error' : 'success'}
disabled={isTransitioning} disabled={isTransitioning}
size="small" size="small"
startIcon={isTransitioning ? <CircularProgress size={14} color="inherit" /> : isRunning ? <StopIcon sx={{ fontSize: '1.1rem' }} /> : <StartIcon sx={{ fontSize: '1.1rem' }} />} startIcon={isTransitioning ? <CircularProgress size={14} color="inherit" /> : isRunning ? <StopIcon sx={{ fontSize: 'var(--font-size-lg)' }} /> : <StartIcon sx={{ fontSize: 'var(--font-size-lg)' }} />}
onClick={() => onToggleService(serviceId)} onClick={() => onToggleService(serviceId)}
sx={{ borderRadius: 2, px: 2, height: 32, fontWeight: 800, fontSize: '0.75rem', boxShadow: 'none' }} sx={{ px: 2, height: 32, boxShadow: 'none' }}
className="rounded-md font-black text-sm"
> >
{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>
</Stack> </Stack>
</Box> </Box>
<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: 'var(--color-border-soft)', px: 4, bgcolor: 'var(--color-bg-soft)' }}>
<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: 48, fontWeight: 700, fontSize: '0.8rem' } '.MuiTab-root': { textTransform: 'none', minHeight: 48 }
}}> }} className="font-bold text-sm">
<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" />
{isPhp && <Tab label="php.ini" value="settings_live" />} {isPhp && <Tab label="php.ini" value="settings_live" />}
@@ -336,10 +336,10 @@ export default function DashboardDetails({
{activeTab === 'overview' && ( {activeTab === 'overview' && (
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Typography variant="body2" sx={{ fontWeight: 800, mb: 1.5, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.8rem' }}> <Typography variant="body2" className="font-black text-sm" sx={{ mb: 1.5, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1 }}>
<StatusIcon sx={{ fontSize: '1rem' }} /> {t('dashboard.system_stats', 'Sistem İstatistikleri')} <StatusIcon sx={{ fontSize: 'var(--font-size-base)' }} /> {t('dashboard.system_stats', 'Sistem İstatistikleri')}
</Typography> </Typography>
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 3, border: '1px solid rgba(255,255,255,0.05)' }}> <Paper sx={{ p: 2, bgcolor: 'var(--color-bg-soft)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--color-border-soft)' }}>
<ResourceMonitor cpu={currentStatus.cpu} memory={currentStatus.memory} /> <ResourceMonitor cpu={currentStatus.cpu} memory={currentStatus.memory} />
</Paper> </Paper>
</Grid> </Grid>
@@ -347,22 +347,23 @@ export default function DashboardDetails({
<Typography variant="body2" sx={{ fontWeight: 800, mb: 1.5, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.8rem' }}> <Typography variant="body2" sx={{ fontWeight: 800, mb: 1.5, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.8rem' }}>
<SettingsIcon sx={{ fontSize: '1rem' }} /> {t('dashboard.service_info', 'Servis Bilgileri')} <SettingsIcon sx={{ fontSize: '1rem' }} /> {t('dashboard.service_info', 'Servis Bilgileri')}
</Typography> </Typography>
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 3, border: '1px solid rgba(255,255,255,0.05)' }}> <Paper sx={{ p: 2, bgcolor: 'var(--color-bg-sidebar)', borderColor: 'var(--color-border-soft)' }} className="rounded-lg">
<Stack spacing={1.5}> <Stack spacing={1.5}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="caption" sx={{ opacity: 0.5, fontWeight: 700, fontSize: '0.75rem' }}>{t('common.port', 'Port')}</Typography> <Typography variant="caption" className="font-bold text-xs" sx={{ opacity: 0.5 }}>{t('common.port', 'Port')}</Typography>
<Typography variant="body2" sx={{ fontWeight: 800, color: 'primary.main', fontFamily: 'monospace', fontSize: '0.75rem' }}> <Typography variant="body2" className="font-black text-xs" sx={{ color: 'primary.main', fontFamily: 'monospace' }}>
{getPort()} {getPort()}
</Typography> </Typography>
</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="caption" sx={{ opacity: 0.5, fontWeight: 700, fontSize: '0.75rem' }}>{t('common.status', 'Durum')}</Typography> <Typography variant="caption" className="font-bold text-xs" sx={{ opacity: 0.5 }}>{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" size="small"
color={isRunning ? 'success' : 'error'} color={isRunning ? 'success' : 'error'}
sx={{ fontWeight: 900, borderRadius: 1.5, height: 20, fontSize: '0.65rem' }} sx={{ borderRadius: 'var(--border-radius-sm)', height: 20 }}
className="font-black text-xs"
/> />
</Box> </Box>
</Stack> </Stack>
@@ -380,16 +381,17 @@ export default function DashboardDetails({
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
startIcon={isLoadingConfig ? <CircularProgress size={16} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem' }} />} startIcon={isLoadingConfig ? <CircularProgress size={16} color="inherit" /> : <SaveIcon sx={{ fontSize: 'var(--font-size-base)' }} />}
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, px: 2, fontWeight: 800, fontSize: '0.75rem' }} sx={{ px: 2 }}
className="rounded-md font-black text-sm"
> >
{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: 'black', borderRadius: 'var(--radius-lg)', border: '1px solid var(--color-border-soft)', 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>
) : ( ) : (
@@ -424,7 +426,7 @@ export default function DashboardDetails({
{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: 'black', borderRadius: 'var(--radius-lg)', border: '1px solid var(--color-border-soft)', 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>
) : ( ) : (
@@ -444,10 +446,10 @@ export default function DashboardDetails({
)} )}
{activeTab === 'logs' && ( {activeTab === 'logs' && (
<Box sx={{ bgcolor: '#0b0e11', borderRadius: 4, p: 3, height: '65vh', overflowY: 'auto', border: '1px solid rgba(255,255,255,0.05)', boxShadow: 'inset 0 0 20px rgba(0,0,0,0.5)' }}> <Box sx={{ bgcolor: 'var(--color-bg-medium)', borderRadius: 'var(--radius-lg)', p: 3, height: '65vh', overflowY: 'auto', border: '1px solid var(--color-border-soft)', boxShadow: 'inset 0 0 20px rgba(0,0,0,0.5)' }}>
<Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1.5, color: 'rgba(255,255,255,0.3)' }}> <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1.5, color: 'var(--color-text-muted)' }}>
<LogIcon fontSize="small" /> <LogIcon fontSize="small" />
<Typography variant="caption" sx={{ fontWeight: 700, letterSpacing: 1 }}>Gerçek Zamanlı Servis Logları</Typography> <Typography variant="caption" className="font-bold" sx={{ letterSpacing: 1 }}>Gerçek Zamanlı Servis Logları</Typography>
</Box> </Box>
{logs.length === 0 ? ( {logs.length === 0 ? (
<Box sx={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: 0.2 }}> <Box sx={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: 0.2 }}>
@@ -457,8 +459,8 @@ export default function DashboardDetails({
logs.map((log, i) => ( logs.map((log, i) => (
<Typography key={i} sx={{ <Typography key={i} sx={{
fontFamily: '"Fira Code", monospace', fontFamily: '"Fira Code", monospace',
fontSize: '0.85rem', fontSize: 'var(--font-size-xs)',
color: log.toLowerCase().includes('error') ? '#ff5252' : (log.toLowerCase().includes('warn') ? '#ffb142' : '#33d9b2'), color: log.toLowerCase().includes('error') ? 'var(--color-error)' : (log.toLowerCase().includes('warn') ? 'var(--color-warning)' : 'var(--color-success)'),
mb: 0.8, mb: 0.8,
lineHeight: 1.5, lineHeight: 1.5,
opacity: 0.9 opacity: 0.9
@@ -476,8 +478,8 @@ export default function DashboardDetails({
<List disablePadding> <List disablePadding>
{serviceProjects.length === 0 ? ( {serviceProjects.length === 0 ? (
<Box sx={{ p: 8, textAlign: 'center', opacity: 0.2 }}> <Box sx={{ p: 8, textAlign: 'center', opacity: 0.2 }}>
<ProjectIcon sx={{ fontSize: 64, mb: 2 }} /> <ProjectIcon sx={{ fontSize: 'var(--font-size-7xl)', mb: 2 }} />
<Typography variant="h6">{t('projects.empty')}</Typography> <Typography variant="h6" className="text-xl">{t('projects.empty')}</Typography>
</Box> </Box>
) : ( ) : (
serviceProjects.map((p_item, idx) => ( serviceProjects.map((p_item, idx) => (
@@ -49,20 +49,20 @@ export default function DashboardSidebar({
selected={selectedServiceId === id} selected={selectedServiceId === id}
onClick={() => setSelectedServiceId(id)} onClick={() => setSelectedServiceId(id)}
sx={{ sx={{
borderRadius: 2,
mb: 0.5, mb: 0.5,
border: selectedServiceId === id ? '1px solid rgba(57, 167, 255, 0.3)' : '1px solid transparent', border: selectedServiceId === id ? '1px solid var(--color-primary-soft)' : '1px solid transparent',
'&.Mui-selected': { bgcolor: 'rgba(57, 167, 255, 0.08)' } '&.Mui-selected': { bgcolor: 'var(--color-primary-soft)' }
}} }}
className="rounded-md"
> >
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%', gap: 1.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', width: '100%', gap: 1.5 }}>
<Icon sx={{ color: selectedServiceId === id ? 'primary.main' : 'rgba(255,255,255,0.4)', fontSize: '1.2rem' }} /> <Icon sx={{ color: selectedServiceId === id ? 'primary.main' : 'var(--color-text-secondary)', fontSize: 'var(--font-size-lg)' }} />
<Box sx={{ flexGrow: 1, minWidth: 0 }}> <Box sx={{ flexGrow: 1, minWidth: 0 }}>
<Typography variant="body2" noWrap sx={{ fontWeight: 600, fontSize: '0.85rem' }}> <Typography variant="body2" noWrap className="font-semibold text-sm">
{label} {label}
</Typography> </Typography>
{isRunning && status[serviceKey] && ( {isRunning && status[serviceKey] && (
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', display: 'block', fontSize: '0.6rem', mt: -0.5 }}> <Typography variant="caption" className="text-secondary text-xs" sx={{ display: 'block', mt: -0.5 }}>
Port: {status[serviceKey].port || '?'} | CPU: {Math.round(status[serviceKey].cpu || 0)}% | RAM: {Math.round((status[serviceKey].memory || 0) / (1024 * 1024))}MB Port: {status[serviceKey].port || '?'} | CPU: {Math.round(status[serviceKey].cpu || 0)}% | RAM: {Math.round((status[serviceKey].memory || 0) / (1024 * 1024))}MB
</Typography> </Typography>
)} )}
@@ -71,8 +71,8 @@ export default function DashboardSidebar({
width: 10, width: 10,
height: 10, height: 10,
borderRadius: '50%', borderRadius: '50%',
bgcolor: isStarting ? 'warning.main' : (isRunning ? 'success.main' : 'error.main'), bgcolor: isStarting ? 'var(--color-warning)' : (isRunning ? 'var(--color-success)' : 'var(--color-error)'),
boxShadow: isRunning ? '0 0 8px rgba(76, 175, 80, 0.5)' : (isStarting ? '0 0 8px rgba(255, 177, 66, 0.5)' : 'none'), boxShadow: isRunning ? '0 0 8px var(--color-success-soft)' : (isStarting ? '0 0 8px var(--color-warning-soft)' : 'none'),
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@@ -94,21 +94,21 @@ export default function DashboardSidebar({
return ( return (
<Box sx={{ <Box sx={{
width: 320, width: 320,
borderRight: '1px solid rgba(255, 255, 255, 0.05)', borderRight: '1px solid var(--color-border-soft)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
bgcolor: 'rgba(255, 255, 255, 0.01)', bgcolor: 'var(--color-bg-sidebar)',
height: '100%', height: '100%',
overflowY: 'auto' overflowY: 'auto'
}}> }}>
<Box sx={{ p: 2, borderBottom: '1px solid rgba(255, 255, 255, 0.05)' }}> <Box sx={{ p: 2, borderBottom: '1px solid var(--color-border-soft)' }}>
<Typography variant="h6" sx={{ fontWeight: 700, fontSize: '1rem' }}> <Typography variant="h6" className="font-bold text-md">
{t('dashboard.services', 'Servisler')} {t('dashboard.services', 'Servisler')}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 1.5 }}> <Box sx={{ p: 1.5 }}>
<Typography variant="caption" sx={{ px: 1, color: 'primary.main', fontWeight: 600, mb: 1, display: 'block', opacity: 0.8 }}> <Typography variant="caption" className="font-semibold text-primary" sx={{ px: 1, mb: 1, display: 'block', opacity: 0.8 }}>
Web Sunucuları Web Sunucuları
</Typography> </Typography>
<List dense disablePadding sx={{ mb: 3 }}> <List dense disablePadding sx={{ mb: 3 }}>
@@ -116,7 +116,7 @@ export default function DashboardSidebar({
<ServiceItem id="apache" label="Apache" icon={ServerIcon} serviceKey="apache" /> <ServiceItem id="apache" label="Apache" icon={ServerIcon} serviceKey="apache" />
</List> </List>
<Typography variant="caption" sx={{ px: 1, color: 'primary.main', fontWeight: 600, mb: 1, display: 'block', opacity: 0.8 }}> <Typography variant="caption" className="font-semibold text-primary" sx={{ px: 1, mb: 1, display: 'block', opacity: 0.8 }}>
PHP Sürümleri PHP Sürümleri
</Typography> </Typography>
<List dense disablePadding sx={{ mb: 3 }}> <List dense disablePadding sx={{ mb: 3 }}>
@@ -131,7 +131,7 @@ export default function DashboardSidebar({
))} ))}
</List> </List>
<Typography variant="caption" sx={{ px: 1, color: 'primary.main', fontWeight: 600, mb: 1, display: 'block', opacity: 0.8 }}> <Typography variant="caption" className="font-semibold text-primary" sx={{ px: 1, mb: 1, display: 'block', opacity: 0.8 }}>
Veritabanı Sürümleri Veritabanı Sürümleri
</Typography> </Typography>
<List dense disablePadding> <List dense disablePadding>
@@ -174,35 +174,36 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
return ( return (
<Box> <Box>
<Paper sx={{ p: 3, mb: 3, bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}> <Paper sx={{ p: 3, mb: 3, bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)', borderRadius: 'var(--radius-lg)' }}>
<Stack direction="row" spacing={2} alignItems="center" justifyContent="space-between" sx={{ mb: 3 }}> <Stack direction="row" spacing={2} alignItems="center" justifyContent="space-between" sx={{ mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<DbIcon color="primary" /> <DbIcon color="primary" />
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>{t('mariadb_manager.title')}</Typography> <Typography variant="h6" className="font-bold">{t('mariadb_manager.title')}</Typography>
</Box> </Box>
<Stack direction="row" spacing={1.5}> <Stack direction="row" spacing={1.5}>
<FormControl size="small" sx={{ minWidth: 160 }}> <FormControl size="small" sx={{ minWidth: 160 }}>
<InputLabel id="version-select-label" sx={{ fontSize: '0.75rem' }}>{t('mariadb.version')}</InputLabel> <InputLabel id="version-select-label" className="text-sm">{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 }} sx={{ height: 32 }}
className="text-sm"
> >
{installedVersions.map(v => ( {installedVersions.map(v => (
<MenuItem key={v.id} value={v.id} sx={{ fontSize: '0.75rem' }}>MariaDB {v.version}</MenuItem> <MenuItem key={v.id} value={v.id} className="text-sm">MariaDB {v.version}</MenuItem>
))} ))}
</Select> </Select>
</FormControl> </FormControl>
<Button variant="outlined" size="small" startIcon={<RefreshIcon sx={{ fontSize: '1rem' }} />} onClick={fetchData} disabled={loading} sx={{ fontSize: '0.7rem', height: 32, borderRadius: 2 }}> <Button variant="outlined" size="small" startIcon={<RefreshIcon sx={{ fontSize: 'var(--font-size-base)' }} />} onClick={fetchData} disabled={loading} sx={{ height: 32 }} className="text-xs rounded-md">
{t('common.refresh')} {t('common.refresh')}
</Button> </Button>
</Stack> </Stack>
</Stack> </Stack>
{!selectedVersion && ( {!selectedVersion && (
<Alert severity="info" sx={{ mt: 2, fontSize: '0.75rem' }}>{t('mariadb_manager.no_active_version')}</Alert> <Alert severity="info" sx={{ mt: 2 }} className="text-sm">{t('mariadb_manager.no_active_version')}</Alert>
)} )}
{selectedVersion && ( {selectedVersion && (
@@ -210,31 +211,31 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
{/* Databases Section */} {/* Databases Section */}
<Box> <Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 800, color: 'primary.light', fontSize: '0.8rem' }}> <Typography variant="subtitle2" sx={{ color: 'var(--color-primary)' }} className="font-black text-md">
{t('mariadb_manager.databases_title')} {t('mariadb_manager.databases_title')}
</Typography> </Typography>
<Button variant="outlined" size="small" startIcon={<AddIcon sx={{ fontSize: '1rem' }} />} onClick={handleCreateDb} sx={{ fontSize: '0.7rem', height: 28, borderRadius: 1.5 }}> <Button variant="outlined" size="small" startIcon={<AddIcon sx={{ fontSize: 'var(--font-size-base)' }} />} onClick={handleCreateDb} sx={{ height: 28 }} className="text-xs rounded-md">
{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: 300, borderRadius: 2 }}> <TableContainer component={Paper} variant="outlined" sx={{ bgcolor: 'var(--color-bg-medium)', maxHeight: 300, borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-soft)' }}>
<Table stickyHeader size="small"> <Table stickyHeader size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.db_name')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-dark)', borderBottom: '1px solid var(--color-border-medium)', py: 1 }} className="font-black text-xs">{t('mariadb_manager.db_name')}</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.db_size')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-dark)', borderBottom: '1px solid var(--color-border-medium)', py: 1 }} className="font-black text-xs">{t('mariadb_manager.db_size')}</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1, textAlign: 'right' }}>{t('common.actions')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-dark)', borderBottom: '1px solid var(--color-border-medium)', py: 1, textAlign: 'right' }} className="font-black text-xs">{t('common.actions')}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{loading ? ( {loading ? (
<TableRow><TableCell colSpan={3} align="center"><CircularProgress size={20} sx={{ my: 1 }} /></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: 2, color: 'text.secondary', fontSize: '0.7rem' }}>{t('common.no_data')}</TableCell></TableRow> <TableRow><TableCell colSpan={3} align="center" sx={{ py: 2 }} className="text-secondary text-xs">{t('common.no_data')}</TableCell></TableRow>
) : databases.map(db => ( ) : databases.map(db => (
<TableRow key={db.name}> <TableRow key={db.name}>
<TableCell sx={{ fontWeight: 600, fontSize: '0.75rem' }}>{db.name}</TableCell> <TableCell className="font-semibold text-sm">{db.name}</TableCell>
<TableCell sx={{ fontSize: '0.7rem', opacity: 0.8 }}>{db.size.toFixed(2)} MB</TableCell> <TableCell className="text-xs" sx={{ opacity: 0.8 }}>{db.size.toFixed(2)} MB</TableCell>
<TableCell align="right" sx={{ py: 0.5 }}> <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)} sx={{ p: 0.5 }}> <IconButton color="error" size="small" onClick={() => handleDeleteDb(db.name)} sx={{ p: 0.5 }}>
@@ -254,10 +255,10 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
{/* Users Section */} {/* Users Section */}
<Box> <Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 800, color: 'secondary.light', fontSize: '0.8rem' }}> <Typography variant="subtitle2" className="font-black text-sm" sx={{ color: 'var(--color-secondary)' }}>
{t('mariadb_manager.users_title')} {t('mariadb_manager.users_title')}
</Typography> </Typography>
<Button variant="outlined" color="secondary" size="small" startIcon={<AddIcon sx={{ fontSize: '1rem' }} />} onClick={handleCreateUser} sx={{ fontSize: '0.7rem', height: 28, borderRadius: 1.5 }}> <Button variant="outlined" color="secondary" size="small" startIcon={<AddIcon sx={{ fontSize: 'var(--font-size-base)' }} />} onClick={handleCreateUser} sx={{ height: 28 }} className="text-xs rounded-md">
{t('mariadb_manager.create_user_btn')} {t('mariadb_manager.create_user_btn')}
</Button> </Button>
</Box> </Box>
@@ -265,27 +266,27 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
<Table stickyHeader size="small"> <Table stickyHeader size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.user_label')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-dark)', borderBottom: '1px solid var(--color-border-medium)', py: 1 }} className="font-black text-xs">{t('mariadb_manager.user_label')}</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>Host</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-dark)', borderBottom: '1px solid var(--color-border-medium)', py: 1 }} className="font-black text-xs">Host</TableCell>
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1, textAlign: 'right' }}>{t('common.actions')}</TableCell> <TableCell sx={{ bgcolor: 'var(--color-bg-dark)', borderBottom: '1px solid var(--color-border-medium)', py: 1, textAlign: 'right' }} className="font-black text-xs">{t('common.actions')}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{loading ? ( {loading ? (
<TableRow><TableCell colSpan={3} align="center"><CircularProgress size={20} sx={{ my: 1 }} /></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: 2, color: 'text.secondary', fontSize: '0.7rem' }}>{t('common.no_data')}</TableCell></TableRow> <TableRow><TableCell colSpan={3} align="center" sx={{ py: 2 }} className="text-secondary text-xs">{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: 600, fontSize: '0.75rem' }}> <TableCell className="font-semibold text-sm">
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box className="flex-align-center" sx={{ gap: 1 }}>
<UserIcon sx={{ opacity: 0.5, fontSize: '0.9rem' }} /> <UserIcon sx={{ fontSize: 'var(--font-size-md)' }} className="text-secondary" />
{user.user} {user.user}
</Box> </Box>
</TableCell> </TableCell>
<TableCell sx={{ fontSize: '0.7rem', opacity: 0.8 }}> <TableCell className="text-xs" sx={{ opacity: 0.8 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box className="flex-align-center" sx={{ gap: 1 }}>
<HostIcon sx={{ opacity: 0.5, fontSize: '0.9rem' }} /> <HostIcon sx={{ fontSize: 'var(--font-size-md)' }} className="text-secondary" />
{user.host} {user.host}
</Box> </Box>
</TableCell> </TableCell>
@@ -219,20 +219,20 @@ const MariaDbExportWizard: React.FC<MariaDbExportWizardProps> = ({ open, onClose
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
{!exportResult ? ( {!exportResult ? (
<> <>
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.05)', borderRadius: 2 }}> <Paper sx={{ p: 2, bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }} className="rounded-md">
<Typography variant="subtitle2" color="primary" sx={{ fontWeight: 'bold' }}>{t('mariadb.import.summary')}</Typography> <Typography variant="subtitle2" sx={{ color: 'var(--color-primary)' }} className="font-bold text-md">{t('mariadb.import.summary')}</Typography>
<Divider sx={{ my: 1 }} /> <Divider sx={{ my: 1, opacity: 0.05 }} />
<Stack spacing={1}> <Stack spacing={1}>
<Typography variant="body2"><strong>{t('mariadb.import.server')}:</strong> {selectedVersion}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.server')}:</strong> {selectedVersion}</Typography>
<Typography variant="body2"><strong>{t('mariadb.import.db')}:</strong> {selectedDb}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.db')}:</strong> {selectedDb}</Typography>
<Typography variant="body2"><strong>{t('mariadb.export.target')}:</strong> {savePath}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.export.target')}:</strong> {savePath}</Typography>
<Typography variant="body2"><strong>{t('mariadb.export.compression')}:</strong> {compress ? t('mariadb.export.on') : t('mariadb.export.off')}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.export.compression')}:</strong> {compress ? t('mariadb.export.on') : t('mariadb.export.off')}</Typography>
</Stack> </Stack>
</Paper> </Paper>
{loading && ( {loading && (
<Box sx={{ mt: 3, p: 2, bgcolor: '#000', borderRadius: 2, maxHeight: '200px', overflowY: 'auto', fontFamily: 'monospace' }}> <Box sx={{ mt: 3, p: 2, bgcolor: 'var(--color-bg-medium)', maxHeight: '200px', overflowY: 'auto', fontFamily: 'monospace' }} className="rounded-md border-soft">
{logs.map((log, i) => ( {logs.map((log, i) => (
<Typography key={i} variant="caption" sx={{ display: 'block', color: log.includes('error') ? 'error.main' : 'success.main' }}> <Typography key={i} variant="caption" sx={{ display: 'block', color: log.includes('error') ? 'var(--color-error)' : 'var(--color-success)' }} className="text-xs">
{log} {log}
</Typography> </Typography>
))} ))}
@@ -244,9 +244,9 @@ const MariaDbExportWizard: React.FC<MariaDbExportWizardProps> = ({ open, onClose
<Box sx={{ textAlign: 'center', py: 3 }}> <Box sx={{ textAlign: 'center', py: 3 }}>
{exportResult.success ? ( {exportResult.success ? (
<> <>
<SuccessIcon color="success" sx={{ fontSize: 60 }} /> <SuccessIcon sx={{ color: 'var(--color-success)', fontSize: 'var(--font-size-4xl)' }} />
<Typography variant="h6" sx={{ mt: 2 }}>{t('mariadb.import.success_title')}</Typography> <Typography variant="h6" className="font-black text-xl" sx={{ mt: 2 }}>{t('mariadb.import.success_title')}</Typography>
<Typography variant="body2" color="text.secondary">{exportResult.message}</Typography> <Typography variant="body2" className="text-secondary">{exportResult.message}</Typography>
<Button <Button
variant="outlined" variant="outlined"
sx={{ mt: 3 }} sx={{ mt: 3 }}
@@ -257,9 +257,9 @@ const MariaDbExportWizard: React.FC<MariaDbExportWizardProps> = ({ open, onClose
</> </>
) : ( ) : (
<> <>
<ErrorIcon color="error" sx={{ fontSize: 60 }} /> <ErrorIcon sx={{ color: 'var(--color-error)', fontSize: 'var(--font-size-4xl)' }} />
<Typography variant="h6" sx={{ mt: 2 }}>{t('mariadb.import.error_title')}</Typography> <Typography variant="h6" className="font-black text-xl" sx={{ mt: 2 }}>{t('mariadb.import.error_title')}</Typography>
<Typography variant="body2" color="error">{exportResult.message}</Typography> <Typography variant="body2" sx={{ color: 'var(--color-error)' }} className="text-sm">{exportResult.message}</Typography>
</> </>
)} )}
</Box> </Box>
@@ -260,20 +260,20 @@ const MariaDbImportWizard: React.FC<MariaDbImportWizardProps> = ({ open, onClose
<Box sx={{ mt: 2 }}> <Box sx={{ mt: 2 }}>
{!importResult ? ( {!importResult ? (
<> <>
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.05)', borderRadius: 2 }}> <Paper sx={{ p: 2, bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }} className="rounded-md">
<Typography variant="subtitle2" color="primary" sx={{ fontWeight: 'bold' }}>{t('mariadb.import.summary')}</Typography> <Typography variant="subtitle2" sx={{ color: 'var(--color-primary)' }} className="font-bold text-md">{t('mariadb.import.summary')}</Typography>
<Divider sx={{ my: 1 }} /> <Divider sx={{ my: 1, opacity: 0.05 }} />
<Stack spacing={1}> <Stack spacing={1}>
<Typography variant="body2"><strong>{t('mariadb.import.file')}:</strong> {filePath.split(/[/\\]/).pop()}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.file')}:</strong> {filePath.split(/[/\\]/).pop()}</Typography>
<Typography variant="body2"><strong>{t('mariadb.import.server')}:</strong> {selectedVersion}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.server')}:</strong> {selectedVersion}</Typography>
<Typography variant="body2"><strong>{t('mariadb.import.db')}:</strong> {dbConfig.dbName}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.db')}:</strong> {dbConfig.dbName}</Typography>
<Typography variant="body2"><strong>{t('mariadb.import.user')}:</strong> {dbConfig.user}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.user')}:</strong> {dbConfig.user}</Typography>
</Stack> </Stack>
</Paper> </Paper>
{loading && ( {loading && (
<Box sx={{ mt: 3, p: 2, bgcolor: '#000', borderRadius: 2, maxHeight: '200px', overflowY: 'auto', fontFamily: 'monospace' }}> <Box sx={{ mt: 3, p: 2, bgcolor: 'var(--color-bg-medium)', maxHeight: '200px', overflowY: 'auto', fontFamily: 'monospace' }} className="rounded-md border-soft">
{logs.map((log, i) => ( {logs.map((log, i) => (
<Typography key={i} variant="caption" sx={{ display: 'block', color: log.includes('fail') ? 'error.main' : 'success.main' }}> <Typography key={i} variant="caption" sx={{ display: 'block', color: log.includes('fail') ? 'var(--color-error)' : 'var(--color-success)' }} className="text-xs">
{log} {log}
</Typography> </Typography>
))} ))}
@@ -285,24 +285,24 @@ const MariaDbImportWizard: React.FC<MariaDbImportWizardProps> = ({ open, onClose
<Box sx={{ textAlign: 'center', py: 3 }}> <Box sx={{ textAlign: 'center', py: 3 }}>
{importResult.success ? ( {importResult.success ? (
<> <>
<SuccessIcon color="success" sx={{ fontSize: 60 }} /> <SuccessIcon sx={{ color: 'var(--color-success)', fontSize: 'var(--font-size-4xl)' }} />
<Typography variant="h6" sx={{ mt: 2 }}>{t('mariadb.import.success_title')}</Typography> <Typography variant="h6" className="font-black text-xl" sx={{ mt: 2 }}>{t('mariadb.import.success_title')}</Typography>
<Typography variant="body2" color="text.secondary">{importResult.message}</Typography> <Typography variant="body2" className="text-secondary">{importResult.message}</Typography>
<Paper sx={{ p: 2, mt: 3, textAlign: 'left', bgcolor: 'rgba(0,180,0,0.1)' }}> <Paper sx={{ p: 2.5, mt: 3, textAlign: 'left', bgcolor: 'var(--color-success-soft)', border: '1px solid var(--color-success-soft)' }} className="rounded-lg">
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>{t('mariadb.import.access_info')}:</Typography> <Typography variant="caption" className="font-black text-xs" sx={{ color: 'var(--color-success)', display: 'block', mb: 1, textTransform: 'uppercase' }}>{t('mariadb.import.access_info')}:</Typography>
<Typography variant="body2"><strong>{t('mariadb.import.host')}:</strong> localhost</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.host')}:</strong> localhost</Typography>
<Typography variant="body2"><strong>{t('mariadb.import.db')}:</strong> {dbConfig.dbName}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.db')}:</strong> {dbConfig.dbName}</Typography>
<Typography variant="body2"><strong>{t('mariadb.import.user')}:</strong> {dbConfig.user}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.user')}:</strong> {dbConfig.user}</Typography>
<Typography variant="body2"><strong>{t('mariadb.import.db_pass')}:</strong> {dbConfig.pass}</Typography> <Typography variant="body2" className="text-sm"><strong>{t('mariadb.import.db_pass')}:</strong> {dbConfig.pass}</Typography>
</Paper> </Paper>
</> </>
) : ( ) : (
<> <>
<ErrorIcon color="error" sx={{ fontSize: 60 }} /> <ErrorIcon sx={{ color: 'var(--color-error)', fontSize: 'var(--font-size-4xl)' }} />
<Typography variant="h6" sx={{ mt: 2 }}>{t('mariadb.import.error_title')}</Typography> <Typography variant="h6" className="font-black text-xl" sx={{ mt: 2 }}>{t('mariadb.import.error_title')}</Typography>
<Typography variant="body2" color="error">{importResult.message}</Typography> <Typography variant="body2" sx={{ color: 'var(--color-error)' }} className="text-sm">{importResult.message}</Typography>
<Box sx={{ mt: 2, textAlign: 'left', bgcolor: '#000', p: 1, maxHeight: 150, overflowY: 'auto' }}> <Box sx={{ mt: 2, textAlign: 'left', bgcolor: 'black', p: 1, maxHeight: 150, overflowY: 'auto' }} className="rounded-md border-soft">
{logs.slice(-5).map((l, i) => <Typography key={i} variant="caption" sx={{ color: '#fff', display: 'block' }}>{l}</Typography>)} {logs.slice(-5).map((l, i) => <Typography key={i} variant="caption" sx={{ color: 'white', display: 'block' }} className="text-2xs font-mono">{l}</Typography>)}
</Box> </Box>
</> </>
)} )}
@@ -168,15 +168,15 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
> >
<DialogTitle sx={{ m: 0, p: 1.5, 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="subtitle2" sx={{ fontWeight: 800 }}>{t('php.extensions.title', { version: phpVersion })}</Typography> <Typography variant="subtitle2" className="font-black">{t('php.extensions.title', { version: phpVersion })}</Typography>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.65rem' }}>{t('php.extensions.desc')}</Typography> <Typography variant="caption" color="text.secondary" className="text-xs">{t('php.extensions.desc')}</Typography>
</Box> </Box>
<IconButton onClick={onClose} size="small" sx={{ color: 'text.secondary' }}> <IconButton onClick={onClose} size="small" sx={{ color: 'text.secondary' }}>
<CloseIcon fontSize="small" /> <CloseIcon fontSize="small" />
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent dividers sx={{ borderColor: 'rgba(255,255,255,0.05)', p: 0 }}> <DialogContent dividers sx={{ borderColor: 'var(--color-border-soft)', p: 0 }}>
<Box sx={{ p: 1.5, bgcolor: 'rgba(255,255,255,0.02)', display: 'flex', gap: 1 }}> <Box sx={{ p: 1.5, bgcolor: 'var(--color-bg-soft)', display: 'flex', gap: 1 }}>
<TextField <TextField
fullWidth fullWidth
size="small" size="small"
@@ -186,18 +186,20 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
<SearchIcon sx={{ color: 'text.secondary', fontSize: '1rem' }} /> <SearchIcon sx={{ color: 'text.secondary', fontSize: 'var(--font-size-base)' }} />
</InputAdornment> </InputAdornment>
), ),
sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)', fontSize: '0.75rem', height: 32 } className: 'text-sm',
sx: { color: 'white', bgcolor: 'var(--color-bg-medium)', height: 32 }
}} }}
/> />
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
startIcon={<RawIcon sx={{ fontSize: '1rem' }} />} startIcon={<RawIcon sx={{ fontSize: 'var(--font-size-base)' }} />}
onClick={fetchRawConfig} onClick={fetchRawConfig}
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 }} sx={{ whiteSpace: 'nowrap', borderColor: 'var(--color-border-medium)', color: 'var(--color-text-secondary)', height: 32 }}
className="text-xs rounded-md"
> >
{t('php.extensions.raw_editor')} {t('php.extensions.raw_editor')}
</Button> </Button>
@@ -213,7 +215,7 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
<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')}
primaryTypographyProps={{ sx: { textAlign: 'center', color: 'text.secondary', fontSize: '0.75rem' } }} primaryTypographyProps={{ className: 'text-sm text-secondary', sx: { textAlign: 'center' } }}
/> />
</ListItem> </ListItem>
) : ( ) : (
@@ -221,7 +223,7 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
<ListItem <ListItem
key={ext.name} key={ext.name}
divider divider
sx={{ borderColor: 'rgba(255,255,255,0.03)', py: 0.5 }} sx={{ borderColor: 'var(--color-border-soft)', py: 0.5 }}
secondaryAction={ secondaryAction={
saving === ext.name ? ( saving === ext.name ? (
<CircularProgress size={16} /> <CircularProgress size={16} />
@@ -238,13 +240,13 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
> >
<ListItemText <ListItemText
primary={ primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.75rem', fontWeight: 600 }}> <Box className="flex-align-center font-semibold text-sm" sx={{ gap: 1 }}>
{ext.name} {ext.name}
{ext.enabled && <Chip label={t('php.extensions.active')} size="small" color="success" sx={{ height: 16, fontSize: '0.55rem', fontWeight: 900 }} />} {ext.enabled && <Chip label={t('php.extensions.active')} size="small" color="success" className="font-black text-xs" sx={{ height: 16 }} />}
</Box> </Box>
} }
secondary={ext.dll} secondary={ext.dll}
secondaryTypographyProps={{ sx: { color: 'text.secondary', fontSize: '0.65rem', opacity: 0.5 } }} secondaryTypographyProps={{ className: 'text-xs text-secondary', sx: { opacity: 0.5 } }}
/> />
</ListItem> </ListItem>
)) ))
@@ -252,11 +254,11 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
</List> </List>
)} )}
</DialogContent> </DialogContent>
<DialogActions sx={{ p: 1.5, bgcolor: 'rgba(255,255,255,0.01)' }}> <DialogActions sx={{ p: 1.5, bgcolor: 'var(--color-bg-soft)' }}>
<Button onClick={fetchExtensions} size="small" startIcon={<RefreshIcon sx={{ fontSize: '0.9rem' }} />} sx={{ mr: 'auto', fontSize: '0.7rem' }}> <Button onClick={fetchExtensions} size="small" startIcon={<RefreshIcon sx={{ fontSize: 'var(--font-size-md)' }} />} className="text-xs" sx={{ mr: 'auto' }}>
{t('common.refresh').toUpperCase()} {t('common.refresh').toUpperCase()}
</Button> </Button>
<Button onClick={onClose} variant="outlined" size="small" sx={{ fontSize: '0.7rem', borderRadius: 1.5 }}> <Button onClick={onClose} variant="outlined" size="small" className="text-xs rounded-md">
{t('common.close').toUpperCase()} {t('common.close').toUpperCase()}
</Button> </Button>
</DialogActions> </DialogActions>
@@ -273,18 +275,18 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
> >
<DialogTitle sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, p: 1.5 }}> <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="subtitle2" sx={{ fontWeight: 800 }}>{t('php.extensions.ini_edit', { version: phpVersion })}</Typography> <Typography variant="subtitle2" className="font-black">{t('php.extensions.ini_edit', { version: phpVersion })}</Typography>
<IconButton onClick={() => setIsRawEditorOpen(false)} size="small" sx={{ color: 'rgba(255,255,255,0.5)' }}> <IconButton onClick={() => setIsRawEditorOpen(false)} size="small" sx={{ color: 'var(--color-text-tertiary)' }}>
<CloseIcon fontSize="small" /> <CloseIcon fontSize="small" />
</IconButton> </IconButton>
</Box> </Box>
<Typography <Typography
variant="caption" variant="caption"
className="text-xs"
sx={{ sx={{
color: 'primary.main', color: 'primary.main',
wordBreak: 'break-all', wordBreak: 'break-all',
cursor: 'pointer', cursor: 'pointer',
fontSize: '0.6rem',
opacity: 0.7, opacity: 0.7,
'&:hover': { textDecoration: 'underline', color: 'primary.light' } '&:hover': { textDecoration: 'underline', color: 'primary.light' }
}} }}
@@ -304,27 +306,27 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
<SearchIcon sx={{ color: 'text.secondary', fontSize: '1rem' }} /> <SearchIcon sx={{ color: 'text.secondary', fontSize: 'var(--font-size-base)' }} />
</InputAdornment> </InputAdornment>
), ),
className: 'text-sm',
sx: { sx: {
color: '#fff', color: 'white',
bgcolor: 'rgba(255,255,255,0.03)', bgcolor: 'var(--color-bg-soft)',
fontSize: '0.75rem',
height: 32, height: 32,
'& fieldset': { borderColor: 'rgba(255,255,255,0.05)' } '& fieldset': { borderColor: 'var(--color-border-soft)' }
} }
}} }}
/> />
</Box> </Box>
<DialogContent sx={{ p: 0, minHeight: 350, borderTop: '1px solid rgba(255,255,255,0.05)' }}> <DialogContent sx={{ p: 0, minHeight: 350, borderTop: '1px solid var(--color-border-soft)' }}>
<PhpIniCodeEditor <PhpIniCodeEditor
value={rawConfig} value={rawConfig}
onChange={setRawConfig} onChange={setRawConfig}
/> />
</DialogContent> </DialogContent>
<DialogActions sx={{ p: 1.5, bgcolor: 'rgba(0,0,0,0.1)' }}> <DialogActions sx={{ p: 1.5, bgcolor: 'var(--color-bg-medium)' }}>
<Button onClick={() => setIsRawEditorOpen(false)} size="small" sx={{ color: 'rgba(255,255,255,0.4)', fontSize: '0.7rem' }}> <Button onClick={() => setIsRawEditorOpen(false)} size="small" className="text-secondary text-xs">
{t('common.cancel').toUpperCase()} {t('common.cancel').toUpperCase()}
</Button> </Button>
<Button <Button
@@ -332,9 +334,10 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
variant="outlined" variant="outlined"
color="primary" color="primary"
size="small" size="small"
startIcon={isSavingRaw ? <CircularProgress size={14} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem' }} />} startIcon={isSavingRaw ? <CircularProgress size={14} color="inherit" /> : <SaveIcon sx={{ fontSize: 'var(--font-size-base)' }} />}
disabled={isSavingRaw} disabled={isSavingRaw}
sx={{ fontSize: '0.7rem', borderRadius: 1.5, px: 2 }} className="text-xs rounded-md"
sx={{ 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>
@@ -199,9 +199,9 @@ export default function ProjectDetails({
if (!p) { if (!p) {
return ( return (
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', opacity: 0.3 }}> <Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', opacity: 0.2 }}>
<ProjectIcon sx={{ fontSize: 80, mb: 2 }} /> <ProjectIcon sx={{ fontSize: 80, mb: 2, color: 'var(--color-primary)' }} />
<Typography variant="h6">{t('projects.no_project_selected')}</Typography> <Typography variant="h6" className="font-bold text-tertiary">{t('projects.no_project_selected')}</Typography>
</Box> </Box>
); );
} }
@@ -215,21 +215,21 @@ export default function ProjectDetails({
const installedMariaDb = mariaDbVersions.filter(v => v.status === 'installed'); const installedMariaDb = mariaDbVersions.filter(v => v.status === 'installed');
return ( return (
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', bgcolor: 'rgba(255,255,255,0.01)', height: '100%', overflow: 'hidden', position: 'relative' }}> <Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', bgcolor: 'var(--color-bg-dark)', height: '100%', overflow: 'hidden', position: 'relative' }}>
{/* Header */} {/* Header */}
<Box sx={{ p: 4, borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', alignItems: 'flex-start', gap: 3, position: 'relative' }}> <Box sx={{ p: 4, borderBottom: '1px solid var(--color-border-soft)', display: 'flex', alignItems: 'flex-start', gap: 3, position: 'relative' }}>
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main', width: 64, height: 64, borderRadius: 3, border: '1px solid rgba(57, 167, 255, 0.2)' }}> <Avatar sx={{ bgcolor: 'var(--color-primary-soft)', color: 'var(--color-primary)', width: 64, height: 64, border: '1px solid var(--color-border-soft)' }} className="rounded-lg">
<ProjectIcon sx={{ fontSize: 32 }} /> <ProjectIcon sx={{ fontSize: 32 }} />
</Avatar> </Avatar>
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
<Box sx={{ mb: 2.5 }}> <Box sx={{ mb: 2.5 }}>
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: '-0.02em', mb: 0.5 }}>{p.name}</Typography> <Typography variant="h4" sx={{ letterSpacing: '-0.02em', mb: 0.5 }} className="font-black">{p.name}</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.4)', fontFamily: 'monospace' }}> <Typography variant="body2" sx={{ fontFamily: 'monospace' }} className="text-secondary">
{p.path} {p.path}
</Typography> </Typography>
<Tooltip title={t('projects.open_folder')}> <Tooltip title={t('projects.open_folder')}>
<IconButton size="small" onClick={onOpenFolder} sx={{ color: 'rgba(255,255,255,0.2)', p: 0.5, '&:hover': { color: 'primary.main', bgcolor: 'rgba(57, 167, 255, 0.1)' } }}> <IconButton size="small" onClick={onOpenFolder} sx={{ color: 'var(--color-text-tertiary)', p: 0.5, '&:hover': { color: 'var(--color-primary)', bgcolor: 'var(--color-primary-soft)' } }}>
<FolderOpenIcon sx={{ fontSize: '1rem' }} /> <FolderOpenIcon sx={{ fontSize: '1rem' }} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@@ -253,7 +253,8 @@ export default function ProjectDetails({
} }
} }
}} }}
sx={{ px: 2, height: 32, borderRadius: 1.5, fontWeight: 700, fontSize: '0.75rem', boxShadow: 'none' }} sx={{ px: 2, height: 32, boxShadow: 'none' }}
className="rounded-md font-bold text-sm"
> >
{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>
@@ -261,9 +262,10 @@ export default function ProjectDetails({
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
startIcon={<LaunchIcon sx={{ fontSize: '1rem !important' }} />} startIcon={<LaunchIcon sx={{ fontSize: 'var(--font-size-base) !important' }} />}
onClick={() => onOpenSite(isHostMapped)} onClick={() => onOpenSite(isHostMapped)}
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)' } }} sx={{ px: 2, height: 32, borderColor: 'var(--color-border-medium)', '&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(57, 167, 255, 0.05)' } }}
className="rounded-md font-semibold text-sm"
> >
{t('projects.open_site').toUpperCase()} {t('projects.open_site').toUpperCase()}
</Button> </Button>
@@ -273,7 +275,8 @@ export default function ProjectDetails({
size="small" size="small"
startIcon={<CodeIcon sx={{ fontSize: '1rem !important' }} />} startIcon={<CodeIcon sx={{ fontSize: '1rem !important' }} />}
onClick={onOpenVSCode} onClick={onOpenVSCode}
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)' } }} sx={{ px: 2, height: 32, borderColor: 'var(--color-border-medium)', color: 'var(--color-text-secondary)', '&:hover': { borderColor: '#007ACC', color: '#007ACC', bgcolor: 'rgba(0, 122, 204, 0.05)' } }}
className="rounded-md font-semibold text-sm"
> >
VS CODE AÇ VS CODE AÇ
</Button> </Button>
@@ -287,7 +290,8 @@ export default function ProjectDetails({
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: 2, height: 32, borderRadius: 1.5, fontWeight: 700, fontSize: '0.75rem', boxShadow: 'none' }} sx={{ px: 2, height: 32, boxShadow: 'none' }}
className="rounded-md font-bold text-sm"
> >
HOSTS DOSYASINA EKLE HOSTS DOSYASINA EKLE
</Button> </Button>
@@ -303,11 +307,11 @@ export default function ProjectDetails({
sx={{ sx={{
width: 40, width: 40,
height: 40, height: 40,
bgcolor: 'rgba(255, 68, 68, 0.05)', bgcolor: 'var(--color-error-soft)',
color: 'error.main', color: 'var(--color-error)',
border: '1px solid rgba(255, 68, 68, 0.1)', border: '1px solid var(--color-error-soft)',
borderRadius: 1.5, borderRadius: 'var(--radius-md)',
'&:hover': { bgcolor: 'error.main', color: '#fff', transform: 'scale(1.05) rotate(5deg)', boxShadow: '0 4px 12px rgba(255, 68, 68, 0.3)' }, '&:hover': { bgcolor: 'var(--color-error)', 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'
}} }}
> >
@@ -318,11 +322,11 @@ export default function ProjectDetails({
</Box> </Box>
{/* 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: 'var(--color-border-soft)', px: 4, bgcolor: 'var(--color-bg-dark)' }}>
<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, fontSize: '0.8rem' }} /> <Tab label={t('projects.overview').toUpperCase()} className="font-bold text-md" sx={{ 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.database').toUpperCase()} className="font-bold text-md" sx={{ py: 2.5, minWidth: 120 }} />
<Tab label={t('projects.server_settings')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120, fontSize: '0.8rem' }} /> <Tab label={t('projects.server_settings').toUpperCase()} className="font-bold text-md" sx={{ py: 2.5, minWidth: 120 }} />
</Tabs> </Tabs>
</Box> </Box>
@@ -333,8 +337,8 @@ export default function ProjectDetails({
<Grid container spacing={4}> <Grid container spacing={4}>
{/* 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: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }}>
<Typography variant="h6" sx={{ mb: 3, fontWeight: 800, color: 'primary.main', display: 'flex', alignItems: 'center', gap: 1.5, fontSize: '1rem' }}> <Typography variant="h6" className="font-black text-md" sx={{ mb: 3, color: 'var(--color-primary)', display: 'flex', alignItems: 'center', gap: 1.5 }}>
<SettingsIcon sx={{ fontSize: '1.25rem' }} /> {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>
@@ -346,7 +350,8 @@ export default function ProjectDetails({
size="small" 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)' }, '& .MuiInputLabel-root': { fontSize: '0.85rem' }, '& .MuiInputBase-input': { fontSize: '0.85rem' } }} sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
className="text-sm"
/> />
<TextField <TextField
@@ -357,7 +362,9 @@ export default function ProjectDetails({
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)' }, '& .MuiInputLabel-root': { fontSize: '0.85rem' }, '& .MuiInputBase-input': { fontSize: '0.85rem' }, '& .MuiFormHelperText-root': { fontSize: '0.7rem' } }} sx={{ borderRadius: 2, '& .MuiFilledInput-root': { bgcolor: 'rgba(0,0,0,0.2)' } }}
className="text-sm"
FormHelperTextProps={{ className: 'text-xs' }}
/> />
<Grid container spacing={2}> <Grid container spacing={2}>
@@ -410,7 +417,8 @@ export default function ProjectDetails({
startIcon={isSaving ? <CircularProgress size={16} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem !important' }} />} startIcon={isSaving ? <CircularProgress size={16} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem !important' }} />}
onClick={handleProjectUpdate} onClick={handleProjectUpdate}
disabled={isSaving} disabled={isSaving}
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' } }} sx={{ px: 4, py: 1, color: 'primary.main', border: '1px solid rgba(33, 150, 243, 0.5)', '&:hover': { bgcolor: 'rgba(33, 150, 243, 0.05)', borderColor: 'primary.main' } }}
className="rounded-lg font-bold text-sm"
> >
{t('common.save').toUpperCase()} {t('common.save').toUpperCase()}
</Button> </Button>
@@ -421,9 +429,9 @@ 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: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)', height: '100%' }}>
<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' }}> <Typography variant="subtitle1" className="font-black text-sm" sx={{ mb: 4, display: 'flex', alignItems: 'center', gap: 1.5, color: 'var(--color-text-tertiary)' }}>
<StatusIcon sx={{ color: 'secondary.main', fontSize: '1.25rem' }} /> {t('dashboard.status')} <StatusIcon sx={{ color: 'var(--color-secondary)', fontSize: '1.25rem' }} /> {t('dashboard.status')}
</Typography> </Typography>
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 4 }}>
@@ -456,20 +464,21 @@ 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', width: 48, height: 48 }}> <Avatar sx={{ bgcolor: 'var(--color-primary-soft)', color: 'var(--color-secondary)', 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, fontSize: '0.65rem' }}>{t('projects.mariadb_version')}</Typography> <Typography variant="overline" className="font-black text-2xs" sx={{ color: 'var(--color-text-tertiary)', letterSpacing: 1 }}>{t('projects.mariadb_version')}</Typography>
<Typography variant="h6" sx={{ fontWeight: 700, fontSize: '1rem' }}>MariaDB {p.mariaDbVersion}</Typography> <Typography variant="h6" className="font-bold text-base">MariaDB {p.mariaDbVersion}</Typography>
</Box> </Box>
</Box> </Box>
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
startIcon={<OpenIcon sx={{ fontSize: '1rem !important' }} />} startIcon={<OpenIcon sx={{ fontSize: 'var(--font-size-base) !important' }} />}
onClick={onManageDb} onClick={onManageDb}
sx={{ borderRadius: 1.5, px: 2, height: 32, fontWeight: 700, fontSize: '0.75rem' }} sx={{ px: 2, height: 32 }}
className="rounded-md font-bold text-sm"
> >
{t('mariadb_manager.title').toUpperCase()} {t('mariadb_manager.title').toUpperCase()}
</Button> </Button>
@@ -485,8 +494,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, fontSize: '0.65rem' }}>{item.label.toUpperCase()}</Typography> <Typography variant="caption" className="font-bold text-2xs" sx={{ color: 'var(--color-text-tertiary)', display: 'block', mb: 0.5 }}>{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', fontSize: '0.85rem' }}> <Typography variant="body1" className="font-semibold text-sm" sx={{ color: item.italic ? 'var(--color-text-tertiary)' : 'var(--color-text-primary)', fontStyle: item.italic ? 'italic' : 'normal' }}>
{item.value} {item.value}
</Typography> </Typography>
</Grid> </Grid>
@@ -499,19 +508,20 @@ 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, 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: 'var(--color-bg-soft)', borderBottom: '1px solid var(--color-border-soft)', 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" sx={{ fontSize: '1.25rem' }} /> <ServerIcon color="primary" sx={{ fontSize: '1.25rem' }} />
<Typography variant="subtitle2" sx={{ fontWeight: 800, textTransform: 'uppercase', letterSpacing: 1, fontSize: '0.75rem' }}> <Typography variant="subtitle2" className="font-black text-xs" sx={{ textTransform: 'uppercase', letterSpacing: 1 }}>
{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 sx={{ fontSize: '1rem !important' }} />} startIcon={<TuningIcon sx={{ fontSize: 'var(--font-size-base) !important' }} />}
onClick={loadConfig} onClick={loadConfig}
sx={{ color: 'rgba(255,255,255,0.5)', borderRadius: 1.5, fontSize: '0.75rem' }} sx={{ color: 'var(--color-text-secondary)' }}
className="rounded-md text-sm"
> >
{t('common.refresh')} {t('common.refresh')}
</Button> </Button>
@@ -519,10 +529,11 @@ export default function ProjectDetails({
variant="outlined" variant="outlined"
size="small" size="small"
color="primary" color="primary"
startIcon={isConfigSaving ? <CircularProgress size={14} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem !important' }} />} startIcon={isConfigSaving ? <CircularProgress size={14} color="inherit" /> : <SaveIcon sx={{ fontSize: 'var(--font-size-base) !important' }} />}
disabled={isConfigSaving || isConfigLoading} disabled={isConfigSaving || isConfigLoading}
onClick={handleSaveConfig} onClick={handleSaveConfig}
sx={{ px: 3, height: 32, borderRadius: 1.5, fontWeight: 800, fontSize: '0.75rem' }} sx={{ px: 3, height: 32 }}
className="rounded-md font-black text-sm"
> >
{t('common.save').toUpperCase()} {t('common.save').toUpperCase()}
</Button> </Button>
@@ -34,27 +34,23 @@ export default function ProjectResourceMonitor({ serverStats, phpStats, serverTy
const ramPercent = Math.min((ramMB / 1024) * 100, 100); const ramPercent = Math.min((ramMB / 1024) * 100, 100);
const getColor = (val: number) => { const getColor = (val: number) => {
if (val < 30) return '#33d9b2'; if (val < 30) return 'var(--color-success)';
if (val < 70) return '#ffb142'; if (val < 70) return 'var(--color-warning)';
return '#ff5252'; return 'var(--color-error)';
} }
return ( return (
<Box sx={{ <Box sx={{
flex: 1, flex: 1,
p: 2, p: 2,
borderRadius: 3, bgcolor: 'var(--color-bg-soft)',
bgcolor: 'rgba(255,255,255,0.02)', border: '1px solid var(--color-border-soft)',
border: '1px solid rgba(255,255,255,0.05)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
gap: 2 gap: 2
}}> }} className="rounded-lg">
<Typography variant="caption" sx={{ <Typography variant="caption" className="text-secondary font-black text-xs" sx={{
color: 'rgba(255,255,255,0.6)',
fontSize: '0.7rem',
fontWeight: 800,
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: '0.1em' letterSpacing: '0.1em'
}}> }}>
@@ -72,14 +68,14 @@ export default function ProjectResourceMonitor({ serverStats, phpStats, serverTy
innerRadius="75%" innerRadius="75%"
outerRadius="100%" outerRadius="100%"
sx={() => ({ sx={() => ({
[`& .${gaugeClasses.valueText}`]: { fontSize: 14, fontWeight: 800, fill: getColor(cpu) }, [`& .${gaugeClasses.valueText}`]: { fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-black)', fill: getColor(cpu) },
[`& .${gaugeClasses.valueArc}`]: { fill: getColor(cpu) }, [`& .${gaugeClasses.valueArc}`]: { fill: getColor(cpu) },
[`& .${gaugeClasses.referenceArc}`]: { fill: 'rgba(255,255,255,0.05)' }, [`& .${gaugeClasses.referenceArc}`]: { fill: 'var(--color-bg-medium)' },
})} })}
text={({ value }) => `${value}%`} text={({ value }) => `${value}%`}
/> />
</Box> </Box>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.3)', fontSize: '0.6rem', mt: -1 }}>CPU</Typography> <Typography variant="caption" className="text-secondary font-bold text-xs" sx={{ mt: -1 }}>CPU</Typography>
</Box> </Box>
{/* RAM Gauge */} {/* RAM Gauge */}
@@ -92,14 +88,14 @@ export default function ProjectResourceMonitor({ serverStats, phpStats, serverTy
innerRadius="75%" innerRadius="75%"
outerRadius="100%" outerRadius="100%"
sx={() => ({ sx={() => ({
[`& .${gaugeClasses.valueText}`]: { fontSize: 12, fontWeight: 800, fill: 'secondary.light' }, [`& .${gaugeClasses.valueText}`]: { fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-black)', fill: 'var(--color-secondary)' },
[`& .${gaugeClasses.valueArc}`]: { fill: 'secondary.main' }, [`& .${gaugeClasses.valueArc}`]: { fill: 'var(--color-secondary)' },
[`& .${gaugeClasses.referenceArc}`]: { fill: 'rgba(255,255,255,0.05)' }, [`& .${gaugeClasses.referenceArc}`]: { fill: 'var(--color-bg-medium)' },
})} })}
text={() => formatMemory(memory)} text={() => formatMemory(memory)}
/> />
</Box> </Box>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.3)', fontSize: '0.6rem', mt: -1 }}>RAM</Typography> <Typography variant="caption" className="text-secondary font-bold text-xs" sx={{ mt: -1 }}>RAM</Typography>
</Box> </Box>
</Box> </Box>
</Box> </Box>
@@ -53,19 +53,19 @@ export default function ProjectSidebar({
return ( return (
<Box sx={{ <Box sx={{
width: 320, width: 320,
borderRight: '1px solid rgba(255, 255, 255, 0.05)', borderRight: '1px solid var(--color-border-soft)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
bgcolor: 'rgba(255, 255, 255, 0.01)', bgcolor: 'var(--color-bg-sidebar)',
height: '100%' height: '100%'
}}> }}>
<Box sx={{ p: 2, borderBottom: '1px solid rgba(255, 255, 255, 0.05)' }}> <Box sx={{ p: 2, borderBottom: '1px solid var(--color-border-soft)' }}>
<Box sx={{ display: 'flex', gap: 1, mb: 2, alignItems: 'center' }}> <Box sx={{ display: 'flex', gap: 1, mb: 2, alignItems: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 700, flexGrow: 1, fontSize: '1.1rem' }}> <Typography variant="h6" className="font-bold text-lg" sx={{ flexGrow: 1 }}>
{t('projects.title')} {t('projects.title')}
</Typography> </Typography>
<Tooltip title={t('projects.add_new')}> <Tooltip title={t('projects.add_new')}>
<IconButton size="small" color="primary" onClick={onAddProject} sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)' }}> <IconButton size="small" color="primary" onClick={onAddProject} sx={{ bgcolor: 'var(--color-primary-soft)' }}>
<AddIcon fontSize="small" /> <AddIcon fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@@ -79,23 +79,24 @@ export default function ProjectSidebar({
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
<SearchIcon sx={{ color: 'rgba(255, 255, 255, 0.2)', fontSize: '1.1rem' }} /> <SearchIcon sx={{ color: 'var(--color-text-secondary)', fontSize: 'var(--font-size-lg)' }} />
</InputAdornment> </InputAdornment>
), ),
sx: { fontSize: '0.85rem', bgcolor: 'rgba(0,0,0,0.2)' } className: 'text-sm',
sx: { bgcolor: 'var(--color-bg-medium)' }
}} }}
/> />
</Box> </Box>
<Box sx={{ flexGrow: 1, overflowY: 'auto', p: 1 }}> <Box sx={{ flexGrow: 1, overflowY: 'auto', p: 1 }}>
{filtered.length === 0 ? ( {filtered.length === 0 ? (
<Box sx={{ p: 4, textAlign: 'center', color: 'rgba(255,255,255,0.2)' }}> <Box sx={{ p: 4, textAlign: 'center', color: 'var(--color-text-muted)' }}>
<Typography variant="body2">{t('projects.empty')}</Typography> <Typography variant="body2">{t('projects.empty')}</Typography>
</Box> </Box>
) : ( ) : (
Object.entries(grouped).sort().map(([version, projs]: [string, any]) => ( Object.entries(grouped).sort().map(([version, projs]: [string, any]) => (
<Box key={version} sx={{ mb: 2 }}> <Box key={version} sx={{ mb: 2 }}>
<Typography variant="overline" sx={{ px: 1.5, color: 'primary.main', fontWeight: 800, letterSpacing: 1 }}> <Typography variant="overline" className="font-black" sx={{ px: 1.5, color: 'var(--color-primary)', letterSpacing: 1 }}>
{version} {version}
</Typography> </Typography>
<List dense disablePadding> <List dense disablePadding>
@@ -113,11 +114,11 @@ export default function ProjectSidebar({
selected={selectedProjectId === p.id} selected={selectedProjectId === p.id}
onClick={() => setSelectedProjectId(p.id)} onClick={() => setSelectedProjectId(p.id)}
sx={{ sx={{
borderRadius: 2,
mb: 0.5, mb: 0.5,
border: selectedProjectId === p.id ? '1px solid rgba(57, 167, 255, 0.3)' : '1px solid transparent', border: selectedProjectId === p.id ? '1px solid var(--color-primary)' : '1px solid transparent',
'&.Mui-selected': { bgcolor: 'rgba(57, 167, 255, 0.08)' } '&.Mui-selected': { bgcolor: 'var(--color-primary-soft)' }
}} }}
className="rounded-md"
> >
<Box sx={{ display: 'flex', flexDirection: 'column', width: '100%', gap: 0.5 }}> <Box sx={{ display: 'flex', flexDirection: 'column', width: '100%', gap: 0.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -125,19 +126,19 @@ export default function ProjectSidebar({
width: 8, width: 8,
height: 8, height: 8,
borderRadius: '50%', borderRadius: '50%',
bgcolor: isRunning ? 'success.main' : 'error.main', bgcolor: isRunning ? 'var(--color-success)' : 'var(--color-error)',
boxShadow: isRunning ? '0 0 8px rgba(76, 175, 80, 0.5)' : 'none' boxShadow: isRunning ? '0 0 8px var(--color-success-soft)' : 'none'
}} /> }} />
<Typography variant="body2" noWrap sx={{ fontWeight: 600, flexGrow: 1 }}> <Typography variant="body2" noWrap className="font-semibold" sx={{ flexGrow: 1 }}>
{p.name} {p.name}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', fontSize: '0.7rem' }}> <Typography variant="caption" className="text-secondary text-xs">
{(p.serverType || '').toUpperCase()} • {p.host} {(p.serverType || '').toUpperCase()} • {p.host}
</Typography> </Typography>
{isRunning && ( {isRunning && (
<Typography variant="caption" sx={{ color: 'primary.light', fontSize: '0.65rem', fontWeight: 700 }}> <Typography variant="caption" className="text-sm font-bold" sx={{ color: 'var(--color-primary)' }}>
{Math.round((serverStats?.cpu || 0) + (phpStats?.cpu || 0))}% CPU {Math.round((serverStats?.cpu || 0) + (phpStats?.cpu || 0))}% CPU
</Typography> </Typography>
)} )}
@@ -4,8 +4,7 @@ import {
IconButton, IconButton,
Typography, Typography,
Paper, Paper,
Stack, Stack
Tooltip
} from '@mui/material' } from '@mui/material'
import { import {
Close as CloseIcon, Close as CloseIcon,
@@ -32,8 +31,7 @@ export default function SearchableCodeEditor({
highlight, highlight,
padding = 20, padding = 20,
style, style,
textareaClassName = "editor-textarea", textareaClassName = "editor-textarea"
languageLabel
}: SearchableCodeEditorProps) { }: SearchableCodeEditorProps) {
const [isSearchOpen, setIsSearchOpen] = useState(false); const [isSearchOpen, setIsSearchOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
@@ -139,14 +137,14 @@ export default function SearchableCodeEditor({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 1, gap: 1,
bgcolor: '#2d2d2d', bgcolor: 'var(--color-bg-dark)',
border: '1px solid rgba(255,255,255,0.1)', border: '1px solid var(--color-border-medium)',
borderRadius: 2,
minWidth: 300, minWidth: 300,
boxShadow: '0 8px 32px rgba(0,0,0,0.5)' boxShadow: '0 8px 32px rgba(0,0,0,0.5)'
}} }}
className="rounded-lg"
> >
<SearchIcon sx={{ ml: 1, opacity: 0.5, fontSize: 18 }} /> <SearchIcon sx={{ ml: 1, opacity: 0.5, fontSize: 'var(--font-size-lg)' }} />
<TextField <TextField
size="small" size="small"
placeholder="Bul..." placeholder="Bul..."
@@ -160,8 +158,7 @@ export default function SearchableCodeEditor({
sx={{ sx={{
flexGrow: 1, flexGrow: 1,
'& .MuiInputBase-root': { '& .MuiInputBase-root': {
fontSize: '0.85rem', color: 'var(--color-text-primary)'
color: '#fff'
}, },
'& .MuiOutlinedInput-notchedOutline': { '& .MuiOutlinedInput-notchedOutline': {
border: 'none' border: 'none'
@@ -170,7 +167,7 @@ export default function SearchableCodeEditor({
/> />
{searchResults.length > 0 && ( {searchResults.length > 0 && (
<Typography variant="caption" sx={{ opacity: 0.5, minWidth: 40, fontWeight: 700 }}> <Typography variant="caption" className="text-secondary font-bold" sx={{ minWidth: 40 }}>
{searchIndex + 1} / {searchResults.length} {searchIndex + 1} / {searchResults.length}
</Typography> </Typography>
)} )}
@@ -197,7 +194,7 @@ export default function SearchableCodeEditor({
padding={padding} padding={padding}
style={{ style={{
fontFamily: '"Fira Code", monospace', fontFamily: '"Fira Code", monospace',
fontSize: '15px', fontSize: '14px',
minHeight: '100%', minHeight: '100%',
...style ...style
}} }}
+23 -23
View File
@@ -258,10 +258,10 @@ export default function ServerTuning() {
return ( return (
<Box> <Box>
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 4 }}>
<Typography variant="h4" sx={{ fontWeight: 800, mb: 1, color: 'primary.light' }}> <Typography variant="h4" sx={{ color: 'var(--color-primary)', mb: 1 }} className="font-black text-3xl">
{t('tuning.title')} {t('tuning.title')}
</Typography> </Typography>
<Typography variant="body1" sx={{ color: 'rgba(255,255,255,0.6)' }}> <Typography variant="body1" sx={{ color: 'var(--color-text-secondary)' }}>
{t('tuning.desc')} {t('tuning.desc')}
</Typography> </Typography>
</Box> </Box>
@@ -269,23 +269,23 @@ export default function ServerTuning() {
<Grid container spacing={4}> <Grid container spacing={4}>
{/* PHP Section */} {/* PHP Section */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Paper sx={{ p: 3, height: '100%', bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}> <Paper sx={{ p: 3, height: '100%', bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }} className="rounded-xl">
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3, gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', mb: 3, gap: 2 }}>
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: 'primary.main', display: 'flex' }}> <Box sx={{ p: 1.5, borderRadius: 'var(--radius-md)', bgcolor: 'var(--color-primary)', display: 'flex' }}>
<PhpIcon sx={{ color: 'white' }} /> <PhpIcon sx={{ color: 'white' }} />
</Box> </Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>PHP Tuning</Typography> <Typography variant="h6" className="font-bold">PHP Tuning</Typography>
<Chip label={phpVersions.length} size="small" variant="outlined" /> <Chip label={phpVersions.length} size="small" variant="outlined" />
</Box> </Box>
<Stack spacing={2}> <Stack spacing={2}>
{phpVersions.map(v => ( {phpVersions.map(v => (
<Card key={v.id} sx={{ bgcolor: 'rgba(255,255,255,0.03)', backgroundImage: 'none', border: '1px solid rgba(255,255,255,0.05)' }}> <Card key={v.id} sx={{ bgcolor: 'var(--color-bg-soft)', backgroundImage: 'none', border: '1px solid var(--color-border-soft)' }}>
<CardActionArea onClick={() => handleOpenTuning(v, 'php')}> <CardActionArea onClick={() => handleOpenTuning(v, 'php')}>
<CardContent sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <CardContent sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box> <Box>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>PHP {v.version}</Typography> <Typography variant="subtitle1" className="font-bold">PHP {v.version}</Typography>
<Typography variant="caption" color="text.secondary">php.ini optimization</Typography> <Typography variant="caption" className="text-secondary">php.ini optimization</Typography>
</Box> </Box>
<Button variant="outlined" size="small" startIcon={<TuneIcon />}> <Button variant="outlined" size="small" startIcon={<TuneIcon />}>
{t('tuning.tune_btn')} {t('tuning.tune_btn')}
@@ -300,23 +300,23 @@ export default function ServerTuning() {
{/* MariaDB Section */} {/* MariaDB Section */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Paper sx={{ p: 3, height: '100%', bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}> <Paper sx={{ p: 3, height: '100%', bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }} className="rounded-xl">
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3, gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', mb: 3, gap: 2 }}>
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: 'secondary.main', display: 'flex' }}> <Box sx={{ p: 1.5, borderRadius: 'var(--radius-md)', bgcolor: 'var(--color-secondary)', display: 'flex' }}>
<DbIcon sx={{ color: 'white' }} /> <DbIcon sx={{ color: 'white' }} />
</Box> </Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MariaDB Tuning</Typography> <Typography variant="h6" className="font-bold">MariaDB Tuning</Typography>
<Chip label={mariaDbVersions.length} size="small" variant="outlined" /> <Chip label={mariaDbVersions.length} size="small" variant="outlined" />
</Box> </Box>
<Stack spacing={2}> <Stack spacing={2}>
{mariaDbVersions.map(v => ( {mariaDbVersions.map(v => (
<Card key={v.id} sx={{ bgcolor: 'rgba(255,255,255,0.03)', backgroundImage: 'none', border: '1px solid rgba(255,255,255,0.05)' }}> <Card key={v.id} sx={{ bgcolor: 'var(--color-bg-soft)', backgroundImage: 'none', border: '1px solid var(--color-border-soft)' }}>
<CardActionArea onClick={() => handleOpenTuning(v, 'mariadb')}> <CardActionArea onClick={() => handleOpenTuning(v, 'mariadb')}>
<CardContent sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <CardContent sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box> <Box>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>MariaDB {v.version}</Typography> <Typography variant="subtitle1" className="font-bold">MariaDB {v.version}</Typography>
<Typography variant="caption" color="text.secondary">Performance variables</Typography> <Typography variant="caption" className="text-secondary">Performance variables</Typography>
</Box> </Box>
<Button variant="outlined" color="secondary" size="small" startIcon={<TuneIcon />}> <Button variant="outlined" color="secondary" size="small" startIcon={<TuneIcon />}>
{t('tuning.tune_btn')} {t('tuning.tune_btn')}
@@ -340,20 +340,20 @@ export default function ServerTuning() {
className: 'mui-custom-dialog-paper' className: 'mui-custom-dialog-paper'
}} }}
> >
<DialogTitle sx={{ borderBottom: '1px solid rgba(255,255,255,0.1)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <DialogTitle sx={{ borderBottom: '1px solid var(--color-border-soft)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<TuneIcon color={selectedVersion?.type === 'php' ? 'primary' : 'secondary'} /> <TuneIcon color={selectedVersion?.type === 'php' ? 'primary' : 'secondary'} />
<Typography variant="h6" sx={{ fontWeight: 800 }}> <Typography variant="h6" className="font-black">
{selectedVersion?.type === 'php' ? `PHP ${selectedVersion?.version}` : `MariaDB ${selectedVersion?.version}`} Tuning {selectedVersion?.type === 'php' ? `PHP ${selectedVersion?.version}` : `MariaDB ${selectedVersion?.version}`} Tuning
</Typography> </Typography>
</Box> </Box>
<IconButton onClick={() => setIsModalOpen(false)} sx={{ color: 'rgba(255,255,255,0.5)' }}> <IconButton onClick={() => setIsModalOpen(false)} sx={{ color: 'var(--color-text-tertiary)' }}>
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box sx={{ pt: 2 }}> <Box sx={{ pt: 2 }}>
<Alert severity="info" sx={{ mb: 4, bgcolor: 'rgba(2, 136, 209, 0.1)', color: 'info.light', border: '1px solid rgba(2, 136, 209, 0.2)' }}> <Alert severity="info" sx={{ mb: 4, bgcolor: 'var(--color-primary-soft)', color: 'var(--color-primary)', border: '1px solid var(--color-border-soft)' }}>
{t('tuning.restart_notice')} {t('tuning.restart_notice')}
</Alert> </Alert>
@@ -362,9 +362,9 @@ export default function ServerTuning() {
<Box key={p.key}> <Box key={p.key}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1, alignItems: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1, alignItems: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>{p.label}</Typography> <Typography variant="subtitle2" className="font-bold">{p.label}</Typography>
<Tooltip title={t(p.description)}> <Tooltip title={t(p.description)}>
<InfoIcon sx={{ fontSize: 16, color: 'rgba(255,255,255,0.3)', cursor: 'help' }} /> <InfoIcon sx={{ color: 'var(--color-text-tertiary)', fontSize: 'var(--font-size-md)', cursor: 'help' }} />
</Tooltip> </Tooltip>
</Box> </Box>
<TextField <TextField
@@ -372,7 +372,7 @@ export default function ServerTuning() {
value={tuningValues[p.key]} value={tuningValues[p.key]}
onChange={(e) => setTuningValues({ ...tuningValues, [p.key]: parseInt(e.target.value) || p.min })} onChange={(e) => setTuningValues({ ...tuningValues, [p.key]: parseInt(e.target.value) || p.min })}
InputProps={{ InputProps={{
endAdornment: <Typography variant="caption" sx={{ ml: 1, opacity: 0.5 }}>{p.unit}</Typography> endAdornment: <Typography variant="caption" sx={{ ml: 1, color: 'var(--color-text-tertiary)' }}>{p.unit}</Typography>
} as any} } as any}
sx={{ width: 120, '& .MuiOutlinedInput-root': { height: 32 } }} sx={{ width: 120, '& .MuiOutlinedInput-root': { height: 32 } }}
/> />
@@ -391,7 +391,7 @@ export default function ServerTuning() {
} }
}} }}
/> />
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', mt: -1, display: 'block' }}> <Typography variant="caption" className="text-secondary" sx={{ mt: -1, display: 'block' }}>
{t(p.description)} {t(p.description)}
</Typography> </Typography>
</Box> </Box>
@@ -399,7 +399,7 @@ export default function ServerTuning() {
</Stack> </Stack>
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions sx={{ p: 3, borderTop: '1px solid rgba(255,255,255,0.1)' }}> <DialogActions sx={{ p: 3, borderTop: '1px solid var(--color-border-soft)' }}>
<Button onClick={() => setIsModalOpen(false)} disabled={saving}> <Button onClick={() => setIsModalOpen(false)} disabled={saving}>
{t('common.cancel')} {t('common.cancel')}
</Button> </Button>
@@ -5,9 +5,7 @@ import {
IconButton, IconButton,
Tooltip, Tooltip,
Stack, Stack,
Button,
Paper, Paper,
Divider,
Alert, Alert,
CircularProgress, CircularProgress,
Chip Chip
@@ -65,11 +63,11 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
title: t('service_monitor.clear_confirm'), title: t('service_monitor.clear_confirm'),
icon: 'warning', icon: 'warning',
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#d33', confirmButtonColor: 'var(--color-primary)',
confirmButtonText: t('common.yes_delete'), confirmButtonText: t('common.yes_delete'),
cancelButtonText: t('common.cancel'), cancelButtonText: t('common.cancel'),
background: '#1a1d21', background: 'var(--color-bg-dark)',
color: '#fff' color: 'var(--color-text-primary)'
}) })
if (result.isConfirmed && window.api) { if (result.isConfirmed && window.api) {
@@ -105,7 +103,7 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
headerName: t('service_monitor.columns.service'), headerName: t('service_monitor.columns.service'),
width: 120, width: 120,
renderCell: (params) => ( renderCell: (params) => (
<Typography variant="body2" sx={{ fontWeight: 'bold' }}> <Typography variant="body2" className="font-bold">
{params.value} {params.value}
</Typography> </Typography>
) )
@@ -125,7 +123,7 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
label={t(`common.${action}`)} label={t(`common.${action}`)}
color={color as any} color={color as any}
variant="outlined" variant="outlined"
sx={{ fontSize: 10, height: 20 }} sx={{ fontSize: '10px', height: '20px' }}
/> />
) )
} }
@@ -137,11 +135,11 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
renderCell: (params) => { renderCell: (params) => {
const status = params.value as string const status = params.value as string
return status === 'success' ? ( return status === 'success' ? (
<SuccessIcon sx={{ color: 'success.main', fontSize: 18 }} /> <SuccessIcon sx={{ color: 'var(--color-success)', fontSize: 'var(--font-size-md)' }} />
) : status === 'failure' ? ( ) : status === 'failure' ? (
<ErrorIcon sx={{ color: 'error.main', fontSize: 18 }} /> <ErrorIcon sx={{ color: 'var(--color-error)', fontSize: 'var(--font-size-md)' }} />
) : ( ) : (
<InfoIcon sx={{ color: 'info.main', fontSize: 18 }} /> <InfoIcon sx={{ color: 'var(--color-primary)', fontSize: 'var(--font-size-md)' }} />
) )
} }
}, },
@@ -151,7 +149,7 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
flex: 1, flex: 1,
renderCell: (params) => ( renderCell: (params) => (
<Tooltip title={params.value || ''}> <Tooltip title={params.value || ''}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.5)', cursor: 'default' }}> <Typography variant="caption" className="text-secondary" sx={{ cursor: 'default' }}>
{params.value || '-'} {params.value || '-'}
</Typography> </Typography>
</Tooltip> </Tooltip>
@@ -160,16 +158,16 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
] ]
return ( return (
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column', bgcolor: '#111418', color: '#fff' }}> <Box sx={{ height: '100%', display: 'flex', flexDirection: 'column', bgcolor: 'var(--color-bg-dark)', color: 'var(--color-text-primary)' }}>
{/* Header */} {/* Header */}
<Box sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '1px solid rgba(255,255,255,0.05)' }}> <Box sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '1px solid var(--color-border-soft)' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<HistoryIcon color="primary" /> <HistoryIcon color="primary" />
<Box> <Box>
<Typography variant="h6" sx={{ fontWeight: 800, lineHeight: 1 }}> <Typography variant="h6" className="font-black" sx={{ lineHeight: 1 }}>
{t('service_monitor.title')} {t('service_monitor.title')}
</Typography> </Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)' }}> <Typography variant="caption" className="text-secondary">
{t('service_monitor.desc')} {t('service_monitor.desc')}
</Typography> </Typography>
</Box> </Box>
@@ -178,7 +176,7 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
<IconButton onClick={fetchEvents} size="small" sx={{ color: 'rgba(255,255,255,0.5)' }}> <IconButton onClick={fetchEvents} size="small" sx={{ color: 'rgba(255,255,255,0.5)' }}>
<RefreshIcon fontSize="small" /> <RefreshIcon fontSize="small" />
</IconButton> </IconButton>
<IconButton onClick={handleClear} size="small" sx={{ color: '#f44336' }}> <IconButton onClick={handleClear} size="small" sx={{ color: 'var(--color-error)' }}>
<ClearIcon fontSize="small" /> <ClearIcon fontSize="small" />
</IconButton> </IconButton>
{onClose && ( {onClose && (
@@ -196,14 +194,14 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
<CircularProgress size={30} /> <CircularProgress size={30} />
</Box> </Box>
) : events.length === 0 ? ( ) : events.length === 0 ? (
<Alert severity="info" sx={{ bgcolor: 'rgba(2, 136, 209, 0.05)', color: 'info.light' }}> <Alert severity="info" sx={{ bgcolor: 'var(--color-primary-soft)', color: 'var(--color-primary)' }}>
{t('service_monitor.no_events')} {t('service_monitor.no_events')}
</Alert> </Alert>
) : ( ) : (
<Stack spacing={3}> <Stack spacing={3}>
{/* Chart Area */} {/* Chart Area */}
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 3, border: '1px solid rgba(255,255,255,0.05)' }}> <Paper sx={{ p: 2, bgcolor: 'var(--color-bg-sidebar)', borderColor: 'var(--color-border-soft)' }} className="rounded-lg">
<Typography variant="subtitle2" sx={{ mb: 2, px: 1, fontWeight: 'bold', opacity: 0.6 }}> <Typography variant="subtitle2" className="font-bold" sx={{ mb: 2, px: 1, opacity: 0.6 }}>
{t('service_monitor.chart_title')} {t('service_monitor.chart_title')}
</Typography> </Typography>
<Box sx={{ height: 200, width: '100%' }}> <Box sx={{ height: 200, width: '100%' }}>
@@ -211,24 +209,22 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
dataset={chartData} dataset={chartData}
xAxis={[{ scaleType: 'band', dataKey: 'service' }]} xAxis={[{ scaleType: 'band', dataKey: 'service' }]}
series={[ series={[
{ dataKey: 'start', label: t('common.start'), color: '#4caf50' }, { dataKey: 'start', label: t('common.start'), color: 'var(--color-success)' },
{ dataKey: 'stop', label: t('common.stop'), color: '#f44336' }, { dataKey: 'stop', label: t('common.stop'), color: 'var(--color-error)' },
{ dataKey: 'error', label: t('common.error'), color: '#ff9800' } { dataKey: 'error', label: t('common.error'), color: 'var(--color-warning)' }
]} ]}
slotProps={{ slotProps={{
legend: { legend: {
labelStyle: { fill: '#fff', fontSize: 10 }, direction: 'horizontal',
direction: 'row', position: { vertical: 'bottom', horizontal: 'center' }
position: { vertical: 'bottom', horizontal: 'middle' },
padding: -10,
} }
}} }}
margin={{ top: 10, bottom: 50, left: 30, right: 10 }} margin={{ top: 10, bottom: 50, left: 30, right: 10 }}
sx={{ sx={{
'& .MuiChartsAxis-left .MuiChartsAxis-tickLabel': { fill: 'rgba(255,255,255,0.3)' }, '& .MuiChartsAxis-left .MuiChartsAxis-tickLabel': { fill: 'var(--color-text-muted)' },
'& .MuiChartsAxis-bottom .MuiChartsAxis-tickLabel': { fill: 'rgba(255,255,255,0.3)', fontSize: 10 }, '& .MuiChartsAxis-bottom .MuiChartsAxis-tickLabel': { fill: 'var(--color-text-muted)', fontSize: 10 },
'& .MuiChartsAxis-line': { stroke: 'rgba(255,255,255,0.1)' }, '& .MuiChartsAxis-line': { stroke: 'var(--color-border-soft)' },
'& .MuiChartsAxis-tick': { stroke: 'rgba(255,255,255,0.1)' } '& .MuiChartsAxis-tick': { stroke: 'var(--color-border-soft)' }
}} }}
/> />
</Box> </Box>
@@ -244,12 +240,12 @@ export default function ServiceEventMonitor({ onClose }: { onClose?: () => void
hideFooter hideFooter
sx={{ sx={{
border: 'none', border: 'none',
color: '#fff', color: 'var(--color-text-primary)',
'& .MuiDataGrid-cell': { borderBottom: '1px solid rgba(255,255,255,0.03)', fontSize: 12 }, '& .MuiDataGrid-cell': { borderBottom: '1px solid var(--color-border-soft)', fontSize: 'var(--font-size-xs)' },
'& .MuiDataGrid-columnHeaders': { bgcolor: 'rgba(255,255,255,0.03)', borderBottom: '1px solid rgba(255,255,255,0.1)' }, '& .MuiDataGrid-columnHeaders': { bgcolor: 'var(--color-bg-soft)', borderBottom: '1px solid var(--color-border-medium)' },
'& .MuiDataGrid-columnHeaderTitle': { fontWeight: 'bold', fontSize: 11, opacity: 0.5 }, '& .MuiDataGrid-columnHeaderTitle': { fontWeight: 'bold', fontSize: 'var(--font-size-xs)', opacity: 0.5 },
'& .MuiDataGrid-virtualScroller': { bgcolor: 'transparent' }, '& .MuiDataGrid-virtualScroller': { bgcolor: 'transparent' },
'& .MuiDataGrid-footerContainer': { borderTop: '1px solid rgba(255,255,255,0.1)' } '& .MuiDataGrid-footerContainer': { borderTop: '1px solid var(--color-border-soft)' }
}} }}
/> />
</Box> </Box>
+31 -36
View File
@@ -107,9 +107,9 @@ const UserGuide = () => {
sx={{ my: 2, '& .MuiAlert-icon': { alignItems: 'center' } }} sx={{ my: 2, '& .MuiAlert-icon': { alignItems: 'center' } }}
icon={<Icon />} icon={<Icon />}
> >
{alertTitle && <AlertTitle sx={{ fontWeight: 'bold' }}>{alertTitle}</AlertTitle>} {alertTitle && <AlertTitle className="font-bold">{alertTitle}</AlertTitle>}
{alertLines.map((line, i) => ( {alertLines.map((line, i) => (
<Typography key={i} variant="body2" sx={{ mb: i === alertLines.length - 1 ? 0 : 0.5 }}> <Typography key={i} variant="body2" className="text-secondary" sx={{ mb: i === alertLines.length - 1 ? 0 : 0.5 }}>
{parseInline(line)} {parseInline(line)}
</Typography> </Typography>
))} ))}
@@ -129,7 +129,7 @@ const UserGuide = () => {
return <strong key={i}>{part.slice(2, -2)}</strong> return <strong key={i}>{part.slice(2, -2)}</strong>
} }
if (part.startsWith('`') && part.endsWith('`')) { if (part.startsWith('`') && part.endsWith('`')) {
return <code key={i} style={{ backgroundColor: 'rgba(255,255,255,0.05)', padding: '2px 4px', borderRadius: 4 }}>{part.slice(1, -1)}</code> return <code key={i} style={{ backgroundColor: 'var(--color-bg-soft)', padding: '2px 4px', borderRadius: 'var(--border-radius-sm)', border: '1px solid var(--color-border-soft)' }}>{part.slice(1, -1)}</code>
} }
return part return part
}) })
@@ -146,12 +146,13 @@ const UserGuide = () => {
key={`code-${index}`} key={`code-${index}`}
variant="outlined" variant="outlined"
sx={{ sx={{
bgcolor: '#0d1117', bgcolor: 'var(--color-bg-medium)',
p: 2, p: 2,
mb: 2, mb: 2,
overflow: 'auto', overflow: 'auto',
borderColor: 'rgba(255,255,255,0.1)' borderColor: 'var(--color-border-soft)'
}} }}
className="rounded-lg"
> >
<pre style={{ margin: 0 }}> <pre style={{ margin: 0 }}>
<code className={`language-${codeLang || 'plaintext'}`}> <code className={`language-${codeLang || 'plaintext'}`}>
@@ -215,11 +216,11 @@ const UserGuide = () => {
sx={{ sx={{
mt: level === 1 ? 0 : 4, mt: level === 1 ? 0 : 4,
mb: 2, mb: 2,
fontWeight: level === 1 ? 800 : 700, color: level === 1 ? 'var(--color-primary)' : 'white',
color: level === 1 ? 'primary.light' : '#fff', borderBottom: level === 1 ? '1px solid var(--color-border-soft)' : 'none',
borderBottom: level === 1 ? '1px solid rgba(255,255,255,0.1)' : 'none',
pb: level === 1 ? 1 : 0 pb: level === 1 ? 1 : 0
}} }}
className={level === 1 ? 'font-black' : 'font-bold'}
> >
{parseInline(content)} {parseInline(content)}
</Typography> </Typography>
@@ -232,7 +233,7 @@ const UserGuide = () => {
flushAlert() flushAlert()
const content = trimmedLine.slice(2) const content = trimmedLine.slice(2)
currentList.push( currentList.push(
<Box component="li" key={`li-${index}`} sx={{ mb: 1, color: 'rgba(255,255,255,0.9)' }}> <Box component="li" key={`li-${index}`} sx={{ mb: 1 }} className="text-secondary">
<Typography variant="body2">{parseInline(content)}</Typography> <Typography variant="body2">{parseInline(content)}</Typography>
</Box> </Box>
) )
@@ -254,10 +255,9 @@ const UserGuide = () => {
variant="body1" variant="body1"
sx={{ sx={{
mb: 2, mb: 2,
lineHeight: 1.7, lineHeight: 1.7
color: 'rgba(255,255,255,0.8)',
fontSize: '0.95rem'
}} }}
className="text-secondary text-md"
> >
{parseInline(trimmedLine)} {parseInline(trimmedLine)}
</Typography> </Typography>
@@ -279,12 +279,12 @@ const UserGuide = () => {
<Box sx={{ height: 'calc(100vh - 140px)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}> <Box sx={{ height: 'calc(100vh - 140px)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
{/* Breadcrumbs */} {/* Breadcrumbs */}
<Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}> <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}>
<Breadcrumbs separator={<ChevronRightIcon sx={{ fontSize: '1rem', color: 'rgba(255,255,255,0.3)' }} />} aria-label="breadcrumb"> <Breadcrumbs separator={<ChevronRightIcon sx={{ fontSize: 'var(--font-size-md)', color: 'var(--color-border-medium)' }} />} aria-label="breadcrumb">
<Link underline="hover" color="inherit" sx={{ display: 'flex', alignItems: 'center', color: 'rgba(255,255,255,0.5)', fontSize: '0.85rem' }}> <Link underline="hover" color="inherit" sx={{ display: 'flex', alignItems: 'center' }} className="text-secondary text-sm">
<GuideIcon sx={{ mr: 0.5, fontSize: '1.1rem' }} /> <GuideIcon sx={{ mr: 0.5, fontSize: 'var(--font-size-lg)' }} />
{t('common.user_guide')} {t('common.user_guide')}
</Link> </Link>
<Typography color="primary.light" sx={{ fontSize: '0.85rem', fontWeight: 'bold' }}> <Typography color="primary.light" className="text-sm font-bold">
{topics.find(t => t.file === selectedTopic)?.title || selectedTopic} {topics.find(t => t.file === selectedTopic)?.title || selectedTopic}
</Typography> </Typography>
</Breadcrumbs> </Breadcrumbs>
@@ -296,14 +296,10 @@ const UserGuide = () => {
<Paper <Paper
variant="outlined" variant="outlined"
sx={{ sx={{
bgcolor: 'rgba(255,255,255,0.02)', bgcolor: 'var(--color-bg-sidebar)',
borderColor: 'rgba(255,255,255,0.05)', borderColor: 'var(--color-border-soft)'
borderRadius: 3,
overflow: 'hidden',
height: '100%',
display: 'flex',
flexDirection: 'column'
}} }}
className="rounded-lg overflow-hidden flex flex-col h-full"
> >
<Box sx={{ p: 2, pb: 1 }}> <Box sx={{ p: 2, pb: 1 }}>
<TextField <TextField
@@ -315,20 +311,20 @@ const UserGuide = () => {
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
<SearchIcon sx={{ color: 'rgba(255,255,255,0.3)', fontSize: '1.2rem' }} /> <SearchIcon sx={{ color: 'var(--color-text-secondary)', fontSize: 'var(--font-size-lg)' }} />
</InputAdornment> </InputAdornment>
), ),
}} }}
sx={{ sx={{
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
bgcolor: 'rgba(0,0,0,0.2)', bgcolor: 'var(--color-bg-medium)',
borderRadius: 2, borderRadius: 'var(--radius-md)',
'& fieldset': { borderColor: 'rgba(255,255,255,0.05)' }, '& fieldset': { borderColor: 'var(--color-border-soft)' },
} }
}} }}
/> />
</Box> </Box>
<Typography variant="overline" sx={{ px: 2, pt: 1, display: 'block', color: 'primary.main', fontWeight: 800 }}> <Typography variant="overline" className="font-black text-primary" sx={{ px: 2, pt: 1, display: 'block' }}>
{t('common.topics', 'KONULAR')} {t('common.topics', 'KONULAR')}
</Typography> </Typography>
<List sx={{ pt: 1, flexGrow: 1, overflowY: 'auto' }}> <List sx={{ pt: 1, flexGrow: 1, overflowY: 'auto' }}>
@@ -340,17 +336,16 @@ const UserGuide = () => {
sx={{ sx={{
py: 1.5, py: 1.5,
borderLeft: '3px solid', borderLeft: '3px solid',
borderColor: selectedTopic === topic.file ? 'primary.main' : 'transparent', borderColor: selectedTopic === topic.file ? 'var(--color-primary)' : 'transparent',
bgcolor: selectedTopic === topic.file ? 'rgba(57, 167, 255, 0.05)' : 'transparent', bgcolor: selectedTopic === topic.file ? 'var(--color-primary-soft)' : 'transparent',
'&:hover': { bgcolor: 'rgba(255,255,255,0.05)' } '&:hover': { bgcolor: 'var(--color-bg-soft)' }
}} }}
> >
<ListItemText <ListItemText
primary={topic.title} primary={topic.title}
primaryTypographyProps={{ primaryTypographyProps={{
variant: 'body2', variant: 'body2',
fontWeight: selectedTopic === topic.file ? 700 : 500, className: selectedTopic === topic.file ? 'font-bold text-white' : 'text-secondary font-medium'
color: selectedTopic === topic.file ? '#fff' : 'rgba(255,255,255,0.6)'
}} }}
/> />
</ListItemButton> </ListItemButton>
@@ -366,12 +361,12 @@ const UserGuide = () => {
elevation={0} elevation={0}
sx={{ sx={{
p: { xs: 3, md: 5 }, p: { xs: 3, md: 5 },
bgcolor: 'rgba(0,0,0,0.2)', bgcolor: 'var(--color-bg-soft)',
borderRadius: 4,
minHeight: '100%', minHeight: '100%',
border: '1px solid rgba(255,255,255,0.03)', border: '1px solid var(--color-border-soft)',
position: 'relative' position: 'relative'
}} }}
className="rounded-xl"
> >
{loading ? ( {loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '200px' }}> <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '200px' }}>
+3 -1
View File
@@ -45,7 +45,9 @@
"browse": "Durchsuchen", "browse": "Durchsuchen",
"reset_confirm_title": "Sind Sie sicher?", "reset_confirm_title": "Sind Sie sicher?",
"reset_confirm_text": "Möchten Sie die {{service}}-Daten zurücksetzen? Dieser Vorgang löscht alle Daten und führt eine Neuinstallation durch.", "reset_confirm_text": "Möchten Sie die {{service}}-Daten zurücksetzen? Dieser Vorgang löscht alle Daten und führt eine Neuinstallation durch.",
"reset_btn": "Ja, Zurücksetzen" "reset_btn": "Ja, Zurücksetzen",
"status": "Status",
"admin_required": "Bitte versuchen Sie, die Anwendung als Administrator auszuführen."
}, },
"dashboard": { "dashboard": {
"title": "Trunçgil Multi-PHP Server", "title": "Trunçgil Multi-PHP Server",
+3 -1
View File
@@ -51,7 +51,9 @@
"reset_confirm_text": "Do you want to reset {{service}} data? This operation deletes all data and performs a clean installation.", "reset_confirm_text": "Do you want to reset {{service}} data? This operation deletes all data and performs a clean installation.",
"reset_btn": "Yes, Reset", "reset_btn": "Yes, Reset",
"operating": "Operation in progress...", "operating": "Operation in progress...",
"please_wait": "Please wait..." "please_wait": "Please wait...",
"status": "Status",
"admin_required": "Please try running the application as an administrator."
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
+2 -1
View File
@@ -52,7 +52,8 @@
"reset_btn": "Да, сбросить", "reset_btn": "Да, сбросить",
"operating": "Операция выполняется...", "operating": "Операция выполняется...",
"please_wait": "Пожалуйста, подождите...", "please_wait": "Пожалуйста, подождите...",
"status": "Статус" "status": "Статус",
"admin_required": "Пожалуйста, попробуйте запустить приложение от имени администратора."
}, },
"dashboard": { "dashboard": {
"title": "Панель управления", "title": "Панель управления",
+15 -1
View File
@@ -7,6 +7,7 @@
"restart": "Yeniden Başlat", "restart": "Yeniden Başlat",
"settings": "Ayarlar", "settings": "Ayarlar",
"tools": "Araçlar", "tools": "Araçlar",
"open": "Aç",
"close": "Kapat", "close": "Kapat",
"quit": "Çıkış", "quit": "Çıkış",
"save": "Kaydet", "save": "Kaydet",
@@ -52,7 +53,8 @@
"reset_btn": "Evet, Sıfırla", "reset_btn": "Evet, Sıfırla",
"operating": "İşlem devam ediyor...", "operating": "İşlem devam ediyor...",
"please_wait": "Lütfen bekleyin...", "please_wait": "Lütfen bekleyin...",
"status": "Durum" "status": "Durum",
"admin_required": "Lütfen uygulamayı yönetici olarak çalıştırmayı deneyin."
}, },
"dashboard": { "dashboard": {
"title": "Özet", "title": "Özet",
@@ -75,6 +77,7 @@
}, },
"projects": { "projects": {
"title": "Projelerim", "title": "Projelerim",
"site_name": "Site Adı",
"add_new": "Yeni Proje Ekle", "add_new": "Yeni Proje Ekle",
"search": "Proje veya host ara...", "search": "Proje veya host ara...",
"edit": "Proje Düzenle", "edit": "Proje Düzenle",
@@ -356,5 +359,16 @@
"delete_db_confirm": "'{{db}}' veritabanını silmek istediğinize emin misiniz? Bu işlem geri alınamaz!", "delete_db_confirm": "'{{db}}' veritabanını silmek istediğinize emin misiniz? Bu işlem geri alınamaz!",
"delete_user_confirm": "'{{user}}' kullanıcısını silmek istediğinize emin misiniz?", "delete_user_confirm": "'{{user}}' kullanıcısını silmek istediğinize emin misiniz?",
"no_active_version": "Lütfen önce yüklü bir MariaDB sürümü seçin veya bir sürüm indirin." "no_active_version": "Lütfen önce yüklü bir MariaDB sürümü seçin veya bir sürüm indirin."
},
"updates": {
"title": "Güncellemeler",
"check_btn": "Güncellemeleri Denetle",
"checking": "Güncellemeler denetleniyor...",
"available": "Yeni bir güncelleme mevcut: {{version}}",
"not_available": "Uygulama zaten güncel.",
"downloading": "Güncelleme indiriliyor...",
"downloaded": "Güncelleme indirildi. Yüklemek için yeniden başlatın.",
"install_btn": "Yeniden Başlat ve Yükle",
"error": "Güncelleme hatası: {{error}}"
} }
} }
+67
View File
@@ -1,6 +1,73 @@
import { GlobalStyles as MuiGlobalStyles } from '@mui/material' import { GlobalStyles as MuiGlobalStyles } from '@mui/material'
const styles = { const styles = {
':root': {
'--font-size-2xs': '0.65rem',
'--font-size-xs': '0.7rem',
'--font-size-sm': '0.8rem',
'--font-size-md': '0.9rem',
'--font-size-base': '1rem',
'--font-size-lg': '1.25rem',
'--font-size-xl': '1.5rem',
'--font-size-2xl': '2rem',
'--font-size-3xl': '3rem',
'--font-size-4xl': '4rem',
'--font-weight-thin': '300',
'--font-weight-normal': '400',
'--font-weight-medium': '500',
'--font-weight-semibold': '600',
'--font-weight-bold': '700',
'--font-weight-black': '800',
'--radius-sm': '4px',
'--radius-md': '8px',
'--radius-lg': '12px',
'--radius-xl': '16px',
'--color-primary': '#39A7FF',
'--color-primary-soft': 'rgba(57, 167, 255, 0.1)',
'--color-secondary': '#33d9b2',
'--color-secondary-soft': 'rgba(51, 217, 178, 0.1)',
'--color-success': '#33d9b2',
'--color-success-soft': 'rgba(51, 217, 178, 0.1)',
'--color-warning': '#ffb142',
'--color-warning-soft': 'rgba(255, 177, 66, 0.1)',
'--color-error': '#ff5252',
'--color-error-soft': 'rgba(255, 82, 82, 0.1)',
'--color-bg-dark': '#121212',
'--color-bg-navbar': 'rgba(21, 62, 94, 0.85)',
'--color-bg-sidebar': 'rgba(18, 18, 18, 0.95)',
'--color-bg-soft': 'rgba(255, 255, 255, 0.03)',
'--color-bg-medium': 'rgba(0, 0, 0, 0.2)',
'--color-border-soft': 'rgba(255, 255, 255, 0.05)',
'--color-border-medium': 'rgba(255, 255, 255, 0.1)',
'--color-text-primary': '#ffffff',
'--color-text-secondary': 'rgba(255, 255, 255, 0.7)',
'--color-text-tertiary': 'rgba(255, 255, 255, 0.5)',
'--color-text-muted': 'rgba(255, 255, 255, 0.3)',
},
'.text-xs': { fontSize: 'var(--font-size-xs) !important' },
'.text-sm': { fontSize: 'var(--font-size-sm) !important' },
'.text-md': { fontSize: 'var(--font-size-md) !important' },
'.text-base': { fontSize: 'var(--font-size-base) !important' },
'.text-lg': { fontSize: 'var(--font-size-lg) !important' },
'.text-xl': { fontSize: 'var(--font-size-xl) !important' },
'.font-normal': { fontWeight: 'var(--font-weight-normal) !important' },
'.font-medium': { fontWeight: 'var(--font-weight-medium) !important' },
'.font-semibold': { fontWeight: 'var(--font-weight-semibold) !important' },
'.font-bold': { fontWeight: 'var(--font-weight-bold) !important' },
'.font-black': { fontWeight: 'var(--font-weight-black) !important' },
'.text-secondary': { color: 'var(--color-text-secondary) !important' },
'.text-muted': { color: 'var(--color-text-muted) !important' },
'.border-soft': { border: '1px solid var(--color-border-soft) !important' },
'.border-medium': { border: '1px solid var(--color-border-medium) !important' },
'.bg-soft': { backgroundColor: 'var(--color-bg-soft) !important' },
'.bg-medium': { backgroundColor: 'var(--color-bg-medium) !important' },
'.rounded-sm': { borderRadius: 'var(--radius-sm) !important' },
'.rounded-md': { borderRadius: 'var(--radius-md) !important' },
'.rounded-lg': { borderRadius: 'var(--radius-lg) !important' },
'.rounded-xl': { borderRadius: 'var(--radius-xl) !important' },
'.flex-center': { display: 'flex', alignItems: 'center', justifyContent: 'center' },
'.flex-between': { display: 'flex', alignItems: 'center', justifyContent: 'space-between' },
'.flex-align-center': { display: 'flex', alignItems: 'center' },
'html, body, #root': { 'html, body, #root': {
margin: 0, margin: 0,
padding: 0, padding: 0,