feat: implement core architecture and dashboard UI for service and project management
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
[mysqld]
|
[mysqld]
|
||||||
port=3306
|
port={{PORT}}
|
||||||
datadir="{{DATADIR}}"
|
datadir="{{DATADIR}}"
|
||||||
socket="{{SOCKET}}"
|
socket="{{SOCKET}}"
|
||||||
character-set-server=utf8mb4
|
character-set-server=utf8mb4
|
||||||
collation-server=utf8mb4_unicode_ci
|
collation-server=utf8mb4_unicode_ci
|
||||||
|
|
||||||
[client]
|
[client]
|
||||||
port=3306
|
port={{PORT}}
|
||||||
socket="{{SOCKET}}"
|
socket="{{SOCKET}}"
|
||||||
default-character-set=utf8mb4
|
default-character-set=utf8mb4
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
fastcgi_pass 127.0.0.1:9001;
|
fastcgi_pass 127.0.0.1:{{PHP_PORT}};
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
|
|||||||
+53
-11
@@ -1,4 +1,5 @@
|
|||||||
import { ipcMain, app, dialog } from 'electron'
|
import { ipcMain, app, dialog } from 'electron'
|
||||||
|
import fs from 'fs'
|
||||||
import { processManager } from '../services/ProcessManager'
|
import { processManager } from '../services/ProcessManager'
|
||||||
import { configService } from '../services/ConfigService'
|
import { configService } from '../services/ConfigService'
|
||||||
import { downloadService } from '../services/DownloadService'
|
import { downloadService } from '../services/DownloadService'
|
||||||
@@ -7,37 +8,78 @@ import { phpManagerService } from '../services/PhpManagerService'
|
|||||||
import { mySqlManagerService } from '../services/MySqlManagerService'
|
import { mySqlManagerService } from '../services/MySqlManagerService'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
function findExecutable(dir: string, name: string): string | null {
|
||||||
|
if (!fs.existsSync(dir)) return null
|
||||||
|
const files = fs.readdirSync(dir)
|
||||||
|
for (const file of files) {
|
||||||
|
const fullPath = path.join(dir, file)
|
||||||
|
const stat = fs.statSync(fullPath)
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const found = findExecutable(fullPath, name)
|
||||||
|
if (found) return found
|
||||||
|
} else if (file.toLowerCase() === name.toLowerCase()) {
|
||||||
|
return fullPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export function registerIpcHandlers(): void {
|
export function registerIpcHandlers(): void {
|
||||||
// Service management
|
// Service management
|
||||||
ipcMain.handle('services:start', async (_event, serviceName: string) => {
|
ipcMain.handle('services:start', async (_event, serviceName: string) => {
|
||||||
try {
|
try {
|
||||||
|
const settings = configService.getSettings()
|
||||||
|
const binDir = path.join(app.getPath('userData'), 'bin')
|
||||||
|
|
||||||
if (serviceName === 'nginx') {
|
if (serviceName === 'nginx') {
|
||||||
const rootPath = path.join(app.getAppPath(), 'www')
|
const rootPath = path.join(app.getAppPath(), 'www')
|
||||||
const configPath = await configService.generateConfig('nginx.conf.template', 'nginx.conf', {
|
const configPath = await configService.generateConfig('nginx.conf.template', 'nginx.conf', {
|
||||||
PORT: '80',
|
PORT: settings.nginxPort,
|
||||||
|
PHP_PORT: settings.phpPort,
|
||||||
ROOT: rootPath
|
ROOT: rootPath
|
||||||
})
|
})
|
||||||
const binPath = downloadService.getBinPath('nginx', 'nginx.exe')
|
|
||||||
|
const nginxDir = path.join(binDir, 'nginx')
|
||||||
|
const binPath = findExecutable(nginxDir, 'nginx.exe')
|
||||||
|
|
||||||
|
if (!binPath) return { success: false, message: 'Nginx executable bulunamadı. Lütfen indirin.' }
|
||||||
|
|
||||||
const success = await processManager.startService('nginx', binPath, ['-c', configPath])
|
const success = await processManager.startService('nginx', binPath, ['-c', configPath])
|
||||||
return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx başlatılamadı.' }
|
return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx dosyaları bulunamadı veya port meşgul.' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceName === 'php') {
|
if (serviceName === 'php') {
|
||||||
const configPath = await configService.generateConfig('php.ini.template', 'php.ini', {})
|
const configPath = await configService.generateConfig('php.ini.template', 'php.ini', {})
|
||||||
const binPath = downloadService.getBinPath('php8.2', 'php-cgi.exe')
|
// Find any installed PHP version
|
||||||
const success = await processManager.startService('php', binPath, ['-b', '127.0.0.1:9001', '-c', configPath])
|
const binPath = findExecutable(binDir, 'php-cgi.exe')
|
||||||
return { success, message: success ? 'PHP başlatıldı.' : 'PHP başlatılamadı.' }
|
|
||||||
|
if (!binPath) return { success: false, message: 'PHP executable bulunamadı. Lütfen en az bir sürüm indirin.' }
|
||||||
|
|
||||||
|
const success = await processManager.startService('php', binPath, ['-b', `127.0.0.1:${settings.phpPort}`, '-c', configPath])
|
||||||
|
return { success, message: success ? 'PHP başlatıldı.' : 'PHP dosyaları bulunamadı veya port meşgul.' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceName === 'mysql') {
|
if (serviceName === 'mysql') {
|
||||||
const dataDir = path.join(app.getPath('userData'), 'mysql_data')
|
const dataDir = path.join(app.getPath('userData'), 'mysql_data')
|
||||||
|
const binPath = findExecutable(binDir, 'mysqld.exe')
|
||||||
|
|
||||||
|
if (!binPath) return { success: false, message: 'MySQL executable bulunamadı. Lütfen indirin.' }
|
||||||
|
|
||||||
|
// Initialize if data directory is empty
|
||||||
|
if (!fs.existsSync(dataDir) || fs.readdirSync(dataDir).length === 0) {
|
||||||
|
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
|
||||||
|
console.log('Initializing MySQL data directory...')
|
||||||
|
await processManager.runOnce(binPath, ['--initialize-insecure', `--datadir=${dataDir.replace(/\\/g, '/')}`])
|
||||||
|
}
|
||||||
|
|
||||||
const configPath = await configService.generateConfig('my.ini.template', 'my.ini', {
|
const configPath = await configService.generateConfig('my.ini.template', 'my.ini', {
|
||||||
|
PORT: settings.mysqlPort,
|
||||||
DATADIR: dataDir,
|
DATADIR: dataDir,
|
||||||
SOCKET: '/tmp/mysql.sock'
|
SOCKET: '/tmp/mysql.sock'
|
||||||
})
|
})
|
||||||
const binPath = downloadService.getBinPath('mysql', 'bin/mysqld.exe')
|
|
||||||
const success = await processManager.startService('mysql', binPath, ['--defaults-file=' + configPath])
|
const success = await processManager.startService('mysql', binPath, ['--defaults-file=' + configPath])
|
||||||
return { success, message: success ? 'MySQL başlatıldı.' : 'MySQL başlatılamadı.' }
|
return { success, message: success ? 'MySQL başlatıldı.' : 'MySQL dosyaları bulunamadı veya port meşgul.' }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: false, message: 'Bilinmeyen servis.' }
|
return { success: false, message: 'Bilinmeyen servis.' }
|
||||||
@@ -140,11 +182,11 @@ export function registerIpcHandlers(): void {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Config management
|
// Config management
|
||||||
ipcMain.handle('config:get', async (_event, key: string) => {
|
ipcMain.handle('config:get', async () => {
|
||||||
return null
|
return configService.getSettings()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('config:save', async (_event, config: any) => {
|
ipcMain.handle('config:save', async (_event, config: any) => {
|
||||||
return { success: true }
|
return { success: true, settings: configService.updateSettings(config) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,26 @@ import { app } from 'electron'
|
|||||||
export class ConfigService {
|
export class ConfigService {
|
||||||
private configDir: string
|
private configDir: string
|
||||||
private templateDir: string
|
private templateDir: string
|
||||||
|
private settingsPath: string
|
||||||
|
private defaultSettings = {
|
||||||
|
nginxPort: '80',
|
||||||
|
phpPort: '9001',
|
||||||
|
mysqlPort: '3306',
|
||||||
|
language: 'tr'
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.templateDir = path.join(app.getAppPath(), 'config')
|
this.templateDir = path.join(app.getAppPath(), 'config')
|
||||||
this.configDir = path.join(app.getPath('userData'), 'generated_configs')
|
this.configDir = path.join(app.getPath('userData'), 'generated_configs')
|
||||||
|
this.settingsPath = path.join(app.getPath('userData'), 'settings.json')
|
||||||
|
|
||||||
if (!fs.existsSync(this.configDir)) {
|
if (!fs.existsSync(this.configDir)) {
|
||||||
fs.mkdirSync(this.configDir, { recursive: true })
|
fs.mkdirSync(this.configDir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(this.settingsPath)) {
|
||||||
|
fs.writeFileSync(this.settingsPath, JSON.stringify(this.defaultSettings, null, 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateConfig(templateName: string, targetName: string, variables: Record<string, string>): Promise<string> {
|
async generateConfig(templateName: string, targetName: string, variables: Record<string, string>): Promise<string> {
|
||||||
@@ -37,6 +49,30 @@ export class ConfigService {
|
|||||||
getGeneratedPath(targetName: string): string {
|
getGeneratedPath(targetName: string): string {
|
||||||
return path.join(this.configDir, targetName)
|
return path.join(this.configDir, targetName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSettings(): any {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(this.settingsPath)) {
|
||||||
|
const data = fs.readFileSync(this.settingsPath, 'utf-8')
|
||||||
|
return { ...this.defaultSettings, ...JSON.parse(data) }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to read settings:', e)
|
||||||
|
}
|
||||||
|
return this.defaultSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSettings(newSettings: any) {
|
||||||
|
try {
|
||||||
|
const current = this.getSettings()
|
||||||
|
const updated = { ...current, ...newSettings }
|
||||||
|
fs.writeFileSync(this.settingsPath, JSON.stringify(updated, null, 2))
|
||||||
|
return updated
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save settings:', e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const configService = new ConfigService()
|
export const configService = new ConfigService()
|
||||||
|
|||||||
@@ -10,6 +10,18 @@ export class ProcessManager {
|
|||||||
this.processes = new Map()
|
this.processes = new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async runOnce(binPath: string, args: string[]): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const child = spawn(binPath, args, { stdio: 'inherit' })
|
||||||
|
child.on('close', (code) => {
|
||||||
|
resolve(code === 0)
|
||||||
|
})
|
||||||
|
child.on('error', () => {
|
||||||
|
resolve(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async startService(name: string, binPath: string, args: string[]): Promise<boolean> {
|
async startService(name: string, binPath: string, args: string[]): Promise<boolean> {
|
||||||
if (this.processes.has(name)) {
|
if (this.processes.has(name)) {
|
||||||
console.log(`Service ${name} is already running.`)
|
console.log(`Service ${name} is already running.`)
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ const api = {
|
|||||||
getMySqlVersions: () => electronAPI.ipcRenderer.invoke('mysql:versions'),
|
getMySqlVersions: () => electronAPI.ipcRenderer.invoke('mysql:versions'),
|
||||||
downloadMySqlVersion: (id: string) => electronAPI.ipcRenderer.invoke('mysql:download', id),
|
downloadMySqlVersion: (id: string) => electronAPI.ipcRenderer.invoke('mysql:download', id),
|
||||||
onMySqlProgress: (callback: any) => electronAPI.ipcRenderer.on('mysql:progress', (_event, data) => callback(data)),
|
onMySqlProgress: (callback: any) => electronAPI.ipcRenderer.on('mysql:progress', (_event, data) => callback(data)),
|
||||||
selectDirectory: () => electronAPI.ipcRenderer.invoke('dialog:select-directory')
|
selectDirectory: () => electronAPI.ipcRenderer.invoke('dialog:select-directory'),
|
||||||
|
getSettings: () => electronAPI.ipcRenderer.invoke('config:get'),
|
||||||
|
saveSettings: (settings: any) => electronAPI.ipcRenderer.invoke('config:save', settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use `contextBridge` APIs to expose Electron APIs to
|
// Use `contextBridge` APIs to expose Electron APIs to
|
||||||
|
|||||||
+85
-10
@@ -33,7 +33,8 @@ import {
|
|||||||
Tabs,
|
Tabs,
|
||||||
Tab,
|
Tab,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
CircularProgress
|
CircularProgress,
|
||||||
|
Stack
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import {
|
import {
|
||||||
Dashboard as DashboardIcon,
|
Dashboard as DashboardIcon,
|
||||||
@@ -50,7 +51,9 @@ import {
|
|||||||
CheckCircle as InstalledIcon,
|
CheckCircle as InstalledIcon,
|
||||||
CloudDownload as AvailableIcon,
|
CloudDownload as AvailableIcon,
|
||||||
Refresh as RefreshIcon,
|
Refresh as RefreshIcon,
|
||||||
FolderOpen as FolderOpenIcon
|
FolderOpen as FolderOpenIcon,
|
||||||
|
Save as SaveIcon,
|
||||||
|
Terminal as TerminalIcon
|
||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
@@ -68,6 +71,13 @@ interface ServiceStatus {
|
|||||||
mysql: string
|
mysql: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AppSettings {
|
||||||
|
nginxPort: string
|
||||||
|
phpPort: string
|
||||||
|
mysqlPort: string
|
||||||
|
language: string
|
||||||
|
}
|
||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const [status, setStatus] = useState<ServiceStatus>({
|
const [status, setStatus] = useState<ServiceStatus>({
|
||||||
@@ -83,6 +93,12 @@ function App(): JSX.Element {
|
|||||||
const [loadingVersions, setLoadingVersions] = useState(false)
|
const [loadingVersions, setLoadingVersions] = useState(false)
|
||||||
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false)
|
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false)
|
||||||
const [newProject, setNewProject] = useState({ name: '', path: '', phpVersion: '', host: 'localhost' })
|
const [newProject, setNewProject] = useState({ name: '', path: '', phpVersion: '', host: 'localhost' })
|
||||||
|
const [settings, setSettings] = useState<AppSettings>({
|
||||||
|
nginxPort: '80',
|
||||||
|
phpPort: '9001',
|
||||||
|
mysqlPort: '3306',
|
||||||
|
language: 'tr'
|
||||||
|
})
|
||||||
const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({
|
const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({
|
||||||
open: false,
|
open: false,
|
||||||
message: '',
|
message: '',
|
||||||
@@ -96,6 +112,13 @@ function App(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchSettings = async () => {
|
||||||
|
if (window.api && window.api.getSettings) {
|
||||||
|
const s = await window.api.getSettings()
|
||||||
|
if (s) setSettings(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
if (window.api && window.api.getProjects) {
|
if (window.api && window.api.getProjects) {
|
||||||
const list = await window.api.getProjects()
|
const list = await window.api.getProjects()
|
||||||
@@ -142,6 +165,7 @@ function App(): JSX.Element {
|
|||||||
fetchProjects()
|
fetchProjects()
|
||||||
fetchPhpVersions()
|
fetchPhpVersions()
|
||||||
fetchMySqlVersions()
|
fetchMySqlVersions()
|
||||||
|
fetchSettings()
|
||||||
|
|
||||||
const removePhpListener = window.api.onBinaryProgress ? window.api.onBinaryProgress((data) => {
|
const removePhpListener = window.api.onBinaryProgress ? window.api.onBinaryProgress((data) => {
|
||||||
if (!data) return
|
if (!data) return
|
||||||
@@ -160,7 +184,7 @@ function App(): JSX.Element {
|
|||||||
fetchProjects()
|
fetchProjects()
|
||||||
fetchPhpVersions()
|
fetchPhpVersions()
|
||||||
fetchMySqlVersions()
|
fetchMySqlVersions()
|
||||||
}, 3000)
|
}, 5000)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
@@ -191,6 +215,15 @@ function App(): JSX.Element {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSaveSettings = async () => {
|
||||||
|
if (window.api && window.api.saveSettings) {
|
||||||
|
const result = await window.api.saveSettings(settings)
|
||||||
|
if (result.success) {
|
||||||
|
setNotification({ open: true, message: 'Ayarlar kaydedildi.', severity: 'success' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toggleLanguage = () => {
|
const toggleLanguage = () => {
|
||||||
const newLang = i18n.language === 'tr' ? 'en' : i18n.language === 'en' ? 'de' : 'tr'
|
const newLang = i18n.language === 'tr' ? 'en' : i18n.language === 'en' ? 'de' : 'tr'
|
||||||
i18n.changeLanguage(newLang)
|
i18n.changeLanguage(newLang)
|
||||||
@@ -326,15 +359,18 @@ function App(): JSX.Element {
|
|||||||
<Typography variant="h5" sx={{ fontWeight: 'medium' }}>
|
<Typography variant="h5" sx={{ fontWeight: 'medium' }}>
|
||||||
Genel Ayarlar
|
Genel Ayarlar
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button startIcon={loadingVersions ? <CircularProgress size={20} /> : <RefreshIcon />} onClick={handleRefreshVersions} disabled={loadingVersions}>
|
{settingsTab < 2 && (
|
||||||
Listeyi Yenile
|
<Button startIcon={loadingVersions ? <CircularProgress size={20} /> : <RefreshIcon />} onClick={handleRefreshVersions} disabled={loadingVersions}>
|
||||||
</Button>
|
Listeyi Yenile
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
|
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
|
||||||
<Tabs value={settingsTab} onChange={(_e, v) => setSettingsTab(v)} textColor="primary" indicatorColor="primary">
|
<Tabs value={settingsTab} onChange={(_e, v) => setSettingsTab(v)} textColor="primary" indicatorColor="primary" variant="scrollable" scrollButtons="auto">
|
||||||
<Tab label="PHP Sürümleri" icon={<PhpIcon />} iconPosition="start" />
|
<Tab label="PHP Sürümleri" icon={<PhpIcon />} iconPosition="start" />
|
||||||
<Tab label="MySQL Sürümleri" icon={<DbIcon />} iconPosition="start" />
|
<Tab label="MySQL Sürümleri" icon={<DbIcon />} iconPosition="start" />
|
||||||
|
<Tab label="Sunucu Ayarları" icon={<ServerIcon />} iconPosition="start" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -426,6 +462,45 @@ function App(): JSX.Element {
|
|||||||
</List>
|
</List>
|
||||||
</Paper>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{settingsTab === 2 && (
|
||||||
|
<Box component={Stack} spacing={3}>
|
||||||
|
<Paper sx={{ p: 3, bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>Port Ayarları</Typography>
|
||||||
|
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 3, mt: 2 }}>
|
||||||
|
<TextField
|
||||||
|
label="Nginx Portu"
|
||||||
|
value={settings.nginxPort}
|
||||||
|
onChange={(e) => setSettings({ ...settings, nginxPort: e.target.value })}
|
||||||
|
variant="filled"
|
||||||
|
size="small"
|
||||||
|
helperText="Varsayılan: 80"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="PHP FastCGI Portu"
|
||||||
|
value={settings.phpPort}
|
||||||
|
onChange={(e) => setSettings({ ...settings, phpPort: e.target.value })}
|
||||||
|
variant="filled"
|
||||||
|
size="small"
|
||||||
|
helperText="Varsayılan: 9001"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="MySQL Portu"
|
||||||
|
value={settings.mysqlPort}
|
||||||
|
onChange={(e) => setSettings({ ...settings, mysqlPort: e.target.value })}
|
||||||
|
variant="filled"
|
||||||
|
size="small"
|
||||||
|
helperText="Varsayılan: 3306"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<Button variant="contained" startIcon={<SaveIcon />} size="large" onClick={handleSaveSettings}>
|
||||||
|
Ayarları Kaydet
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{activeTab === 'dashboard' && (
|
{activeTab === 'dashboard' && (
|
||||||
@@ -437,7 +512,7 @@ function App(): JSX.Element {
|
|||||||
<List dense>
|
<List dense>
|
||||||
<ListItem sx={{ py: 1.5 }}>
|
<ListItem sx={{ py: 1.5 }}>
|
||||||
<ListItemIcon><ServerIcon color="primary" /></ListItemIcon>
|
<ListItemIcon><ServerIcon color="primary" /></ListItemIcon>
|
||||||
<ListItemText primary="Nginx Web Sunucusu" secondary="Port: 80, 443" />
|
<ListItemText primary="Nginx Web Sunucusu" secondary={`Port: ${settings.nginxPort}`} />
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
{getStatusChip(status.nginx)}
|
{getStatusChip(status.nginx)}
|
||||||
<Switch checked={status.nginx === 'running'} onChange={() => handleToggleService('nginx')} />
|
<Switch checked={status.nginx === 'running'} onChange={() => handleToggleService('nginx')} />
|
||||||
@@ -446,7 +521,7 @@ function App(): JSX.Element {
|
|||||||
<Divider variant="inset" />
|
<Divider variant="inset" />
|
||||||
<ListItem sx={{ py: 1.5 }}>
|
<ListItem sx={{ py: 1.5 }}>
|
||||||
<ListItemIcon><PhpIcon color="primary" /></ListItemIcon>
|
<ListItemIcon><PhpIcon color="primary" /></ListItemIcon>
|
||||||
<ListItemText primary="PHP FastCGI" secondary="Sürüm: Seçilmedi" />
|
<ListItemText primary="PHP FastCGI" secondary={`Port: ${settings.phpPort}`} />
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
{getStatusChip(status.php)}
|
{getStatusChip(status.php)}
|
||||||
<Switch checked={status.php === 'running'} onChange={() => handleToggleService('php')} />
|
<Switch checked={status.php === 'running'} onChange={() => handleToggleService('php')} />
|
||||||
@@ -455,7 +530,7 @@ function App(): JSX.Element {
|
|||||||
<Divider variant="inset" />
|
<Divider variant="inset" />
|
||||||
<ListItem sx={{ py: 1.5 }}>
|
<ListItem sx={{ py: 1.5 }}>
|
||||||
<ListItemIcon><DbIcon color="primary" /></ListItemIcon>
|
<ListItemIcon><DbIcon color="primary" /></ListItemIcon>
|
||||||
<ListItemText primary="MySQL / MariaDB" secondary="Port: 3306" />
|
<ListItemText primary="MySQL / MariaDB" secondary={`Port: ${settings.mysqlPort}`} />
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
{getStatusChip(status.mysql)}
|
{getStatusChip(status.mysql)}
|
||||||
<Switch checked={status.mysql === 'running'} onChange={() => handleToggleService('mysql')} />
|
<Switch checked={status.mysql === 'running'} onChange={() => handleToggleService('mysql')} />
|
||||||
|
|||||||
Reference in New Issue
Block a user