feat: implement project management and service orchestration architecture
This commit is contained in:
+1
-1
@@ -84,9 +84,9 @@ app.whenReady().then(() => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
registerIpcHandlers()
|
||||
createWindow()
|
||||
createTray()
|
||||
registerIpcHandlers()
|
||||
|
||||
// Generate CLI aliases on start
|
||||
cliAliasService.generateAliases().catch(console.error)
|
||||
|
||||
+118
-6
@@ -7,6 +7,7 @@ import { projectService } from '../services/ProjectService'
|
||||
import { phpManagerService } from '../services/PhpManagerService'
|
||||
import { mariaDbManagerService } from '../services/MariaDbManagerService'
|
||||
import { nginxManagerService } from '../services/NginxManagerService'
|
||||
import { apacheManagerService } from '../services/ApacheManagerService'
|
||||
import { phpMyAdminService } from '../services/PhpMyAdminService'
|
||||
import { portMappingService } from '../services/PortMappingService'
|
||||
import path from 'path'
|
||||
@@ -55,7 +56,8 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin
|
||||
}
|
||||
|
||||
// Generate project configs
|
||||
const projects = await projectService.getProjects()
|
||||
const allProjects = await projectService.getProjects()
|
||||
const projects = allProjects.filter(p => !p.serverType || p.serverType === 'nginx')
|
||||
for (const p of projects) {
|
||||
const projectConfPath = path.join(projectsConfDir, `${p.host}.conf`)
|
||||
let shouldGenerate = forceProjects || !fs.existsSync(projectConfPath)
|
||||
@@ -142,12 +144,88 @@ async function reloadNginx(): Promise<{ success: boolean; message: string }> {
|
||||
return { success: false, message: `Reload hatası: ${error.message}` }
|
||||
}
|
||||
}
|
||||
async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ binPath: string; apacheDir: string; configPath: string }> {
|
||||
const settings = configService.getSettings()
|
||||
const binDir = path.join(configService.getBasePath(), 'bin')
|
||||
const apacheDir = path.join(binDir, 'apache')
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
const binPath = findExecutable(apacheDir, isWindows ? 'httpd.exe' : 'httpd')
|
||||
if (!binPath) throw new Error('Apache executable bulunamadı.')
|
||||
|
||||
const apacheRoot = path.dirname(path.dirname(binPath)) // bin'in üstü
|
||||
const logDir = path.join(configService.getBasePath(), 'logs')
|
||||
const rootPath = path.join(app.getAppPath(), 'www')
|
||||
|
||||
// Ensure directories exist
|
||||
const projectsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects_apache')
|
||||
if (!fs.existsSync(projectsConfDir)) fs.mkdirSync(projectsConfDir, { recursive: true })
|
||||
|
||||
// Generate project configs
|
||||
const allProjects = await projectService.getProjects()
|
||||
const projects = allProjects.filter(p => p.serverType === 'apache')
|
||||
for (const p of projects) {
|
||||
const projectConfPath = path.join(projectsConfDir, `${p.host}.conf`)
|
||||
let shouldGenerate = forceProjects || !fs.existsSync(projectConfPath)
|
||||
|
||||
if (shouldGenerate) {
|
||||
const phpPort = portMappingService.getPhpPort(p.phpVersion)
|
||||
let normalizedPath = p.path.replace(/\\/g, '/')
|
||||
if (!normalizedPath.endsWith('/')) normalizedPath += '/'
|
||||
|
||||
await configService.generateConfig('apache_project.conf.template', `projects_apache/${p.host}.conf`, {
|
||||
HOST: p.host,
|
||||
PATH: normalizedPath,
|
||||
PHP_PORT: phpPort.toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const projectsInclusion = projects.length > 0
|
||||
? `Include "${projectsConfDir.replace(/\\/g, '/')}/*.conf"`
|
||||
: ''
|
||||
|
||||
const configPath = await configService.generateConfig('httpd.conf.template', 'httpd.conf', {
|
||||
PORT: settings.apachePort || '8080',
|
||||
LISTEN_ADDRESS: settings.allowRemoteAccess ? '0.0.0.0' : '127.0.0.1',
|
||||
PHP_PORT: settings.phpPort,
|
||||
ROOT: rootPath.replace(/\\/g, '/'),
|
||||
SRVROOT: apacheRoot.replace(/\\/g, '/'),
|
||||
LOG_DIR: logDir.replace(/\\/g, '/'),
|
||||
PHPMYADMIN_DIR: phpMyAdminService.getPath().replace(/\\/g, '/'),
|
||||
PROJECTS: projectsInclusion
|
||||
})
|
||||
|
||||
return { binPath, apacheDir, configPath }
|
||||
}
|
||||
|
||||
async function restartApache(): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
const { binPath, configPath } = await rebuildApacheConfig(false)
|
||||
|
||||
await processManager.forceKillAll('apache')
|
||||
await processManager.stopService('apache')
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
const success = await processManager.startService('apache', binPath, ['-f', configPath], false)
|
||||
|
||||
if (success) {
|
||||
return { success: true, message: 'Apache restart successful' }
|
||||
} else {
|
||||
const errorLogs = await processManager.getLastErrorLogs('apache')
|
||||
return { success: false, message: `Apache restart failed:\n${errorLogs.join('\n')}` }
|
||||
}
|
||||
} catch (error: any) {
|
||||
return { success: false, message: `Restart hatası: ${error.message}` }
|
||||
}
|
||||
}
|
||||
|
||||
export function registerIpcHandlers(): void {
|
||||
// Service management
|
||||
ipcMain.handle('services:reload', async (_event, serviceName: string) => {
|
||||
if (serviceName !== 'nginx') return { success: false, message: 'Yalnızca Nginx için reload desteklenmektedir.' }
|
||||
return await reloadNginx()
|
||||
if (serviceName === 'nginx') return await reloadNginx()
|
||||
if (serviceName === 'apache') return await restartApache() // Apache doesn't have a simple reload here
|
||||
return { success: false, message: 'Bu servis için reload desteklenmiyor.' }
|
||||
})
|
||||
|
||||
ipcMain.handle('services:start', async (_event, serviceName: string) => {
|
||||
@@ -157,17 +235,24 @@ export function registerIpcHandlers(): void {
|
||||
|
||||
if (serviceName === 'nginx') {
|
||||
const { binPath, nginxPrefix, configPath } = await rebuildNginxConfig(false)
|
||||
|
||||
const success = await processManager.startService('nginx', binPath, ['-p', nginxPrefix, '-c', configPath], false)
|
||||
|
||||
if (!success) {
|
||||
const errorLogs = await processManager.getLastErrorLogs('nginx')
|
||||
return { success: false, message: `Nginx başlatılamadı.\n\n${errorLogs.join('\n')}` }
|
||||
}
|
||||
|
||||
return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx devreye alınamadı.' }
|
||||
}
|
||||
|
||||
if (serviceName === 'apache') {
|
||||
const { binPath, configPath } = await rebuildApacheConfig(false)
|
||||
const success = await processManager.startService('apache', binPath, ['-f', configPath], false)
|
||||
if (!success) {
|
||||
const errorLogs = await processManager.getLastErrorLogs('apache')
|
||||
return { success: false, message: `Apache başlatılamadı.\n\n${errorLogs.join('\n')}` }
|
||||
}
|
||||
return { success, message: success ? 'Apache başlatıldı.' : 'Apache devreye alınamadı.' }
|
||||
}
|
||||
|
||||
if (serviceName.startsWith('php')) {
|
||||
const versionMatch = serviceName.match(/^php:(.+)$/)
|
||||
const phpVersion = versionMatch ? versionMatch[1] : null
|
||||
@@ -451,6 +536,33 @@ export function registerIpcHandlers(): void {
|
||||
}
|
||||
})
|
||||
|
||||
// Apache Versions
|
||||
ipcMain.handle('apache:versions', async () => {
|
||||
return await apacheManagerService.getVersions()
|
||||
})
|
||||
|
||||
ipcMain.handle('apache:download', async (event, id: string) => {
|
||||
const versions = await apacheManagerService.getVersions()
|
||||
const version = versions.find(v => v.id === id)
|
||||
if (!version) return { success: false, message: 'Geçersiz Apache sürümü.' }
|
||||
|
||||
try {
|
||||
apacheManagerService.updateProgress(id, 0, 'downloading')
|
||||
event.sender.send('apache:progress', { id, progress: 0 })
|
||||
|
||||
await downloadService.downloadAndExtract(version.url, id, (p) => {
|
||||
apacheManagerService.updateProgress(id, p)
|
||||
event.sender.send('apache:progress', { id, progress: p })
|
||||
})
|
||||
|
||||
apacheManagerService.updateProgress(id, 100, 'installed')
|
||||
return { success: true, message: 'Apache indirme tamamlandı.' }
|
||||
} catch (error: any) {
|
||||
apacheManagerService.updateProgress(id, 0, 'available')
|
||||
return { success: false, message: `Apache hatası: ${error.message}` }
|
||||
}
|
||||
})
|
||||
|
||||
// Nginx Versions
|
||||
ipcMain.handle('nginx:versions', async () => {
|
||||
return await nginxManagerService.getVersions()
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { configService } from './ConfigService'
|
||||
|
||||
export interface ApacheVersion {
|
||||
id: string
|
||||
version: string
|
||||
url: string
|
||||
status: 'available' | 'downloading' | 'installed'
|
||||
progress: number
|
||||
}
|
||||
|
||||
export class ApacheManagerService {
|
||||
private versions: ApacheVersion[] = []
|
||||
private binDir: string
|
||||
|
||||
constructor() {
|
||||
this.binDir = path.join(configService.getBasePath(), 'bin')
|
||||
this.init()
|
||||
}
|
||||
|
||||
private async init() {
|
||||
if (!fs.existsSync(this.binDir)) {
|
||||
fs.mkdirSync(this.binDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
async getVersions(): Promise<ApacheVersion[]> {
|
||||
// Apache Lounge VS17 Win64 version
|
||||
const stableVersion = '2.4.66'
|
||||
const url = `https://www.apachelounge.com/download/VS18/binaries/httpd-2.4.66-260223-Win64-VS18.zip`
|
||||
|
||||
const id = 'apache'
|
||||
const targetDir = path.join(this.binDir, id)
|
||||
|
||||
// Check if installed (look for httpd.exe anywhere in the zip extraction folder)
|
||||
const isInstalled = this.isInstalled(targetDir)
|
||||
|
||||
const v: ApacheVersion = {
|
||||
id,
|
||||
version: stableVersion,
|
||||
url,
|
||||
status: isInstalled ? 'installed' : 'available',
|
||||
progress: isInstalled ? 100 : 0
|
||||
}
|
||||
|
||||
// Maintain internal state for progress updates
|
||||
const existing = this.versions.find(ev => ev.id === id)
|
||||
if (existing && existing.status === 'downloading') {
|
||||
return [existing]
|
||||
}
|
||||
|
||||
this.versions = [v]
|
||||
return this.versions
|
||||
}
|
||||
|
||||
private isInstalled(dir: string): boolean {
|
||||
if (!fs.existsSync(dir)) return false
|
||||
|
||||
const findHttpd = (d: string): boolean => {
|
||||
const files = fs.readdirSync(d)
|
||||
for (const f of files) {
|
||||
const p = path.join(d, f)
|
||||
const s = fs.statSync(p)
|
||||
if (s.isDirectory()) {
|
||||
if (findHttpd(p)) return true
|
||||
} else if (f.toLowerCase() === 'httpd.exe') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return findHttpd(dir)
|
||||
}
|
||||
|
||||
updateProgress(id: string, progress: number, status: ApacheVersion['status'] = 'downloading') {
|
||||
const v = this.versions.find(ev => ev.id === id)
|
||||
if (v) {
|
||||
v.progress = progress
|
||||
v.status = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const apacheManagerService = new ApacheManagerService()
|
||||
@@ -8,6 +8,8 @@ export class ConfigService {
|
||||
private settingsPath: string
|
||||
private defaultSettings = {
|
||||
nginxPort: '80',
|
||||
apachePort: '8080',
|
||||
serverType: 'nginx' as 'nginx' | 'apache',
|
||||
phpPort: '9001',
|
||||
mariaDbPort: '3306',
|
||||
mariaDbPorts: {} as Record<string, string>,
|
||||
|
||||
@@ -22,7 +22,7 @@ export class ProcessManager {
|
||||
fs.mkdirSync(this.logDir, { recursive: true })
|
||||
}
|
||||
// Initialize with default logs
|
||||
['nginx', 'php', 'mariadb'].forEach(s => {
|
||||
['nginx', 'apache', 'php', 'mariadb'].forEach(s => {
|
||||
if (!this.logs.has(s)) {
|
||||
this.logs.set(s, [`[${new Date().toLocaleTimeString()}] Sistem hazır. İşlem bekleniyor...`])
|
||||
}
|
||||
@@ -209,12 +209,12 @@ export class ProcessManager {
|
||||
} else {
|
||||
const isWindows = process.platform === 'win32';
|
||||
if (isWindows) {
|
||||
const executableName = name === 'nginx' ? 'nginx.exe' : name === 'php' ? 'php-cgi.exe' : 'mariadbd.exe'
|
||||
const executableName = name === 'nginx' ? 'nginx.exe' : name === 'apache' ? 'httpd.exe' : name === 'php' ? 'php-cgi.exe' : 'mariadbd.exe'
|
||||
spawn('taskkill', ['/F', '/IM', executableName, '/T'], { windowsHide: true })
|
||||
.on('close', () => resolve(true))
|
||||
.on('error', () => resolve(true))
|
||||
} else {
|
||||
const processName = name === 'nginx' ? 'nginx' : name === 'php' ? 'php-cgi' : 'mariadbd'
|
||||
const processName = name === 'nginx' ? 'nginx' : name === 'apache' ? 'httpd' : name === 'php' ? 'php-cgi' : 'mariadbd'
|
||||
spawn('pkill', ['-f', processName], { windowsHide: true })
|
||||
.on('close', () => resolve(true))
|
||||
.on('error', () => resolve(true))
|
||||
@@ -227,7 +227,7 @@ export class ProcessManager {
|
||||
this.processes.delete(name)
|
||||
}
|
||||
|
||||
const procName = name === 'nginx' ? 'nginx' : name === 'php' ? 'php-fpm' : 'mariadbd'
|
||||
const procName = name === 'nginx' ? 'nginx' : name === 'apache' ? 'httpd' : name === 'php' ? 'php-fpm' : 'mariadbd'
|
||||
spawn('pkill', ['-9', '-f', procName])
|
||||
.on('close', () => resolve(true))
|
||||
.on('error', () => resolve(true))
|
||||
@@ -287,6 +287,7 @@ export class ProcessManager {
|
||||
}
|
||||
// Ensure defaults are present if stopped
|
||||
if (!statuses['nginx']) statuses['nginx'] = 'stopped'
|
||||
if (!statuses['apache']) statuses['apache'] = 'stopped'
|
||||
if (!statuses['mariadb']) statuses['mariadb'] = 'stopped'
|
||||
return statuses
|
||||
}
|
||||
@@ -295,6 +296,7 @@ export class ProcessManager {
|
||||
if (!this.logDir) return
|
||||
const logPaths: Record<string, string> = {
|
||||
nginx: path.join(this.logDir, 'nginx_error.log'),
|
||||
apache: path.join(this.logDir, 'apache_error.log'),
|
||||
mariadb: path.join(this.logDir, 'mariadb_error.log')
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface Project {
|
||||
phpVersion: string
|
||||
mariaDbVersion: string
|
||||
host: string
|
||||
serverType?: 'nginx' | 'apache'
|
||||
}
|
||||
|
||||
export class ProjectService {
|
||||
|
||||
@@ -25,6 +25,9 @@ const api = {
|
||||
getNginxVersions: () => electronAPI.ipcRenderer.invoke('nginx:versions'),
|
||||
downloadNginxVersion: (id: string) => electronAPI.ipcRenderer.invoke('nginx:download', id),
|
||||
onNginxProgress: (callback: any) => electronAPI.ipcRenderer.on('nginx:progress', (_event, data) => callback(data)),
|
||||
getApacheVersions: () => electronAPI.ipcRenderer.invoke('apache:versions'),
|
||||
downloadApacheVersion: (id: string) => electronAPI.ipcRenderer.invoke('apache:download', id),
|
||||
onApacheProgress: (callback: any) => electronAPI.ipcRenderer.on('apache:progress', (_event, data) => callback(data)),
|
||||
pmaStatus: () => electronAPI.ipcRenderer.invoke('pma:status'),
|
||||
pmaSetup: (mariaDbPort: string) => electronAPI.ipcRenderer.invoke('pma:setup', mariaDbPort),
|
||||
onPmaProgress: (callback: any) => electronAPI.ipcRenderer.on('pma:progress', (_event, data) => callback(data)),
|
||||
|
||||
+154
-10
@@ -91,6 +91,8 @@ interface ServiceStatus {
|
||||
|
||||
interface AppSettings {
|
||||
nginxPort: string
|
||||
apachePort: string
|
||||
serverType: 'nginx' | 'apache'
|
||||
phpPort: string
|
||||
mariaDbPort: string
|
||||
mariaDbPorts?: Record<string, string>
|
||||
@@ -102,6 +104,7 @@ function App(): JSX.Element {
|
||||
const { t, i18n } = useTranslation()
|
||||
const [status, setStatus] = useState<ServiceStatus>({
|
||||
nginx: 'stopped',
|
||||
apache: 'stopped',
|
||||
php: 'stopped',
|
||||
mariadb: 'stopped'
|
||||
})
|
||||
@@ -112,11 +115,14 @@ function App(): JSX.Element {
|
||||
const [phpVersions, setPhpVersions] = useState<any[]>([])
|
||||
const [mariaDbVersions, setMariaDbVersions] = useState<any[]>([])
|
||||
const [nginxVersions, setNginxVersions] = useState<any[]>([])
|
||||
const [apacheVersions, setApacheVersions] = useState<any[]>([])
|
||||
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false)
|
||||
const slugify = (text: string) => text.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, '');
|
||||
const [newProject, setNewProject] = useState<any>({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
|
||||
const [newProject, setNewProject] = useState<any>({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost', serverType: 'nginx' })
|
||||
const [settings, setSettings] = useState<AppSettings>({
|
||||
nginxPort: '80',
|
||||
apachePort: '8080',
|
||||
serverType: 'nginx',
|
||||
phpPort: '9001',
|
||||
mariaDbPort: '3306',
|
||||
allowRemoteAccess: false,
|
||||
@@ -151,7 +157,7 @@ function App(): JSX.Element {
|
||||
const filePath = match[1].replace(/\\/g, '/')
|
||||
const line = parseInt(match[2])
|
||||
const fileName = filePath.split('/').pop() || ''
|
||||
const isProjectConfig = filePath.includes('/generated_configs/projects/')
|
||||
const isProjectConfig = filePath.includes('/generated_configs/projects/') || filePath.includes('/generated_configs/projects_apache/')
|
||||
const host = isProjectConfig ? fileName.replace('.conf', '') : null
|
||||
return { filePath, line, host }
|
||||
}
|
||||
@@ -198,6 +204,12 @@ function App(): JSX.Element {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchApacheVersions = async () => {
|
||||
if (!window.api || !window.api.getApacheVersions) return
|
||||
const v = await window.api.getApacheVersions()
|
||||
setApacheVersions(v || [])
|
||||
}
|
||||
|
||||
const fetchLogs = async (service: string) => {
|
||||
if (window.api && window.api.getServiceLogs) {
|
||||
const list = await window.api.getServiceLogs(service)
|
||||
@@ -238,6 +250,7 @@ function App(): JSX.Element {
|
||||
fetchPhpVersions()
|
||||
fetchMariaDbVersions()
|
||||
fetchNginxVersions()
|
||||
fetchApacheVersions()
|
||||
fetchSettings()
|
||||
fetchPmaStatus()
|
||||
|
||||
@@ -259,6 +272,12 @@ function App(): JSX.Element {
|
||||
setNginxVersions(prev => prev.map(v => v.id === id ? { ...v, progress, status: progress === 100 ? 'installed' : 'downloading' } : v))
|
||||
}) : null
|
||||
|
||||
const removeApacheListener = window.api.onApacheProgress ? window.api.onApacheProgress((data) => {
|
||||
if (!data) return
|
||||
const { id, progress } = data
|
||||
setApacheVersions(prev => prev.map(v => v.id === id ? { ...v, progress, status: progress === 100 ? 'installed' : 'downloading' } : v))
|
||||
}) : null
|
||||
|
||||
const removePmaListener = window.api.onPmaProgress ? window.api.onPmaProgress((p: number) => {
|
||||
setPmaProgress(p)
|
||||
}) : null
|
||||
@@ -284,6 +303,7 @@ function App(): JSX.Element {
|
||||
if (typeof removePhpListener === 'function') removePhpListener()
|
||||
if (typeof removeMariaDbListener === 'function') removeMariaDbListener()
|
||||
if (typeof removeNginxListener === 'function') removeNginxListener()
|
||||
if (typeof removeApacheListener === 'function') removeApacheListener()
|
||||
if (typeof removePmaListener === 'function') removePmaListener()
|
||||
}
|
||||
}, [logDialogOpen, selectedService])
|
||||
@@ -392,7 +412,7 @@ function App(): JSX.Element {
|
||||
|
||||
const handleStartAll = async () => {
|
||||
if (!window.api) return
|
||||
const criticalServices = ['nginx']
|
||||
const criticalServices = [settings.serverType]
|
||||
for (const s of criticalServices) {
|
||||
if (status[s] === 'stopped') setStatus(prev => ({ ...prev, [s]: 'starting' }))
|
||||
}
|
||||
@@ -419,7 +439,7 @@ function App(): JSX.Element {
|
||||
|
||||
const handleStopAll = async () => {
|
||||
if (!window.api) return
|
||||
const criticalServices = ['nginx']
|
||||
const criticalServices = ['nginx', 'apache']
|
||||
const phpToStop = Object.keys(status).filter(s => s.startsWith('php:') && (status[s] === 'running' || status[s] === 'starting'))
|
||||
const mariaDbToStop = Object.keys(status).filter(s => (s.startsWith('mariadb:') || s.startsWith('mysql:')) && (status[s] === 'running' || status[s] === 'starting'))
|
||||
|
||||
@@ -587,6 +607,17 @@ function App(): JSX.Element {
|
||||
fetchNginxVersions()
|
||||
}
|
||||
|
||||
const handleDownloadApache = async (id: string) => {
|
||||
if (!window.api) return
|
||||
const result = await window.api.downloadApacheVersion(id)
|
||||
setNotification({
|
||||
open: true,
|
||||
message: result.message,
|
||||
severity: result.success ? 'success' : 'error'
|
||||
})
|
||||
fetchApacheVersions()
|
||||
}
|
||||
|
||||
const handleSetupPma = async () => {
|
||||
if (!window.api) return
|
||||
setPmaLoading(true)
|
||||
@@ -605,7 +636,8 @@ function App(): JSX.Element {
|
||||
}
|
||||
|
||||
const handleOpenPma = () => {
|
||||
window.open(`http://localhost:${settings.nginxPort}/phpmyadmin`, '_blank')
|
||||
const port = settings.serverType === 'apache' ? settings.apachePort : settings.nginxPort
|
||||
window.open(`http://localhost:${port}/phpmyadmin`, '_blank')
|
||||
}
|
||||
|
||||
const handleOpenPhpExtensions = (version: string) => {
|
||||
@@ -712,6 +744,7 @@ function App(): JSX.Element {
|
||||
<Tab label="PHP" icon={<PhpIcon />} iconPosition="start" />
|
||||
<Tab label="MariaDB" icon={<DbIcon />} iconPosition="start" />
|
||||
<Tab label="Nginx" icon={<WebIcon />} iconPosition="start" />
|
||||
<Tab label="Apache" icon={<ServerIcon />} iconPosition="start" />
|
||||
<Tab label="Sunucu Ayarları" icon={<ServerIcon />} iconPosition="start" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
@@ -865,10 +898,58 @@ function App(): JSX.Element {
|
||||
)}
|
||||
|
||||
{settingsTab === 3 && (
|
||||
<Paper sx={{ bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}>
|
||||
<Box sx={{ p: 2, borderBottom: '1px solid rgba(255, 255, 255, 0.05)' }}>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>Apache Web Sunucusu</Typography>
|
||||
<Typography variant="body2" color="text.secondary">Apache HTTP Server binary dosyalarını buradan yönetebilirsiniz.</Typography>
|
||||
</Box>
|
||||
<List>
|
||||
{apacheVersions.map((v) => (
|
||||
<ListItem key={v.id} sx={{ py: 2 }}>
|
||||
<ListItemIcon>
|
||||
{v.status === 'installed' ? <InstalledIcon color="success" /> : <AvailableIcon color="action" />}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={`Apache ${v.version}`}
|
||||
secondary={v.status === 'downloading' ? `İndiriliyor... %${Math.round(v.progress)}` : v.status === 'installed' ? 'Kurulu' : 'İndirilebilir'}
|
||||
sx={{ flexGrow: 1 }}
|
||||
/>
|
||||
<Box sx={{ minWidth: 120, textAlign: 'right' }}>
|
||||
{v.status === 'available' && (
|
||||
<Button variant="outlined" size="small" startIcon={<DownloadIcon />} onClick={() => handleDownloadApache(v.id)}>
|
||||
İNDİR
|
||||
</Button>
|
||||
)}
|
||||
{v.status === 'downloading' && (
|
||||
<Box sx={{ width: '100%', mt: 1 }}>
|
||||
<LinearProgress variant="determinate" value={v.progress} sx={{ height: 8, borderRadius: 5 }} />
|
||||
</Box>
|
||||
)}
|
||||
{v.status === 'installed' && (
|
||||
<Chip label="HAZIR" color="success" variant="outlined" size="small" />
|
||||
)}
|
||||
</Box>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{settingsTab === 4 && (
|
||||
<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 }}>
|
||||
<FormControl variant="filled" size="small" fullWidth>
|
||||
<InputLabel>Varsayılan Web Sunucusu</InputLabel>
|
||||
<Select
|
||||
value={settings.serverType || 'nginx'}
|
||||
onChange={(e) => setSettings({ ...settings, serverType: e.target.value as any })}
|
||||
>
|
||||
<MenuItem value="nginx">Nginx (Performans)</MenuItem>
|
||||
<MenuItem value="apache">Apache (htaccess Desteği)</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
label="Nginx Portu"
|
||||
value={settings.nginxPort}
|
||||
@@ -877,6 +958,14 @@ function App(): JSX.Element {
|
||||
size="small"
|
||||
helperText="Varsayılan: 80"
|
||||
/>
|
||||
<TextField
|
||||
label="Apache Portu"
|
||||
value={settings.apachePort}
|
||||
onChange={(e) => setSettings({ ...settings, apachePort: e.target.value })}
|
||||
variant="filled"
|
||||
size="small"
|
||||
helperText="Varsayılan: 8080"
|
||||
/>
|
||||
<TextField
|
||||
label="PHP FastCGI Portu"
|
||||
value={settings.phpPort}
|
||||
@@ -931,6 +1020,7 @@ function App(): JSX.Element {
|
||||
<Tabs value={dashboardTab} onChange={(_e, v) => setDashboardTab(v)} textColor="primary" indicatorColor="primary">
|
||||
<Tab label={t('common.all')} />
|
||||
<Tab label="Nginx" />
|
||||
<Tab label="Apache" />
|
||||
<Tab label="PHP" />
|
||||
<Tab label="MariaDB" />
|
||||
</Tabs>
|
||||
@@ -991,8 +1081,41 @@ function App(): JSX.Element {
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Apache Box */}
|
||||
{(dashboardTab === 0 || dashboardTab === 2) && (
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<Paper elevation={4} sx={{
|
||||
p: 3, borderRadius: 3,
|
||||
bgcolor: 'rgba(255, 255, 255, 0.03)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
transition: 'transform 0.2s',
|
||||
'&:hover': { transform: 'scale(1.02)' }
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<ServerIcon color="primary" sx={{ fontSize: 40 }} />
|
||||
{getStatusChip(status.apache)}
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Apache</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>Port: {settings.apachePort}</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<IconButton onClick={() => handleOpenLogs('apache')} title="Hata Logları" size="small" sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}>
|
||||
<TerminalIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Switch
|
||||
checked={status.apache === 'running' || status.apache === 'starting'}
|
||||
onChange={() => handleToggleService('apache')}
|
||||
disabled={status.apache === 'starting'}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* PHP Boxes */}
|
||||
{(dashboardTab === 0 || dashboardTab === 2) && installedPhpVersions.map(v => (
|
||||
{(dashboardTab === 0 || dashboardTab === 3) && installedPhpVersions.map(v => (
|
||||
<Grid item xs={12} sm={6} md={4} key={v.id}>
|
||||
<Paper elevation={4} sx={{
|
||||
p: 3, borderRadius: 3,
|
||||
@@ -1023,7 +1146,7 @@ function App(): JSX.Element {
|
||||
))}
|
||||
|
||||
{/* MariaDB Boxes */}
|
||||
{(dashboardTab === 0 || dashboardTab === 3) && mariaDbVersions.filter(v => v.status === 'installed').map(v => (
|
||||
{(dashboardTab === 0 || dashboardTab === 4) && mariaDbVersions.filter(v => v.status === 'installed').map(v => (
|
||||
<Grid item xs={12} sm={6} md={4} key={v.id}>
|
||||
<Paper elevation={4} sx={{
|
||||
p: 3, borderRadius: 3,
|
||||
@@ -1255,6 +1378,12 @@ function App(): JSX.Element {
|
||||
label={`PHP ${project.phpVersion}`}
|
||||
sx={{ bgcolor: 'rgba(71, 74, 255, 0.1)', color: '#777bb4', fontWeight: 600, border: '1px solid rgba(71, 74, 255, 0.2)' }}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
icon={project.serverType === 'apache' ? <ServerIcon sx={{ fontSize: '1rem !important' }} /> : <WebIcon sx={{ fontSize: '1rem !important' }} />}
|
||||
label={project.serverType === 'apache' ? 'Apache' : 'Nginx'}
|
||||
sx={{ bgcolor: 'rgba(255, 255, 255, 0.05)', color: '#fff', fontWeight: 600 }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Paper variant="outlined" sx={{
|
||||
@@ -1275,7 +1404,10 @@ function App(): JSX.Element {
|
||||
<Button
|
||||
size="small"
|
||||
endIcon={<LaunchIcon />}
|
||||
onClick={() => window.open(`http://localhost:${settings.nginxPort}/${project.host}/`, '_blank')}
|
||||
onClick={() => {
|
||||
const port = project.serverType === 'apache' ? settings.apachePort : settings.nginxPort
|
||||
window.open(`http://localhost:${port}/${project.host}/`, '_blank')
|
||||
}}
|
||||
sx={{
|
||||
color: '#39A7FF',
|
||||
textTransform: 'none',
|
||||
@@ -1422,7 +1554,7 @@ function App(): JSX.Element {
|
||||
|
||||
<Dialog open={isProjectDialogOpen} onClose={() => {
|
||||
setIsProjectDialogOpen(false)
|
||||
setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
|
||||
setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost', serverType: 'nginx' })
|
||||
}} fullWidth maxWidth="sm">
|
||||
<DialogTitle>{newProject.id ? 'Proje Düzenle' : 'Yeni Proje Ekle'}</DialogTitle>
|
||||
<DialogContent>
|
||||
@@ -1471,12 +1603,24 @@ function App(): JSX.Element {
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="server-type-label">Web Sunucusu</InputLabel>
|
||||
<Select
|
||||
labelId="server-type-label"
|
||||
value={newProject.serverType || 'nginx'}
|
||||
label="Web Sunucusu"
|
||||
onChange={(e) => setNewProject({ ...newProject, serverType: e.target.value as any })}
|
||||
>
|
||||
<MenuItem value="nginx">Nginx</MenuItem>
|
||||
<MenuItem value="apache">Apache (htaccess desteği)</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => {
|
||||
setIsProjectDialogOpen(false)
|
||||
setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost' })
|
||||
setNewProject({ name: '', path: '', phpVersion: '', mariaDbVersion: '', host: 'localhost', serverType: 'nginx' })
|
||||
}}>İptal</Button>
|
||||
<Button
|
||||
onClick={handleAddProject}
|
||||
|
||||
Reference in New Issue
Block a user