feat: implement PHP and MySQL version management services with automated download and cataloging capabilities
This commit is contained in:
Generated
+164
-1
@@ -33,6 +33,7 @@
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"electron": "^31.7.7",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-reload": "^2.0.0-alpha.1",
|
||||
"electron-vite": "^2.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
@@ -2672,6 +2673,20 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/app-builder-bin": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz",
|
||||
@@ -3151,6 +3166,19 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
@@ -3199,6 +3227,19 @@
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
||||
@@ -3516,6 +3557,44 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
@@ -4552,6 +4631,16 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-reload": {
|
||||
"version": "2.0.0-alpha.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-reload/-/electron-reload-2.0.0-alpha.1.tgz",
|
||||
"integrity": "sha512-hTde7gv0TEqxbxlB3pj2CwoyCQ9sdiQrcP8GkpzhosxyVeYM3mZbMEVKCZK3L0fED7Mz5A9IWmK7zEvi4H3P1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.328",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz",
|
||||
@@ -5295,6 +5384,19 @@
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-root": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||
@@ -6194,6 +6296,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-boolean-object": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
|
||||
@@ -6387,6 +6502,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number-object": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
|
||||
@@ -7190,7 +7315,6 @@
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -7518,6 +7642,19 @@
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
@@ -7872,6 +8009,19 @@
|
||||
"minimatch": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect.getprototypeof": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||
@@ -8946,6 +9096,19 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"electron": "^31.7.7",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-reload": "^2.0.0-alpha.1",
|
||||
"electron-vite": "^2.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
|
||||
+37
-1
@@ -1,9 +1,10 @@
|
||||
import { ipcMain, app } from 'electron'
|
||||
import { ipcMain, app, dialog } from 'electron'
|
||||
import { processManager } from '../services/ProcessManager'
|
||||
import { configService } from '../services/ConfigService'
|
||||
import { downloadService } from '../services/DownloadService'
|
||||
import { projectService } from '../services/ProjectService'
|
||||
import { phpManagerService } from '../services/PhpManagerService'
|
||||
import { mySqlManagerService } from '../services/MySqlManagerService'
|
||||
import path from 'path'
|
||||
|
||||
export function registerIpcHandlers(): void {
|
||||
@@ -83,6 +84,41 @@ export function registerIpcHandlers(): void {
|
||||
}
|
||||
})
|
||||
|
||||
// MySQL Versions
|
||||
ipcMain.handle('mysql:versions', async () => {
|
||||
return await mySqlManagerService.getVersions()
|
||||
})
|
||||
|
||||
ipcMain.handle('mysql:download', async (event, id: string) => {
|
||||
const versions = await mySqlManagerService.getVersions()
|
||||
const version = versions.find(v => v.id === id)
|
||||
if (!version) return { success: false, message: 'Geçersiz MySQL sürümü.' }
|
||||
|
||||
try {
|
||||
mySqlManagerService.updateProgress(id, 0, 'downloading')
|
||||
event.sender.send('mysql:progress', { id, progress: 0 })
|
||||
|
||||
await downloadService.downloadAndExtract(version.url, id, (p) => {
|
||||
mySqlManagerService.updateProgress(id, p)
|
||||
event.sender.send('mysql:progress', { id, progress: p })
|
||||
})
|
||||
|
||||
mySqlManagerService.updateProgress(id, 100, 'installed')
|
||||
return { success: true, message: 'MySQL indirme tamamlandı.' }
|
||||
} catch (error: any) {
|
||||
mySqlManagerService.updateProgress(id, 0, 'available')
|
||||
return { success: false, message: `MySQL hatası: ${error.message}` }
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:select-directory', async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
if (result.canceled) return null
|
||||
return result.filePaths[0]
|
||||
})
|
||||
|
||||
ipcMain.handle('binaries:download', async (_event, { name, url }) => {
|
||||
return downloadService.downloadAndExtract(url, name, (p) => {
|
||||
// TODO: Emit progress to renderer via webContents.send
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"mysql_release_catalog": {
|
||||
"generated_date": "2026-03-28",
|
||||
"provider": "Oracle MySQL",
|
||||
"releases": [
|
||||
{
|
||||
"series": "9.x (Innovation)",
|
||||
"version": "9.2.0",
|
||||
"status": "Latest Innovation",
|
||||
"php_min_recommended": "PHP 8.2+",
|
||||
"download_url": "https://dev.mysql.com/downloads/mysql/",
|
||||
"is_archive": false
|
||||
},
|
||||
{
|
||||
"series": "8.4 (LTS)",
|
||||
"version": "8.4.2",
|
||||
"status": "Current LTS",
|
||||
"php_min_recommended": "PHP 8.1+",
|
||||
"download_url": "https://dev.mysql.com/downloads/mysql/8.4.html",
|
||||
"is_archive": false
|
||||
},
|
||||
{
|
||||
"series": "8.0",
|
||||
"version": "8.0.40",
|
||||
"status": "End of Life (April 2026)",
|
||||
"php_min_recommended": "PHP 7.4 - 8.3",
|
||||
"download_url": "https://downloads.mysql.com/archives/community/?product=community&version=8.0.40",
|
||||
"is_archive": true
|
||||
},
|
||||
{
|
||||
"series": "5.7",
|
||||
"version": "5.7.44",
|
||||
"status": "End of Life (2023)",
|
||||
"php_min_recommended": "PHP 5.6 - 7.4",
|
||||
"download_url": "https://downloads.mysql.com/archives/community/?product=community&version=5.7.44",
|
||||
"is_archive": true
|
||||
},
|
||||
{
|
||||
"series": "5.6",
|
||||
"version": "5.6.51",
|
||||
"status": "Legacy / End of Life",
|
||||
"php_min_recommended": "PHP 5.5 - 5.6",
|
||||
"download_url": "https://downloads.mysql.com/archives/community/?product=community&version=5.6.51",
|
||||
"is_archive": true
|
||||
},
|
||||
{
|
||||
"series": "5.5",
|
||||
"version": "5.5.62",
|
||||
"status": "Legacy / End of Life",
|
||||
"php_min_recommended": "PHP 5.4 - 5.6",
|
||||
"download_url": "https://downloads.mysql.com/archives/community/?product=community&version=5.5.62",
|
||||
"is_archive": true
|
||||
}
|
||||
],
|
||||
"useful_links": {
|
||||
"full_archive": "https://downloads.mysql.com/archives/community/",
|
||||
"yum_repo": "https://dev.mysql.com/downloads/repo/yum/",
|
||||
"apt_repo": "https://dev.mysql.com/downloads/repo/apt/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,8 @@ export class DownloadService {
|
||||
method: 'GET',
|
||||
responseType: 'stream',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Referer': url.includes('mysql.com') ? 'https://dev.mysql.com/downloads/mysql/' : undefined
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
|
||||
export interface MySqlVersion {
|
||||
id: string
|
||||
version: string
|
||||
cycle: string
|
||||
url: string
|
||||
status: 'available' | 'downloading' | 'installed'
|
||||
progress: number
|
||||
description?: string
|
||||
}
|
||||
|
||||
export class MySqlManagerService {
|
||||
private versions: MySqlVersion[] = []
|
||||
private binDir: string
|
||||
private catalogPath: string
|
||||
|
||||
constructor() {
|
||||
this.binDir = path.join(app.getPath('userData'), 'bin')
|
||||
// In development, it's in src/main/resources, but we should handle both
|
||||
this.catalogPath = path.join(app.getAppPath(), 'src/main/resources/mysql_catalog.json')
|
||||
if (!fs.existsSync(this.catalogPath)) {
|
||||
// Fallback for production or different root
|
||||
this.catalogPath = path.join(process.cwd(), 'src/main/resources/mysql_catalog.json')
|
||||
}
|
||||
this.init()
|
||||
}
|
||||
|
||||
private async init() {
|
||||
if (!fs.existsSync(this.binDir)) {
|
||||
fs.mkdirSync(this.binDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
async fetchAvailableVersions(): Promise<MySqlVersion[]> {
|
||||
console.log('Loading MySQL versions from local catalog:', this.catalogPath)
|
||||
try {
|
||||
if (!fs.existsSync(this.catalogPath)) {
|
||||
console.error('MySQL Catalog not found at:', this.catalogPath)
|
||||
return this.versions
|
||||
}
|
||||
|
||||
const rawData = fs.readFileSync(this.catalogPath, 'utf8')
|
||||
const catalog = JSON.parse(rawData)
|
||||
const releases = catalog.mysql_release_catalog.releases as any[]
|
||||
|
||||
const dynamicVersions: MySqlVersion[] = releases.map(v => {
|
||||
const version = v.version
|
||||
const cycle = v.series
|
||||
// Pattern: https://downloads.mysql.com/archives/get/p/23/file/mysql-VERSION-winx64.zip
|
||||
const url = `https://downloads.mysql.com/archives/get/p/23/file/mysql-${version}-winx64.zip`
|
||||
|
||||
return {
|
||||
id: `mysql-${version}`,
|
||||
version,
|
||||
cycle,
|
||||
url,
|
||||
status: 'available',
|
||||
progress: 0,
|
||||
description: v.status
|
||||
}
|
||||
})
|
||||
|
||||
this.updateVersionsList(dynamicVersions)
|
||||
return this.versions
|
||||
} catch (e: any) {
|
||||
console.error('Failed to load MySQL catalog:', e.message)
|
||||
return this.versions
|
||||
}
|
||||
}
|
||||
|
||||
private updateVersionsList(newList: MySqlVersion[]) {
|
||||
this.versions = newList.map(dv => {
|
||||
const current = this.versions.find(cv => cv.id === dv.id)
|
||||
if (current && current.status === 'downloading') return current
|
||||
return dv
|
||||
}).sort((a, b) => b.version.localeCompare(a.version, undefined, { numeric: true, sensitivity: 'base' }))
|
||||
|
||||
this.refreshInstalledStatus()
|
||||
}
|
||||
|
||||
private refreshInstalledStatus() {
|
||||
this.versions = this.versions.map(v => {
|
||||
const targetDir = path.join(this.binDir, v.id)
|
||||
const possiblePaths = [
|
||||
path.join(targetDir, 'bin', 'mysqld.exe'),
|
||||
path.join(targetDir, `mysql-${v.version}-winx64`, 'bin', 'mysqld.exe')
|
||||
]
|
||||
|
||||
const isInstalled = possiblePaths.some(p => fs.existsSync(p))
|
||||
|
||||
if (isInstalled) {
|
||||
return { ...v, status: 'installed', progress: 100 }
|
||||
}
|
||||
return v
|
||||
})
|
||||
}
|
||||
|
||||
async getVersions(): Promise<MySqlVersion[]> {
|
||||
if (this.versions.length === 0) {
|
||||
await this.fetchAvailableVersions()
|
||||
} else {
|
||||
this.refreshInstalledStatus()
|
||||
}
|
||||
return this.versions
|
||||
}
|
||||
|
||||
updateProgress(id: string, progress: number, status: MySqlVersion['status'] = 'downloading') {
|
||||
const version = this.versions.find(v => v.id === id)
|
||||
if (version) {
|
||||
version.progress = progress
|
||||
version.status = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mySqlManagerService = new MySqlManagerService()
|
||||
@@ -15,6 +15,7 @@ export class PhpManagerService {
|
||||
private versions: PhpVersion[] = []
|
||||
private binDir: string
|
||||
private winPhpUrl = 'https://windows.php.net/downloads/releases/'
|
||||
private winArchiveUrl = 'https://windows.php.net/downloads/releases/archives/'
|
||||
private winReleasesJsonUrl = 'https://windows.php.net/downloads/releases/releases.json'
|
||||
private mainReleasesJsonUrl = 'https://www.php.net/releases/index.php?json'
|
||||
|
||||
@@ -32,28 +33,29 @@ export class PhpManagerService {
|
||||
|
||||
private async fetchAvailableVersions() {
|
||||
try {
|
||||
// Fetch from windows.php.net for actual paths
|
||||
// 1. Fetch from windows.php.net for current releases
|
||||
const winResponse = await axios.get(this.winReleasesJsonUrl, {
|
||||
headers: { 'User-Agent': 'Mozilla/5.0' }
|
||||
})
|
||||
}).catch(() => ({ data: {} }))
|
||||
const winData = winResponse.data
|
||||
|
||||
// Fetch from php.net for supported versions list
|
||||
// 2. Fetch from main php.net for the comprehensive version list
|
||||
const mainResponse = await axios.get(this.mainReleasesJsonUrl, {
|
||||
headers: { 'User-Agent': 'Mozilla/5.0' }
|
||||
})
|
||||
const mainData = mainResponse.data
|
||||
|
||||
const dynamicVersions: PhpVersion[] = []
|
||||
const dynamicVersions: Map<string, PhpVersion> = new Map()
|
||||
|
||||
// Method 1: Versions explicitly available in windows releases.json
|
||||
for (const majorMinor in winData) {
|
||||
const release = winData[majorMinor]
|
||||
// Strategy A: Direct matches from windows releases.json (current & stable)
|
||||
for (const branch in winData) {
|
||||
const release = winData[branch]
|
||||
const version = release.version
|
||||
const tsBuild = release['ts-vs16-x64'] || release['ts-vs17-x64'] || release['ts-vc15-x64']
|
||||
// prioritize ts-vs16/17-x64
|
||||
const tsBuild = release['ts-vs16-x64'] || release['ts-vs17-x64'] || release['ts-vc15-x64'] || release['ts-vc14-x64']
|
||||
|
||||
if (tsBuild && tsBuild.zip) {
|
||||
dynamicVersions.push({
|
||||
dynamicVersions.set(version, {
|
||||
id: `php-${version}`,
|
||||
version: version,
|
||||
url: this.winPhpUrl + tsBuild.zip.path,
|
||||
@@ -63,21 +65,56 @@ export class PhpManagerService {
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Check supported_versions from main php.net
|
||||
// This helps if windows releases.json doesn't group some newer versions yet
|
||||
if (mainData['8'] && mainData['8'].supported_versions) {
|
||||
for (const ver of mainData['8'].supported_versions) {
|
||||
// ver is "8.2", "8.3" etc.
|
||||
// If we already have a version for this branch, we are good.
|
||||
// Otherwise, we could try to guess or find more.
|
||||
// Strategy B: Legacy versions from main php.net JSON
|
||||
for (const majorKey in mainData) {
|
||||
const majorEntry = mainData[majorKey]
|
||||
const latestVersion = majorEntry.version
|
||||
|
||||
if (!dynamicVersions.has(latestVersion)) {
|
||||
// Construct archive URL for older versions like 5.6.40, 7.4.33 etc if not in winData
|
||||
// Pattern: php-VERSION-Win32-COMPILER-ARCH.zip
|
||||
let arch = 'x64'
|
||||
let compiler = 'vc15'
|
||||
|
||||
if (latestVersion.startsWith('5.6')) compiler = 'vc11'
|
||||
else if (latestVersion.startsWith('7.0') || latestVersion.startsWith('7.1')) compiler = 'vc14'
|
||||
else if (latestVersion.startsWith('7.2') || latestVersion.startsWith('7.3') || latestVersion.startsWith('7.4')) compiler = 'vc15'
|
||||
else compiler = 'vs16'
|
||||
|
||||
const url = `${this.winArchiveUrl}php-${latestVersion}-Win32-${compiler}-${arch}.zip`
|
||||
|
||||
// We add it, noting that older versions might have different naming conventions
|
||||
// But for 5.6 and up, this pattern is generally consistent for x64 TS.
|
||||
dynamicVersions.set(latestVersion, {
|
||||
id: `php-${latestVersion}`,
|
||||
version: latestVersion,
|
||||
url: url,
|
||||
status: 'available',
|
||||
progress: 0
|
||||
})
|
||||
}
|
||||
|
||||
// Also check for supported versions list (e.g. older 8.x)
|
||||
if (majorEntry.supported_versions) {
|
||||
for (const branch of majorEntry.supported_versions) {
|
||||
// This JSON doesn't give specific patch versions for these branches,
|
||||
// but winData usually covers the latest of each branch anyway.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// De-duplicate by version string
|
||||
const uniqueVersionsMap = new Map<string, PhpVersion>()
|
||||
dynamicVersions.forEach(v => uniqueVersionsMap.set(v.version, v))
|
||||
// De-duplicate and sort
|
||||
this.versions = Array.from(dynamicVersions.values()).sort((a, b) => {
|
||||
// Semi-numeric sort to handle versions correctly
|
||||
const partsA = a.version.split('.').map(Number)
|
||||
const partsB = b.version.split('.').map(Number)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if ((partsA[i] || 0) > (partsB[i] || 0)) return -1
|
||||
if ((partsA[i] || 0) < (partsB[i] || 0)) return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
this.versions = Array.from(uniqueVersionsMap.values()).sort((a, b) => b.version.localeCompare(a.version))
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch PHP versions:', e)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,11 @@ const api = {
|
||||
removeProject: (id: string) => electronAPI.ipcRenderer.invoke('projects:remove', id),
|
||||
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))
|
||||
onBinaryProgress: (callback: any) => electronAPI.ipcRenderer.on('binaries:progress', (_event, data) => callback(data)),
|
||||
getMySqlVersions: () => electronAPI.ipcRenderer.invoke('mysql:versions'),
|
||||
downloadMySqlVersion: (id: string) => electronAPI.ipcRenderer.invoke('mysql:download', id),
|
||||
onMySqlProgress: (callback: any) => electronAPI.ipcRenderer.on('mysql:progress', (_event, data) => callback(data)),
|
||||
selectDirectory: () => electronAPI.ipcRenderer.invoke('dialog:select-directory')
|
||||
}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
|
||||
+222
-71
@@ -29,7 +29,11 @@ import {
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
LinearProgress
|
||||
LinearProgress,
|
||||
Tabs,
|
||||
Tab,
|
||||
InputAdornment,
|
||||
CircularProgress
|
||||
} from '@mui/material'
|
||||
import {
|
||||
Dashboard as DashboardIcon,
|
||||
@@ -44,7 +48,9 @@ import {
|
||||
Delete as DeleteIcon,
|
||||
Download as DownloadIcon,
|
||||
CheckCircle as InstalledIcon,
|
||||
CloudDownload as AvailableIcon
|
||||
CloudDownload as AvailableIcon,
|
||||
Refresh as RefreshIcon,
|
||||
FolderOpen as FolderOpenIcon
|
||||
} from '@mui/icons-material'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -70,10 +76,13 @@ function App(): JSX.Element {
|
||||
mysql: 'stopped'
|
||||
})
|
||||
const [activeTab, setActiveTab] = useState('dashboard')
|
||||
const [settingsTab, setSettingsTab] = useState(0)
|
||||
const [projects, setProjects] = useState<any[]>([])
|
||||
const [phpVersions, setPhpVersions] = useState<any[]>([])
|
||||
const [mySqlVersions, setMySqlVersions] = useState<any[]>([])
|
||||
const [loadingVersions, setLoadingVersions] = useState(false)
|
||||
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false)
|
||||
const [newProject, setNewProject] = useState({ name: '', path: '', phpVersion: '8.2', host: 'localhost' })
|
||||
const [newProject, setNewProject] = useState({ name: '', path: '', phpVersion: '', host: 'localhost' })
|
||||
const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({
|
||||
open: false,
|
||||
message: '',
|
||||
@@ -81,51 +90,96 @@ function App(): JSX.Element {
|
||||
})
|
||||
|
||||
const fetchStatus = async () => {
|
||||
// @ts-ignore
|
||||
const currentStatus = await window.api.getServiceStatus()
|
||||
if (currentStatus) setStatus(currentStatus)
|
||||
if (window.api && window.api.getServiceStatus) {
|
||||
const currentStatus = await window.api.getServiceStatus()
|
||||
if (currentStatus) setStatus(currentStatus)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchProjects = async () => {
|
||||
// @ts-ignore
|
||||
const list = await window.api.getProjects()
|
||||
setProjects(list || [])
|
||||
if (window.api && window.api.getProjects) {
|
||||
const list = await window.api.getProjects()
|
||||
setProjects(list || [])
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPhpVersions = async () => {
|
||||
// @ts-ignore
|
||||
const list = await window.api.getPhpVersions()
|
||||
setPhpVersions(list || [])
|
||||
if (window.api && window.api.getPhpVersions) {
|
||||
const list = await window.api.getPhpVersions()
|
||||
setPhpVersions(list || [])
|
||||
}
|
||||
}
|
||||
|
||||
const fetchMySqlVersions = async () => {
|
||||
if (window.api && window.api.getMySqlVersions) {
|
||||
const list = await window.api.getMySqlVersions()
|
||||
setMySqlVersions(list || [])
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefreshVersions = async () => {
|
||||
setLoadingVersions(true)
|
||||
try {
|
||||
await Promise.all([fetchPhpVersions(), fetchMySqlVersions()])
|
||||
} finally {
|
||||
setLoadingVersions(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isProjectDialogOpen && !newProject.phpVersion && phpVersions.length > 0) {
|
||||
const installed = phpVersions.find(v => v.status === 'installed')
|
||||
if (installed) {
|
||||
setNewProject(prev => ({ ...prev, phpVersion: installed.version }))
|
||||
}
|
||||
}
|
||||
}, [isProjectDialogOpen, phpVersions])
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.api) return
|
||||
|
||||
fetchStatus()
|
||||
fetchProjects()
|
||||
fetchPhpVersions()
|
||||
fetchMySqlVersions()
|
||||
|
||||
// @ts-ignore
|
||||
const removeListener = window.api.onBinaryProgress((data) => {
|
||||
const removePhpListener = window.api.onBinaryProgress ? window.api.onBinaryProgress((data) => {
|
||||
if (!data) return
|
||||
const { id, progress } = data
|
||||
setPhpVersions(prev => prev.map(v => v.id === id ? { ...v, progress, status: progress === 100 ? 'installed' : 'downloading' } : v))
|
||||
})
|
||||
}) : null
|
||||
|
||||
const removeMySqlListener = window.api.onMySqlProgress ? window.api.onMySqlProgress((data) => {
|
||||
if (!data) return
|
||||
const { id, progress } = data
|
||||
setMySqlVersions(prev => prev.map(v => v.id === id ? { ...v, progress, status: progress === 100 ? 'installed' : 'downloading' } : v))
|
||||
}) : null
|
||||
|
||||
const interval = setInterval(() => {
|
||||
fetchStatus()
|
||||
fetchProjects()
|
||||
fetchPhpVersions()
|
||||
fetchMySqlVersions()
|
||||
}, 3000)
|
||||
|
||||
return () => {
|
||||
clearInterval(interval)
|
||||
if (typeof removeListener === 'function') removeListener()
|
||||
if (typeof removePhpListener === 'function') removePhpListener()
|
||||
if (typeof removeMySqlListener === 'function') removeMySqlListener()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleSelectPath = async () => {
|
||||
if (window.api && window.api.selectDirectory) {
|
||||
const path = await window.api.selectDirectory()
|
||||
if (path) {
|
||||
setNewProject(prev => ({ ...prev, path }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleService = async (service: keyof ServiceStatus) => {
|
||||
// @ts-ignore
|
||||
if (!window.api) return
|
||||
const result = status[service] === 'running'
|
||||
? await window.api.stopService(service)
|
||||
: await window.api.startService(service)
|
||||
@@ -143,10 +197,10 @@ function App(): JSX.Element {
|
||||
}
|
||||
|
||||
const handleStartAll = async () => {
|
||||
if (!window.api) return
|
||||
const services: (keyof ServiceStatus)[] = ['nginx', 'php', 'mysql']
|
||||
for (const s of services) {
|
||||
if (status[s] !== 'running') {
|
||||
// @ts-ignore
|
||||
await window.api.startService(s)
|
||||
}
|
||||
}
|
||||
@@ -154,10 +208,10 @@ function App(): JSX.Element {
|
||||
}
|
||||
|
||||
const handleStopAll = async () => {
|
||||
if (!window.api) return
|
||||
const services: (keyof ServiceStatus)[] = ['nginx', 'php', 'mysql']
|
||||
for (const s of services) {
|
||||
if (status[s] === 'running') {
|
||||
// @ts-ignore
|
||||
await window.api.stopService(s)
|
||||
}
|
||||
}
|
||||
@@ -170,23 +224,23 @@ function App(): JSX.Element {
|
||||
}
|
||||
|
||||
const handleAddProject = async () => {
|
||||
// @ts-ignore
|
||||
if (!window.api) return
|
||||
await window.api.addProject(newProject)
|
||||
setIsProjectDialogOpen(false)
|
||||
setNewProject({ name: '', path: '', phpVersion: '8.2', host: 'localhost' })
|
||||
setNewProject({ name: '', path: '', phpVersion: '', host: 'localhost' })
|
||||
setNotification({ open: true, message: 'Proje başarıyla eklendi.', severity: 'success' })
|
||||
fetchProjects()
|
||||
}
|
||||
|
||||
const handleRemoveProject = async (id: string) => {
|
||||
// @ts-ignore
|
||||
if (!window.api) return
|
||||
await window.api.removeProject(id)
|
||||
setNotification({ open: true, message: 'Proje silindi.', severity: 'success' })
|
||||
fetchProjects()
|
||||
}
|
||||
|
||||
const handleDownloadPhp = async (id: string) => {
|
||||
// @ts-ignore
|
||||
if (!window.api) return
|
||||
const result = await window.api.downloadPhpVersion(id)
|
||||
setNotification({
|
||||
open: true,
|
||||
@@ -196,6 +250,19 @@ function App(): JSX.Element {
|
||||
fetchPhpVersions()
|
||||
}
|
||||
|
||||
const handleDownloadMySql = async (id: string) => {
|
||||
if (!window.api) return
|
||||
const result = await window.api.downloadMySqlVersion(id)
|
||||
setNotification({
|
||||
open: true,
|
||||
message: result.message,
|
||||
severity: result.success ? 'success' : 'error'
|
||||
})
|
||||
fetchMySqlVersions()
|
||||
}
|
||||
|
||||
const installedPhpVersions = phpVersions.filter(v => v.status === 'installed')
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', minHeight: '100vh', backgroundImage: 'radial-gradient(circle at 50% 0%, #153E5E 0%, #121212 100%)', bgcolor: 'transparent' }}>
|
||||
<AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1, bgcolor: 'rgba(21, 62, 94, 0.85)', backdropFilter: 'blur(10px)' }}>
|
||||
@@ -208,7 +275,7 @@ function App(): JSX.Element {
|
||||
<LanguageIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<IconButton color="inherit">
|
||||
<IconButton color="inherit" onClick={() => setActiveTab('settings')}>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
@@ -219,7 +286,7 @@ function App(): JSX.Element {
|
||||
sx={{
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box', bgcolor: 'rgba(30, 30, 30, 0.95)', borderRight: '1px solid rgba(255, 255, 255, 0.05)' },
|
||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box', bgcolor: 'rgba(18, 18, 18, 0.95)', borderRight: '1px solid rgba(255, 255, 255, 0.05)' },
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
@@ -255,45 +322,110 @@ function App(): JSX.Element {
|
||||
<Container maxWidth="md">
|
||||
{activeTab === 'settings' && (
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{ fontWeight: 'medium', mb: 3 }}>
|
||||
Genel Ayarlar
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 'medium' }}>
|
||||
Genel Ayarlar
|
||||
</Typography>
|
||||
<Button startIcon={loadingVersions ? <CircularProgress size={20} /> : <RefreshIcon />} onClick={handleRefreshVersions} disabled={loadingVersions}>
|
||||
Listeyi Yenile
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper sx={{ mb: 4, 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' }}>PHP Sürümleri</Typography>
|
||||
<Typography variant="body2" color="text.secondary">Gereken PHP sürümlerini buradan indirebilir ve yönetebilirsiniz.</Typography>
|
||||
</Box>
|
||||
<List>
|
||||
{phpVersions.map((v) => (
|
||||
<ListItem key={v.id} sx={{ py: 2 }}>
|
||||
<ListItemIcon>
|
||||
{v.status === 'installed' ? <InstalledIcon color="success" /> : <AvailableIcon color="action" />}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={`PHP ${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={() => handleDownloadPhp(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>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
|
||||
<Tabs value={settingsTab} onChange={(_e, v) => setSettingsTab(v)} textColor="primary" indicatorColor="primary">
|
||||
<Tab label="PHP Sürümleri" icon={<PhpIcon />} iconPosition="start" />
|
||||
<Tab label="MySQL Sürümleri" icon={<DbIcon />} iconPosition="start" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{settingsTab === 0 && (
|
||||
<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' }}>PHP Sürümleri</Typography>
|
||||
<Typography variant="body2" color="text.secondary">Gereken PHP sürümlerini buradan indirebilir ve yönetebilirsiniz.</Typography>
|
||||
</Box>
|
||||
<List sx={{ maxHeight: 500, overflow: 'auto' }}>
|
||||
{phpVersions.length === 0 && !loadingVersions && (
|
||||
<ListItem><ListItemText primary="Yükleniyor veya liste boş..." /></ListItem>
|
||||
)}
|
||||
{phpVersions.map((v) => (
|
||||
<ListItem key={v.id} sx={{ py: 2 }}>
|
||||
<ListItemIcon>
|
||||
{v.status === 'installed' ? <InstalledIcon color="success" /> : <AvailableIcon color="action" />}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={`PHP ${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={() => handleDownloadPhp(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 === 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>
|
||||
<Typography variant="body2" color="text.secondary">Farklı MySQL sürümlerini indirebilir ve sunucunuzda kullanabilirsiniz.</Typography>
|
||||
</Box>
|
||||
<List sx={{ maxHeight: 500, overflow: 'auto' }}>
|
||||
{mySqlVersions.length === 0 && !loadingVersions && (
|
||||
<ListItem><ListItemText primary="Yükleniyor veya liste boş..." /></ListItem>
|
||||
)}
|
||||
{mySqlVersions.map((v) => (
|
||||
<ListItem key={v.id} sx={{ py: 2 }}>
|
||||
<ListItemIcon>
|
||||
{v.status === 'installed' ? <InstalledIcon color="success" /> : <AvailableIcon color="action" />}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{`MySQL ${v.version} (${v.cycle})`}
|
||||
{v.description && (
|
||||
<Chip label={v.description} size="small" variant="outlined" sx={{ fontSize: '0.7rem', height: 20 }} />
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
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={() => handleDownloadMySql(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>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{activeTab === 'dashboard' && (
|
||||
@@ -405,9 +537,18 @@ function App(): JSX.Element {
|
||||
onChange={(e) => setNewProject({ ...newProject, name: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
label="Dosya Yolu (Absolute path)"
|
||||
label="Dosya Yolu"
|
||||
fullWidth
|
||||
value={newProject.path}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleSelectPath} edge="end">
|
||||
<FolderOpenIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={(e) => setNewProject({ ...newProject, path: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
@@ -416,24 +557,34 @@ function App(): JSX.Element {
|
||||
value={newProject.host}
|
||||
onChange={(e) => setNewProject({ ...newProject, host: e.target.value })}
|
||||
/>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>PHP Sürümü</InputLabel>
|
||||
<FormControl fullWidth disabled={installedPhpVersions.length === 0}>
|
||||
<InputLabel id="php-version-label">PHP Sürümü</InputLabel>
|
||||
<Select
|
||||
labelId="php-version-label"
|
||||
value={newProject.phpVersion}
|
||||
label="PHP Sürümü"
|
||||
onChange={(e) => setNewProject({ ...newProject, phpVersion: e.target.value })}
|
||||
>
|
||||
<MenuItem value="8.0">PHP 8.0</MenuItem>
|
||||
<MenuItem value="8.1">PHP 8.1</MenuItem>
|
||||
<MenuItem value="8.2">PHP 8.2</MenuItem>
|
||||
<MenuItem value="8.3">PHP 8.3</MenuItem>
|
||||
{installedPhpVersions.length > 0 ? (
|
||||
installedPhpVersions.map(v => (
|
||||
<MenuItem key={v.id} value={v.version}>PHP {v.version}</MenuItem>
|
||||
))
|
||||
) : (
|
||||
<MenuItem disabled value="">Önce PHP sürümü indirmelisiniz</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setIsProjectDialogOpen(false)}>İptal</Button>
|
||||
<Button onClick={handleAddProject} variant="contained" disabled={!newProject.name || !newProject.path}>Ekle</Button>
|
||||
<Button
|
||||
onClick={handleAddProject}
|
||||
variant="contained"
|
||||
disabled={!newProject.name || !newProject.path || installedPhpVersions.length === 0}
|
||||
>
|
||||
Ekle
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
||||
<title>fw_error_www</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div align="center">
|
||||
<div align="center">
|
||||
<p> </p>
|
||||
</div>
|
||||
<table width="449" align="center" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="68%" valign="top">
|
||||
<div align="center">
|
||||
<p><img alt="" src="https://www.oracle.com/splash/oralogo_small.gif" width="154" height="19" /></p>
|
||||
<p align="justify"><font face="Arial, Helvetica, sans-serif">This site </font><font face="Arial, Helvetica, sans-serif"> is experiencing technical difficulty. We are aware of the issue and are working as quickly as possible to correct the issue. <br />
|
||||
<br />
|
||||
We apologize for any inconvenience this may have caused. <br />
|
||||
<br />
|
||||
To speak with an Oracle sales representative: 1.800.ORACLE1.<br />
|
||||
<br />
|
||||
To contact Oracle Corporate Headquarters from anywhere in the world: 1.650.506.7000.<br />
|
||||
<br />
|
||||
To get technical support in the United States: 1.800.633.0738.
|
||||
<br><br><br><p style="font-size: 18px" align="left"><b>Incident Number: 0.64e1202.1774680040.2bfa9102</b></p></font><br />
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
</body>
|
||||
@@ -0,0 +1,48 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
|
||||
async function test() {
|
||||
console.log('Testing two-step download...');
|
||||
const url = 'https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.40-winx64.zip';
|
||||
const landing = 'https://dev.mysql.com/downloads/mysql/';
|
||||
|
||||
try {
|
||||
console.log('1. Fetching landing page for cookies...');
|
||||
const landingRes = await axios.get(landing, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
||||
}
|
||||
});
|
||||
|
||||
const cookies = landingRes.headers['set-cookie'];
|
||||
console.log('Cookies received:', cookies ? cookies.length : 0);
|
||||
|
||||
console.log('2. Downloading ZIP with cookies...');
|
||||
const downloadRes = await axios({
|
||||
url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Referer': landing,
|
||||
'Cookie': cookies ? cookies.join('; ') : ''
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
console.log('Download Status:', downloadRes.status);
|
||||
console.log('Content Length:', downloadRes.data.length);
|
||||
if (downloadRes.data.length < 5000) {
|
||||
console.log('Content Preview:', downloadRes.data.toString().substring(0, 500));
|
||||
} else {
|
||||
console.log('Download SUCCESS (likely binary)');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('ERROR:', e.response ? e.response.status : e.message);
|
||||
if (e.response && e.response.data) {
|
||||
console.log('Error Data:', e.response.data.toString().substring(0, 200));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
||||
@@ -0,0 +1,37 @@
|
||||
const axios = require('axios');
|
||||
|
||||
async function test() {
|
||||
console.log('Testing official MySQL endpoints...');
|
||||
const urls = [
|
||||
'https://downloads.mysql.com/archives/community/?json=1',
|
||||
'https://downloads.mysql.com/archives/community/?tpl=version&os=3&json=1',
|
||||
'https://dev.mysql.com/downloads/mysql/?json=1',
|
||||
'https://dev.mysql.com/downloads/mysql/?tpl=version&os=3&json=1'
|
||||
];
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
timeout: 5000
|
||||
});
|
||||
console.log(`URL: ${url}`);
|
||||
console.log(`STATUS: ${response.status}`);
|
||||
console.log(`CONTENT-TYPE: ${response.headers['content-type']}`);
|
||||
console.log(`IS JSON: ${typeof response.data === 'object'}`);
|
||||
if (typeof response.data === 'object') {
|
||||
console.log('DATA PREVIEW:', JSON.stringify(response.data).substring(0, 200));
|
||||
} else {
|
||||
console.log('DATA PREVIEW (100 chars):', String(response.data).substring(0, 100));
|
||||
}
|
||||
console.log('---');
|
||||
} catch (e) {
|
||||
console.log(`URL: ${url} failed: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
||||
Reference in New Issue
Block a user