feat: implement core application UI and backend services for PHP environment management
This commit is contained in:
@@ -20,6 +20,18 @@ http {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location /phpmyadmin {
|
||||
alias "{{PHPMYADMIN_DIR}}";
|
||||
index index.php;
|
||||
location ~ ^/phpmyadmin/(.+\.php)$ {
|
||||
alias "{{PHPMYADMIN_DIR}}/$1";
|
||||
fastcgi_pass 127.0.0.1:{{PHP_PORT}};
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
include "{{CONF_DIR}}/fastcgi_params";
|
||||
}
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass 127.0.0.1:{{PHP_PORT}};
|
||||
fastcgi_index index.php;
|
||||
|
||||
@@ -65,6 +65,14 @@ multiphp/
|
||||
|
||||
---
|
||||
|
||||
## phpMyAdmin Entegrasyonu
|
||||
- phpMyAdmin'in en son sürümünün (all-languages.zip) otomatik indirilmesi ve ayıklanması.
|
||||
- Nginx üzerinde `/phpmyadmin` aliası ile yapılandırma.
|
||||
- `config.inc.php` dosyasının yerel MySQL ile uyumlu şekilde otomatik oluşturulması.
|
||||
- Dashboard üzerinde phpMyAdmin'e hızlı erişim butonu.
|
||||
|
||||
---
|
||||
|
||||
## Doğrulama ve Test Planı
|
||||
|
||||
### Otomatik Testler
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 24 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
+17
-1
@@ -7,6 +7,7 @@ import { projectService } from '../services/ProjectService'
|
||||
import { phpManagerService } from '../services/PhpManagerService'
|
||||
import { mySqlManagerService } from '../services/MySqlManagerService'
|
||||
import { nginxManagerService } from '../services/NginxManagerService'
|
||||
import { phpMyAdminService } from '../services/PhpMyAdminService'
|
||||
import path from 'path'
|
||||
|
||||
function findExecutable(dir: string, name: string): string | null {
|
||||
@@ -58,7 +59,8 @@ export function registerIpcHandlers(): void {
|
||||
PHP_PORT: settings.phpPort,
|
||||
ROOT: rootPath,
|
||||
LOG_DIR: logDir.replace(/\\/g, '/'),
|
||||
CONF_DIR: confDir.replace(/\\/g, '/')
|
||||
CONF_DIR: confDir.replace(/\\/g, '/'),
|
||||
PHPMYADMIN_DIR: phpMyAdminService.getPath().replace(/\\/g, '/')
|
||||
})
|
||||
|
||||
// Test config first with prefix
|
||||
@@ -241,6 +243,9 @@ export function registerIpcHandlers(): void {
|
||||
ipcMain.handle('projects:remove', async (_event, id) => {
|
||||
return projectService.removeProject(id)
|
||||
})
|
||||
ipcMain.handle('projects:update', async (_event, project) => {
|
||||
return projectService.updateProject(project)
|
||||
})
|
||||
|
||||
// Config management
|
||||
ipcMain.handle('config:get', async () => {
|
||||
@@ -250,4 +255,15 @@ export function registerIpcHandlers(): void {
|
||||
ipcMain.handle('config:save', async (_event, config: any) => {
|
||||
return { success: true, settings: configService.updateSettings(config) }
|
||||
})
|
||||
|
||||
// phpMyAdmin
|
||||
ipcMain.handle('pma:status', async () => {
|
||||
return await phpMyAdminService.isInstalled()
|
||||
})
|
||||
|
||||
ipcMain.handle('pma:setup', async (event, mysqlPort: string) => {
|
||||
return await phpMyAdminService.setup(mysqlPort, (p) => {
|
||||
event.sender.send('pma:progress', p)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,19 +15,14 @@ export class DownloadService {
|
||||
}
|
||||
}
|
||||
|
||||
async downloadAndExtract(url: string, targetName: string, onProgress?: (p: number) => void): Promise<string> {
|
||||
async downloadAndExtract(url: string, targetName: string, onProgress?: (p: number) => void, customTargetDir?: string): Promise<string> {
|
||||
const tempFile = path.join(app.getPath('temp'), `${targetName}.zip`)
|
||||
const targetDir = path.join(this.binDir, targetName)
|
||||
const phpExe = path.join(targetDir, 'php.exe')
|
||||
const targetDir = customTargetDir || path.join(this.binDir, targetName)
|
||||
|
||||
// If already installed and working, skip
|
||||
if (fs.existsSync(phpExe)) {
|
||||
return targetDir
|
||||
}
|
||||
|
||||
// Cleanup failed attempts
|
||||
if (fs.existsSync(targetDir)) {
|
||||
fs.rmSync(targetDir, { recursive: true, force: true })
|
||||
// Cleanup failed/previous attempts if we want a fresh start
|
||||
// For phpMyAdmin, we always want a fresh extract to avoid version mix
|
||||
if (fs.existsSync(targetDir) && !customTargetDir) {
|
||||
// For PHP/MySQL we might skip, but let's make it smarter
|
||||
}
|
||||
|
||||
const writer = fs.createWriteStream(tempFile)
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { app } from 'electron'
|
||||
import { downloadService } from './DownloadService'
|
||||
|
||||
export class PhpMyAdminService {
|
||||
private pmaDir: string
|
||||
private downloadUrl = 'https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.zip'
|
||||
|
||||
constructor() {
|
||||
this.pmaDir = path.join(app.getPath('userData'), 'phpmyadmin')
|
||||
}
|
||||
|
||||
async isInstalled(): Promise<boolean> {
|
||||
return fs.existsSync(path.join(this.pmaDir, 'index.php'))
|
||||
}
|
||||
|
||||
async setup(mysqlPort: string, onProgress?: (progress: number) => void): Promise<boolean> {
|
||||
if (!fs.existsSync(this.pmaDir)) {
|
||||
fs.mkdirSync(this.pmaDir, { recursive: true })
|
||||
}
|
||||
|
||||
try {
|
||||
await downloadService.downloadAndExtract(this.downloadUrl, this.pmaDir, (p) => {
|
||||
if (onProgress) onProgress(p)
|
||||
})
|
||||
|
||||
// After extraction, the zip usually contains a folder like phpMyAdmin-x.y.z-all-languages
|
||||
// We should move contents to the root of this.pmaDir if they are nested
|
||||
const items = fs.readdirSync(this.pmaDir)
|
||||
if (items.length === 1 && fs.statSync(path.join(this.pmaDir, items[0])).isDirectory()) {
|
||||
const nestedDir = path.join(this.pmaDir, items[0])
|
||||
const nestedItems = fs.readdirSync(nestedDir)
|
||||
for (const item of nestedItems) {
|
||||
fs.renameSync(path.join(nestedDir, item), path.join(this.pmaDir, item))
|
||||
}
|
||||
fs.rmdirSync(nestedDir)
|
||||
}
|
||||
|
||||
// Generate config.inc.php
|
||||
this.generateConfig(mysqlPort)
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('phpMyAdmin setup error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private generateConfig(mysqlPort: string = '3306') {
|
||||
const configPath = path.join(this.pmaDir, 'config.inc.php')
|
||||
const blowfishSecret = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
|
||||
|
||||
const configContent = `<?php
|
||||
$cfg['blowfish_secret'] = '${blowfishSecret}';
|
||||
$i = 0;
|
||||
$i++;
|
||||
$cfg['Servers'][$i]['auth_type'] = 'config';
|
||||
$cfg['Servers'][$i]['host'] = '127.0.0.1:${mysqlPort}';
|
||||
$cfg['Servers'][$i]['user'] = 'root';
|
||||
$cfg['Servers'][$i]['password'] = '';
|
||||
$cfg['Servers'][$i]['extension'] = 'mysqli';
|
||||
$cfg['Servers'][$i]['AllowNoPassword'] = true;
|
||||
$cfg['Lang'] = '';
|
||||
?>`
|
||||
|
||||
fs.writeFileSync(configPath, configContent)
|
||||
}
|
||||
|
||||
getPath(): string {
|
||||
return this.pmaDir
|
||||
}
|
||||
}
|
||||
|
||||
export const phpMyAdminService = new PhpMyAdminService()
|
||||
@@ -0,0 +1,58 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
|
||||
interface PortMap {
|
||||
php: Record<string, number>;
|
||||
mysql: Record<string, number>;
|
||||
}
|
||||
|
||||
export class PortMappingService {
|
||||
private configFile: string
|
||||
private portMap: PortMap = { php: {}, mysql: {} }
|
||||
private nextPhpPort = 9001
|
||||
private nextMysqlPort = 3306
|
||||
|
||||
constructor() {
|
||||
this.configFile = path.join(app.getPath('userData'), 'port_mappings.json')
|
||||
this.load()
|
||||
}
|
||||
|
||||
private load() {
|
||||
if (fs.existsSync(this.configFile)) {
|
||||
try {
|
||||
this.portMap = JSON.parse(fs.readFileSync(this.configFile, 'utf-8'))
|
||||
|
||||
const phpPorts = Object.values(this.portMap.php)
|
||||
if (phpPorts.length > 0) this.nextPhpPort = Math.max(...phpPorts) + 1
|
||||
|
||||
const mysqlPorts = Object.values(this.portMap.mysql)
|
||||
if (mysqlPorts.length > 0) this.nextMysqlPort = Math.max(...mysqlPorts) + 1
|
||||
} catch (e) {
|
||||
console.error('Failed to load port mappings', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private save() {
|
||||
fs.writeFileSync(this.configFile, JSON.stringify(this.portMap, null, 2))
|
||||
}
|
||||
|
||||
getPhpPort(version: string): number {
|
||||
if (!this.portMap.php[version]) {
|
||||
this.portMap.php[version] = this.nextPhpPort++
|
||||
this.save()
|
||||
}
|
||||
return this.portMap.php[version]
|
||||
}
|
||||
|
||||
getMySqlPort(version: string): number {
|
||||
if (!this.portMap.mysql[version]) {
|
||||
this.portMap.mysql[version] = this.nextMysqlPort++
|
||||
this.save()
|
||||
}
|
||||
return this.portMap.mysql[version]
|
||||
}
|
||||
}
|
||||
|
||||
export const portMappingService = new PortMappingService()
|
||||
@@ -7,6 +7,7 @@ export interface Project {
|
||||
name: string
|
||||
path: string
|
||||
phpVersion: string
|
||||
mySqlVersion: string
|
||||
host: string
|
||||
}
|
||||
|
||||
@@ -49,6 +50,14 @@ export class ProjectService {
|
||||
return newProject
|
||||
}
|
||||
|
||||
updateProject(project: Project) {
|
||||
const index = this.projects.findIndex(p => p.id === project.id)
|
||||
if (index !== -1) {
|
||||
this.projects[index] = project
|
||||
this.saveProjects()
|
||||
}
|
||||
}
|
||||
|
||||
removeProject(id: string) {
|
||||
this.projects = this.projects.filter(p => p.id !== id)
|
||||
this.saveProjects()
|
||||
|
||||
@@ -12,6 +12,7 @@ const api = {
|
||||
getProjects: () => electronAPI.ipcRenderer.invoke('projects:list'),
|
||||
addProject: (project: any) => electronAPI.ipcRenderer.invoke('projects:add', project),
|
||||
removeProject: (id: string) => electronAPI.ipcRenderer.invoke('projects:remove', id),
|
||||
updateProject: (project: any) => electronAPI.ipcRenderer.invoke('projects:update', project),
|
||||
getPhpVersions: () => electronAPI.ipcRenderer.invoke('binaries:list-php'),
|
||||
downloadPhpVersion: (id: string) => electronAPI.ipcRenderer.invoke('binaries:download-php', id),
|
||||
onBinaryProgress: (callback: any) => electronAPI.ipcRenderer.on('binaries:progress', (_event, data) => callback(data)),
|
||||
@@ -23,7 +24,10 @@ const api = {
|
||||
saveSettings: (settings: any) => electronAPI.ipcRenderer.invoke('config:save', settings),
|
||||
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))
|
||||
onNginxProgress: (callback: any) => electronAPI.ipcRenderer.on('nginx:progress', (_event, data) => callback(data)),
|
||||
pmaStatus: () => electronAPI.ipcRenderer.invoke('pma:status'),
|
||||
pmaSetup: (mysqlPort: string) => electronAPI.ipcRenderer.invoke('pma:setup', mysqlPort),
|
||||
onPmaProgress: (callback: any) => electronAPI.ipcRenderer.on('pma:progress', (_event, data) => callback(data))
|
||||
}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
|
||||
+150
-20
@@ -57,7 +57,9 @@ import {
|
||||
Terminal as TerminalIcon,
|
||||
ContentCopy as CopyIcon,
|
||||
Close as CloseIcon,
|
||||
ContentCopy as ContentCopyIcon
|
||||
ContentCopy as ContentCopyIcon,
|
||||
OpenInNew as OpenIcon,
|
||||
Edit as EditIcon
|
||||
} from '@mui/icons-material'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -97,7 +99,8 @@ function App(): JSX.Element {
|
||||
const [nginxVersions, setNginxVersions] = useState<any[]>([])
|
||||
const [loadingVersions, setLoadingVersions] = useState(false)
|
||||
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false)
|
||||
const [newProject, setNewProject] = useState({ name: '', path: '', phpVersion: '', host: 'localhost' })
|
||||
const slugify = (text: string) => text.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, '');
|
||||
const [newProject, setNewProject] = useState<any>({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' })
|
||||
const [settings, setSettings] = useState<AppSettings>({
|
||||
nginxPort: '80',
|
||||
phpPort: '9001',
|
||||
@@ -114,6 +117,9 @@ function App(): JSX.Element {
|
||||
const [logDialogOpen, setLogDialogOpen] = useState(false)
|
||||
const [selectedService, setSelectedService] = useState('')
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [pmaInstalled, setPmaInstalled] = useState(false)
|
||||
const [pmaLoading, setPmaLoading] = useState(false)
|
||||
const [pmaProgress, setPmaProgress] = useState(0)
|
||||
|
||||
const fetchStatus = async () => {
|
||||
if (window.api && window.api.getServiceStatus) {
|
||||
@@ -174,23 +180,39 @@ function App(): JSX.Element {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isProjectDialogOpen && !newProject.phpVersion && phpVersions.length > 0) {
|
||||
const installed = phpVersions.find(v => v.status === 'installed')
|
||||
if (installed) {
|
||||
setNewProject(prev => ({ ...prev, phpVersion: installed.version }))
|
||||
if (isProjectDialogOpen && !newProject.id) {
|
||||
const updates: Partial<typeof newProject> = {}
|
||||
if (!newProject.phpVersion && phpVersions.length > 0) {
|
||||
const installedPhp = phpVersions.find(v => v.status === 'installed')
|
||||
if (installedPhp) updates.phpVersion = installedPhp.version
|
||||
}
|
||||
if (!newProject.mySqlVersion && mySqlVersions.length > 0) {
|
||||
const installedMysql = mySqlVersions.find(v => v.status === 'installed')
|
||||
if (installedMysql) updates.mySqlVersion = installedMysql.version
|
||||
}
|
||||
if (Object.keys(updates).length > 0) {
|
||||
setNewProject(prev => ({ ...prev, ...updates }))
|
||||
}
|
||||
}
|
||||
}, [isProjectDialogOpen, phpVersions])
|
||||
}, [isProjectDialogOpen, phpVersions, mySqlVersions])
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.api) return
|
||||
|
||||
const fetchPmaStatus = async () => {
|
||||
if (window.api && window.api.pmaStatus) {
|
||||
const installed = await window.api.pmaStatus()
|
||||
setPmaInstalled(installed)
|
||||
}
|
||||
}
|
||||
|
||||
fetchStatus()
|
||||
fetchProjects()
|
||||
fetchPhpVersions()
|
||||
fetchMySqlVersions()
|
||||
fetchNginxVersions()
|
||||
fetchSettings()
|
||||
fetchPmaStatus()
|
||||
|
||||
const removePhpListener = window.api.onBinaryProgress ? window.api.onBinaryProgress((data) => {
|
||||
if (!data) return
|
||||
@@ -210,6 +232,10 @@ function App(): JSX.Element {
|
||||
setNginxVersions(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
|
||||
|
||||
const interval = setInterval(() => {
|
||||
fetchStatus()
|
||||
fetchProjects()
|
||||
@@ -231,6 +257,7 @@ function App(): JSX.Element {
|
||||
if (typeof removePhpListener === 'function') removePhpListener()
|
||||
if (typeof removeMySqlListener === 'function') removeMySqlListener()
|
||||
if (typeof removeNginxListener === 'function') removeNginxListener()
|
||||
if (typeof removePmaListener === 'function') removePmaListener()
|
||||
}
|
||||
}, [logDialogOpen, selectedService])
|
||||
|
||||
@@ -243,9 +270,11 @@ function App(): JSX.Element {
|
||||
|
||||
const handleSelectPath = async () => {
|
||||
if (window.api && window.api.selectDirectory) {
|
||||
const path = await window.api.selectDirectory()
|
||||
if (path) {
|
||||
setNewProject(prev => ({ ...prev, path }))
|
||||
const pathStr = await window.api.selectDirectory()
|
||||
if (pathStr) {
|
||||
const folderName = pathStr.split(/[/\\]/).pop() || ''
|
||||
const slug = slugify(folderName)
|
||||
setNewProject(prev => ({ ...prev, path: pathStr, name: folderName, host: slug }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,10 +360,14 @@ function App(): JSX.Element {
|
||||
|
||||
const handleAddProject = async () => {
|
||||
if (!window.api) return
|
||||
if (newProject.id) {
|
||||
await window.api.updateProject(newProject)
|
||||
} else {
|
||||
await window.api.addProject(newProject)
|
||||
}
|
||||
setIsProjectDialogOpen(false)
|
||||
setNewProject({ name: '', path: '', phpVersion: '', host: 'localhost' })
|
||||
setNotification({ open: true, message: 'Proje başarıyla eklendi.', severity: 'success' })
|
||||
setNewProject({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' })
|
||||
setNotification({ open: true, message: newProject.id ? 'Proje güncellendi.' : 'Proje başarıyla eklendi.', severity: 'success' })
|
||||
fetchProjects()
|
||||
}
|
||||
|
||||
@@ -378,6 +411,27 @@ function App(): JSX.Element {
|
||||
fetchNginxVersions()
|
||||
}
|
||||
|
||||
const handleSetupPma = async () => {
|
||||
if (!window.api) return
|
||||
setPmaLoading(true)
|
||||
setPmaProgress(0)
|
||||
try {
|
||||
const success = await window.api.pmaSetup()
|
||||
if (success) {
|
||||
setPmaInstalled(true)
|
||||
setNotification({ open: true, message: 'phpMyAdmin başarıyla kuruldu.', severity: 'success' })
|
||||
} else {
|
||||
setNotification({ open: true, message: 'phpMyAdmin kurulumu sırasında hata oluştu.', severity: 'error' })
|
||||
}
|
||||
} finally {
|
||||
setPmaLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenPma = () => {
|
||||
window.open(`http://localhost:${settings.nginxPort}/phpmyadmin`, '_blank')
|
||||
}
|
||||
|
||||
const installedPhpVersions = phpVersions.filter(v => v.status === 'installed')
|
||||
|
||||
return (
|
||||
@@ -501,6 +555,7 @@ function App(): JSX.Element {
|
||||
)}
|
||||
|
||||
{settingsTab === 1 && (
|
||||
<>
|
||||
<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' }}>MySQL Sürümleri</Typography>
|
||||
@@ -546,6 +601,51 @@ function App(): JSX.Element {
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
|
||||
<Paper sx={{ mt: 3, p: 2, bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Box sx={{ p: 1.5, bgcolor: 'rgba(255, 255, 255, 0.05)', borderRadius: 2 }}>
|
||||
<DbIcon color="primary" />
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>phpMyAdmin</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{pmaInstalled ? 'Veritabanı yönetimi için phpMyAdmin hazır.' : 'Veritabanlarını web üzerinden yönetmek için phpMyAdmin kurun.'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
{!pmaInstalled ? (
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={handleSetupPma}
|
||||
disabled={pmaLoading}
|
||||
startIcon={pmaLoading ? <CircularProgress size={20} /> : <DownloadIcon />}
|
||||
>
|
||||
{pmaLoading ? 'KURULUYOR...' : 'KURULUMU BAŞLAT'}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={handleOpenPma}
|
||||
startIcon={<OpenIcon />}
|
||||
>
|
||||
AÇ
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{pmaLoading && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<LinearProgress variant="determinate" value={pmaProgress} sx={{ height: 6, borderRadius: 3 }} />
|
||||
<Typography variant="caption" sx={{ mt: 0.5, display: 'block', textAlign: 'center' }}>
|
||||
İndiriliyor... %{Math.round(pmaProgress)}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
</>
|
||||
)}
|
||||
|
||||
{settingsTab === 2 && (
|
||||
@@ -715,15 +815,23 @@ function App(): JSX.Element {
|
||||
<Paper key={project.id} sx={{ mb: 2, bgcolor: 'rgba(255, 255, 255, 0.05)' }}>
|
||||
<ListItem
|
||||
secondaryAction={
|
||||
<Box>
|
||||
<IconButton edge="end" aria-label="edit" onClick={() => {
|
||||
setNewProject(project)
|
||||
setIsProjectDialogOpen(true)
|
||||
}} sx={{ mr: 1 }}>
|
||||
<EditIcon color="primary" />
|
||||
</IconButton>
|
||||
<IconButton edge="end" aria-label="delete" onClick={() => handleRemoveProject(project.id)}>
|
||||
<DeleteIcon color="error" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<ListItemIcon><ProjectIcon color="primary" /></ListItemIcon>
|
||||
<ListItemText
|
||||
primary={project.name}
|
||||
secondary={`${project.path} - PHP: ${project.phpVersion} - Host: ${project.host}`}
|
||||
secondary={`${project.path} - PHP: ${project.phpVersion} | MySQL: ${project.mySqlVersion || 'Yok'} - Host: ${project.host}`}
|
||||
/>
|
||||
</ListItem>
|
||||
</Paper>
|
||||
@@ -856,8 +964,11 @@ function App(): JSX.Element {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={isProjectDialogOpen} onClose={() => setIsProjectDialogOpen(false)} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Yeni Proje Ekle</DialogTitle>
|
||||
<Dialog open={isProjectDialogOpen} onClose={() => {
|
||||
setIsProjectDialogOpen(false)
|
||||
setNewProject({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' })
|
||||
}} fullWidth maxWidth="sm">
|
||||
<DialogTitle>{newProject.id ? 'Proje Düzenle' : 'Yeni Proje Ekle'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mt: 2, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<TextField
|
||||
@@ -887,6 +998,7 @@ function App(): JSX.Element {
|
||||
value={newProject.host}
|
||||
onChange={(e) => setNewProject({ ...newProject, host: e.target.value })}
|
||||
/>
|
||||
<Stack direction="row" spacing={2} sx={{ width: '100%' }}>
|
||||
<FormControl fullWidth disabled={installedPhpVersions.length === 0}>
|
||||
<InputLabel id="php-version-label">PHP Sürümü</InputLabel>
|
||||
<Select
|
||||
@@ -904,18 +1016,36 @@ function App(): JSX.Element {
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth disabled={mySqlVersions.filter(v => v.status === 'installed').length === 0}>
|
||||
<InputLabel id="mysql-version-label">MySQL Sürümü</InputLabel>
|
||||
<Select
|
||||
labelId="mysql-version-label"
|
||||
value={newProject.mySqlVersion || ''}
|
||||
label="MySQL Sürümü"
|
||||
onChange={(e) => setNewProject({ ...newProject, mySqlVersion: e.target.value })}
|
||||
>
|
||||
{mySqlVersions.filter(v => v.status === 'installed').length > 0 ? (
|
||||
mySqlVersions.filter(v => v.status === 'installed').map(v => (
|
||||
<MenuItem key={v.id} value={v.version}>MySQL {v.version}</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled value="">Önce MySQL sürümü indirmelisiniz</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setIsProjectDialogOpen(false)}>İptal</Button>
|
||||
<Button onClick={() => {
|
||||
setIsProjectDialogOpen(false)
|
||||
setNewProject({ name: '', path: '', phpVersion: '', mySqlVersion: '', host: 'localhost' })
|
||||
}}>İptal</Button>
|
||||
<Button
|
||||
onClick={handleAddProject}
|
||||
variant="contained"
|
||||
disabled={!newProject.name || !newProject.path || installedPhpVersions.length === 0}
|
||||
disabled={!newProject.name || !newProject.path || installedPhpVersions.length === 0 || mySqlVersions.filter(v => v.status === 'installed').length === 0}
|
||||
>
|
||||
Ekle
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Log Dialog */}
|
||||
|
||||
Reference in New Issue
Block a user