feat: implement core IPC handlers and service management for Nginx, Apache, and project configuration
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 471 KiB |
@@ -0,0 +1,12 @@
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
try {
|
||||
console.log('Converting icon using npx png-to-ico...');
|
||||
const buffer = execSync('npx -y png-to-ico logo/icon.png');
|
||||
fs.writeFileSync('build/icon.ico', buffer);
|
||||
console.log('Successfully created build/icon.ico (' + buffer.length + ' bytes)');
|
||||
} catch (err) {
|
||||
console.error('Conversion failed:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
Generated
+106
-4
@@ -21,6 +21,8 @@
|
||||
"@types/prismjs": "^1.26.6",
|
||||
"axios": "^1.14.0",
|
||||
"decompress": "^4.2.1",
|
||||
"electron-log": "^5.4.3",
|
||||
"electron-updater": "^6.8.3",
|
||||
"i18next": "^23.1.0",
|
||||
"pidusage": "^4.0.1",
|
||||
"prismjs": "^1.30.0",
|
||||
@@ -3376,7 +3378,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/array-buffer-byte-length": {
|
||||
@@ -5207,6 +5208,15 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-log": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz",
|
||||
"integrity": "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-publish": {
|
||||
"version": "24.13.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz",
|
||||
@@ -5278,6 +5288,82 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/electron-updater": {
|
||||
"version": "6.8.3",
|
||||
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz",
|
||||
"integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"builder-util-runtime": "9.5.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
"lodash.escaperegexp": "^4.1.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"semver": "~7.7.3",
|
||||
"tiny-typed-emitter": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/builder-util-runtime": {
|
||||
"version": "9.5.1",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
|
||||
"integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/jsonfile": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-vite": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.3.0.tgz",
|
||||
@@ -7451,7 +7537,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -7556,7 +7641,6 @@
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lazystream": {
|
||||
@@ -7676,6 +7760,12 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.escaperegexp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
@@ -7684,6 +7774,13 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
@@ -9120,7 +9217,6 @@
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
|
||||
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=11.0.0"
|
||||
@@ -9854,6 +9950,12 @@
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-typed-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||
|
||||
+39
-10
@@ -17,6 +17,8 @@
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
"build:win": "npm run build && electron-builder --win",
|
||||
"dist": "npm run build && electron-builder --win",
|
||||
"publish:win": "npm run build && electron-builder --win --publish always",
|
||||
"build:mac": "npm run build && electron-builder --mac",
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
},
|
||||
@@ -33,6 +35,8 @@
|
||||
"@types/prismjs": "^1.26.6",
|
||||
"axios": "^1.14.0",
|
||||
"decompress": "^4.2.1",
|
||||
"electron-log": "^5.4.3",
|
||||
"electron-updater": "^6.8.3",
|
||||
"i18next": "^23.1.0",
|
||||
"pidusage": "^4.0.1",
|
||||
"prismjs": "^1.30.0",
|
||||
@@ -63,8 +67,15 @@
|
||||
"vite": "^5.3.1"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.multiphp",
|
||||
"productName": "Multi-PHP",
|
||||
"appId": "com.truncgil.multiphp",
|
||||
"productName": "Trunçgil MultiPHP",
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github",
|
||||
"owner": "truncgil",
|
||||
"repo": "multiphp"
|
||||
}
|
||||
],
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
@@ -76,21 +87,39 @@
|
||||
],
|
||||
"asar": true,
|
||||
"win": {
|
||||
"icon": "resources/icon.png",
|
||||
"icon": "build/icon.ico",
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
"nsis"
|
||||
]
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"shortcutName": "Trunçgil MultiPHP",
|
||||
"installerIcon": "build/icon.ico",
|
||||
"uninstallerIcon": "build/icon.ico",
|
||||
"installerHeaderIcon": "build/icon.ico",
|
||||
"perMachine": false,
|
||||
"multiLanguageInstaller": true,
|
||||
"installerLanguages": [
|
||||
"tr_TR",
|
||||
"en_US",
|
||||
"de_DE",
|
||||
"ru_RU"
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"icon": "resources/icon.png"
|
||||
"category": "public.app-category.developer-tools",
|
||||
"target": [
|
||||
"dmg"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"icon": "resources/icon.png"
|
||||
"target": [
|
||||
"AppImage"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 471 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 496 KiB |
@@ -3,6 +3,7 @@ import { join } from 'path'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import { registerIpcHandlers } from './ipc'
|
||||
import { cliAliasService } from './services/CliAliasService'
|
||||
import { updateService } from './services/UpdateService'
|
||||
|
||||
let tray: Tray | null = null
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
@@ -86,6 +87,10 @@ app.whenReady().then(() => {
|
||||
|
||||
registerIpcHandlers()
|
||||
createWindow()
|
||||
if (mainWindow) {
|
||||
updateService.setMainWindow(mainWindow)
|
||||
updateService.init()
|
||||
}
|
||||
createTray()
|
||||
|
||||
// Generate CLI aliases on start
|
||||
|
||||
+16
-4
@@ -949,6 +949,18 @@ export function registerIpcHandlers(): void {
|
||||
return await shell.openPath(pathStr)
|
||||
})
|
||||
|
||||
ipcMain.handle('www:open', async () => {
|
||||
try {
|
||||
const wwwPath = path.join(configService.getBasePath(), 'www')
|
||||
if (!fs.existsSync(wwwPath)) fs.mkdirSync(wwwPath, { recursive: true })
|
||||
await shell.openPath(wwwPath)
|
||||
return { success: true }
|
||||
} catch (error: any) {
|
||||
console.error('Failed to open www path:', error)
|
||||
return { success: false, message: error.message }
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('open-vscode', async (_event, pathStr: string) => {
|
||||
return new Promise((resolve) => {
|
||||
exec(`code "${pathStr}"`, (error) => {
|
||||
@@ -1444,13 +1456,13 @@ export function registerIpcHandlers(): void {
|
||||
|
||||
ipcMain.handle('docs:open-user-guide', async (_event, locale: string) => {
|
||||
try {
|
||||
const guidePath = path.join(process.cwd(), 'docs', 'user-guide', locale, 'index.md')
|
||||
const guidePath = path.join(configService.getAppPath(), 'docs', 'user-guide', locale, 'index.md')
|
||||
if (fs.existsSync(guidePath)) {
|
||||
await shell.openPath(guidePath)
|
||||
return { success: true }
|
||||
} else {
|
||||
// Fallback to directory or English if specific locale file doesn't exist
|
||||
const langDir = path.join(process.cwd(), 'docs', 'user-guide', locale)
|
||||
const langDir = path.join(configService.getAppPath(), 'docs', 'user-guide', locale)
|
||||
if (fs.existsSync(langDir)) {
|
||||
await shell.openPath(langDir)
|
||||
return { success: true }
|
||||
@@ -1465,7 +1477,7 @@ export function registerIpcHandlers(): void {
|
||||
|
||||
ipcMain.handle('docs:list-topics', async (_event, locale: string) => {
|
||||
try {
|
||||
const localeDir = path.join(process.cwd(), 'docs', 'user-guide', locale)
|
||||
const localeDir = path.join(configService.getAppPath(), 'docs', 'user-guide', locale)
|
||||
if (!fs.existsSync(localeDir)) return []
|
||||
|
||||
const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.md'))
|
||||
@@ -1493,7 +1505,7 @@ export function registerIpcHandlers(): void {
|
||||
|
||||
ipcMain.handle('docs:read-topic', async (_event, { locale, file }) => {
|
||||
try {
|
||||
const filePath = path.join(process.cwd(), 'docs', 'user-guide', locale, file)
|
||||
const filePath = path.join(configService.getAppPath(), 'docs', 'user-guide', locale, file)
|
||||
if (fs.existsSync(filePath)) {
|
||||
return fs.readFileSync(filePath, 'utf8')
|
||||
}
|
||||
|
||||
@@ -23,24 +23,67 @@ export class ConfigService {
|
||||
|
||||
constructor() {
|
||||
this.templateDir = path.join(app.getAppPath(), 'config')
|
||||
const base = this.getBasePath()
|
||||
const base = this.getDataPath()
|
||||
this.configDir = path.join(base, 'generated_configs')
|
||||
this.settingsPath = path.join(base, 'settings.json')
|
||||
|
||||
// Ensure directories exist
|
||||
if (!fs.existsSync(this.configDir)) {
|
||||
try {
|
||||
fs.mkdirSync(this.configDir, { recursive: true })
|
||||
} catch (e) {
|
||||
console.error('Failed to create config directory:', e)
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(this.settingsPath)) {
|
||||
try {
|
||||
fs.writeFileSync(this.settingsPath, JSON.stringify(this.defaultSettings, null, 2))
|
||||
} catch (e) {
|
||||
console.error('Failed to create settings file:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base path for writable data (configs, binaries, logs).
|
||||
* If packaged, checks for write access to the executable directory.
|
||||
* If not writable (e.g. Program Files), falls back to app.getPath('userData').
|
||||
*/
|
||||
getDataPath(): string {
|
||||
if (!app.isPackaged) {
|
||||
return process.cwd()
|
||||
}
|
||||
|
||||
const exeDir = path.dirname(app.getPath('exe'))
|
||||
|
||||
// Check if we are in Program Files
|
||||
const isProgramFiles = exeDir.toLowerCase().includes('program files')
|
||||
|
||||
if (isProgramFiles) {
|
||||
return app.getPath('userData')
|
||||
}
|
||||
|
||||
// Check for write access
|
||||
try {
|
||||
const testFile = path.join(exeDir, '.write_test')
|
||||
fs.writeFileSync(testFile, 'test')
|
||||
fs.unlinkSync(testFile)
|
||||
return exeDir // Writable (Portable Mode)
|
||||
} catch (e) {
|
||||
return app.getPath('userData') // Not writable, use AppData
|
||||
}
|
||||
}
|
||||
|
||||
getBasePath(): string {
|
||||
if (app.isPackaged) {
|
||||
return path.dirname(app.getPath('exe'))
|
||||
return this.getDataPath()
|
||||
}
|
||||
return process.cwd()
|
||||
|
||||
/**
|
||||
* Returns the read-only application path (where templates, docs, etc. are located).
|
||||
*/
|
||||
getAppPath(): string {
|
||||
return app.getAppPath()
|
||||
}
|
||||
|
||||
async generateConfig(templateName: string, targetName: string, variables: Record<string, string>): Promise<string> {
|
||||
|
||||
@@ -22,10 +22,19 @@ export class MariaDbManagerService {
|
||||
|
||||
constructor() {
|
||||
this.binDir = path.join(configService.getBasePath(), 'bin')
|
||||
this.catalogPath = path.join(app.getAppPath(), 'resources/mariadb_catalog.json')
|
||||
if (!fs.existsSync(this.catalogPath)) {
|
||||
this.catalogPath = path.join(process.cwd(), 'resources/mariadb_catalog.json')
|
||||
}
|
||||
|
||||
// Path resolution for both development and production (packaged)
|
||||
const appPath = app.getAppPath()
|
||||
const possiblePaths = [
|
||||
path.join(appPath, 'resources/mariadb_catalog.json'),
|
||||
path.join(appPath, '../../resources/mariadb_catalog.json'),
|
||||
path.join(process.resourcesPath, 'mariadb_catalog.json'),
|
||||
path.join(process.resourcesPath, 'resources/mariadb_catalog.json'),
|
||||
path.join(process.cwd(), 'resources/mariadb_catalog.json')
|
||||
]
|
||||
|
||||
this.catalogPath = possiblePaths.find(p => fs.existsSync(p)) || possiblePaths[0]
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { ipcMain, BrowserWindow } from 'electron'
|
||||
import log from 'electron-log'
|
||||
|
||||
// Configure logging
|
||||
autoUpdater.logger = log
|
||||
// @ts-ignore
|
||||
autoUpdater.logger.transports.file.level = 'info'
|
||||
|
||||
export class UpdateService {
|
||||
private mainWindow: BrowserWindow | null = null
|
||||
|
||||
constructor() {
|
||||
this.setupHandlers()
|
||||
}
|
||||
|
||||
setMainWindow(window: BrowserWindow) {
|
||||
this.mainWindow = window
|
||||
}
|
||||
|
||||
private setupHandlers() {
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
this.sendToRenderer('update-status', 'checking')
|
||||
})
|
||||
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
this.sendToRenderer('update-status', 'available', info)
|
||||
})
|
||||
|
||||
autoUpdater.on('update-not-available', (info) => {
|
||||
this.sendToRenderer('update-status', 'not-available', info)
|
||||
})
|
||||
|
||||
autoUpdater.on('error', (err) => {
|
||||
let message = err.message
|
||||
if (message.includes('No published versions on GitHub')) {
|
||||
message = 'Henüz GitHub üzerinde yayınlanmış bir sürüm bulunmamaktadır. (No published versions on GitHub)'
|
||||
}
|
||||
this.sendToRenderer('update-status', 'error', message)
|
||||
})
|
||||
|
||||
autoUpdater.on('download-progress', (progressObj) => {
|
||||
this.sendToRenderer('update-progress', progressObj)
|
||||
})
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
this.sendToRenderer('update-status', 'downloaded', info)
|
||||
})
|
||||
|
||||
// IPC handlers for manual control
|
||||
ipcMain.handle('check-for-updates', async () => {
|
||||
try {
|
||||
return await autoUpdater.checkForUpdatesAndNotify()
|
||||
} catch (error: any) {
|
||||
return { error: error.message }
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('download-update', async () => {
|
||||
return await autoUpdater.downloadUpdate()
|
||||
})
|
||||
|
||||
ipcMain.handle('quit-and-install', () => {
|
||||
autoUpdater.quitAndInstall()
|
||||
})
|
||||
}
|
||||
|
||||
private sendToRenderer(channel: string, ...args: any[]) {
|
||||
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||
this.mainWindow.webContents.send(channel, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
// You can enable semi-automatic updates here if desired
|
||||
// autoUpdater.checkForUpdatesAndNotify()
|
||||
}
|
||||
}
|
||||
|
||||
export const updateService = new UpdateService()
|
||||
@@ -44,6 +44,19 @@ const api = {
|
||||
electronAPI.ipcRenderer.on('mariadb:export:log', listener)
|
||||
return () => electronAPI.ipcRenderer.removeListener('mariadb:export:log', listener)
|
||||
},
|
||||
checkForUpdates: () => electronAPI.ipcRenderer.invoke('check-for-updates'),
|
||||
downloadUpdate: () => electronAPI.ipcRenderer.invoke('download-update'),
|
||||
quitAndInstall: () => electronAPI.ipcRenderer.invoke('quit-and-install'),
|
||||
onUpdateStatus: (callback: any) => {
|
||||
const listener = (_event, status: string, info: any) => callback(status, info)
|
||||
electronAPI.ipcRenderer.on('update-status', listener)
|
||||
return () => electronAPI.ipcRenderer.removeListener('update-status', listener)
|
||||
},
|
||||
onUpdateProgress: (callback: any) => {
|
||||
const listener = (_event, progress: any) => callback(progress)
|
||||
electronAPI.ipcRenderer.on('update-progress', listener)
|
||||
return () => electronAPI.ipcRenderer.removeListener('update-progress', listener)
|
||||
},
|
||||
invoke: (channel: string, ...args: any[]) => electronAPI.ipcRenderer.invoke(channel, ...args)
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,10 @@ function App(): JSX.Element {
|
||||
severity: 'success'
|
||||
})
|
||||
|
||||
const [updateStatus, setUpdateStatus] = useState<string>('idle')
|
||||
const [updateInfo, setUpdateInfo] = useState<any>(null)
|
||||
const [updateProgress, setUpdateProgress] = useState<any>(null)
|
||||
|
||||
// Log States
|
||||
const [logDialogOpen, setLogDialogOpen] = useState(false)
|
||||
const [selectedService] = useState('')
|
||||
@@ -324,6 +328,15 @@ function App(): JSX.Element {
|
||||
fetchCliConfig()
|
||||
fetchPmaStatus()
|
||||
|
||||
const removeUpdateStatusListener = window.api.onUpdateStatus ? window.api.onUpdateStatus((st: string, info: any) => {
|
||||
setUpdateStatus(st)
|
||||
if (info) setUpdateInfo(info)
|
||||
}) : null
|
||||
|
||||
const removeUpdateProgressListener = window.api.onUpdateProgress ? window.api.onUpdateProgress((p: any) => {
|
||||
setUpdateProgress(p)
|
||||
}) : null
|
||||
|
||||
const removePhpListener = window.api.onBinaryProgress ? window.api.onBinaryProgress((data) => {
|
||||
if (!data) return
|
||||
const { id, progress } = data
|
||||
@@ -379,6 +392,8 @@ function App(): JSX.Element {
|
||||
if (typeof removeNginxListener === 'function') removeNginxListener()
|
||||
if (typeof removeApacheListener === 'function') removeApacheListener()
|
||||
if (typeof removePmaListener === 'function') removePmaListener()
|
||||
if (typeof removeUpdateStatusListener === 'function') removeUpdateStatusListener()
|
||||
if (typeof removeUpdateProgressListener === 'function') removeUpdateProgressListener()
|
||||
}
|
||||
}, [logDialogOpen, selectedService])
|
||||
|
||||
@@ -779,6 +794,16 @@ function App(): JSX.Element {
|
||||
}
|
||||
}
|
||||
|
||||
const handleCheckForUpdates = async () => {
|
||||
if (!window.api) return
|
||||
setUpdateStatus('checking')
|
||||
const result = await window.api.checkForUpdates()
|
||||
if (result && result.error) {
|
||||
setUpdateStatus('error')
|
||||
setNotification({ open: true, message: result.error, severity: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
const installedPhpVersions = phpVersions.filter(v => v.status === 'installed')
|
||||
|
||||
|
||||
@@ -987,6 +1012,16 @@ function App(): JSX.Element {
|
||||
<ListItemText primary={t('mariadb_process_monitor.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Divider sx={{ opacity: 0.05, my: 0.5 }} />
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton onClick={() => window.api.invoke('www:open')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><FolderOpenIcon fontSize="small" sx={{ color: 'var(--color-primary)' }} /></ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('common.open_www', 'WWW Klasörünü Aç')}
|
||||
primaryTypographyProps={{ className: 'text-sm font-medium', sx: { color: 'var(--color-primary)' } }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider sx={{ opacity: 0.05 }} />
|
||||
<List>
|
||||
@@ -1029,6 +1064,7 @@ function App(): JSX.Element {
|
||||
<Tab label="Apache" icon={<ServerIcon />} iconPosition="start" />
|
||||
<Tab label={t('settings.server_settings')} icon={<SettingsIcon />} iconPosition="start" />
|
||||
<Tab label={t('settings.cli_env')} icon={<TerminalIcon />} iconPosition="start" />
|
||||
<Tab label={t('updates.title')} icon={<RefreshIcon />} iconPosition="start" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
@@ -1421,6 +1457,77 @@ function App(): JSX.Element {
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{settingsTab === 6 && (
|
||||
<Paper sx={{ p: 4, bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3, gap: 2 }}>
|
||||
<RefreshIcon color="primary" sx={{ fontSize: 32 }} />
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>{t('updates.title')}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">v1.0.0</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ p: 3, bgcolor: 'rgba(0,0,0,0.2)', borderRadius: 2, border: '1px solid rgba(255,255,255,0.05)', mb: 3 }}>
|
||||
{updateStatus === 'idle' && (
|
||||
<Box sx={{ textAlign: 'center', py: 2 }}>
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>{t('updates.not_available')}</Typography>
|
||||
<Button variant="contained" onClick={handleCheckForUpdates} startIcon={<RefreshIcon />}>
|
||||
{t('updates.check_btn')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{updateStatus === 'checking' && (
|
||||
<Box sx={{ textAlign: 'center', py: 2 }}>
|
||||
<CircularProgress size={24} sx={{ mb: 2 }} />
|
||||
<Typography variant="body1">{t('updates.checking')}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{updateStatus === 'available' && (
|
||||
<Box sx={{ textAlign: 'center', py: 2 }}>
|
||||
<AvailableIcon color="primary" sx={{ fontSize: 48, mb: 1 }} />
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>{t('updates.available', { version: updateInfo?.version })}</Typography>
|
||||
<Button variant="contained" onClick={() => window.api.downloadUpdate()} startIcon={<DownloadIcon />}>
|
||||
{t('common.download')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{updateStatus === 'downloading' && (
|
||||
<Box sx={{ py: 2 }}>
|
||||
<Typography variant="body1" sx={{ mb: 1, textAlign: 'center' }}>{t('updates.downloading')}</Typography>
|
||||
<LinearProgress variant="determinate" value={updateProgress?.percent || 0} sx={{ height: 10, borderRadius: 5, mb: 1 }} />
|
||||
<Typography variant="caption" sx={{ display: 'block', textAlign: 'center' }}>
|
||||
%{Math.round(updateProgress?.percent || 0)} - {Math.round((updateProgress?.bytesPerSecond || 0) / 1024)} KB/s
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{updateStatus === 'downloaded' && (
|
||||
<Box sx={{ textAlign: 'center', py: 2 }}>
|
||||
<InstalledIcon color="success" sx={{ fontSize: 48, mb: 1 }} />
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>{t('updates.downloaded')}</Typography>
|
||||
<Button variant="contained" color="success" onClick={() => window.api.quitAndInstall()} startIcon={<RefreshIcon />}>
|
||||
{t('updates.install_btn')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{updateStatus === 'error' && (
|
||||
<Box sx={{ textAlign: 'center', py: 2 }}>
|
||||
<Typography color="error" sx={{ mb: 2 }}>{t('updates.error', { error: updateInfo })}</Typography>
|
||||
<Button variant="outlined" onClick={handleCheckForUpdates}>{t('updates.check_btn')}</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Alert severity="info" sx={{ bgcolor: 'rgba(2, 136, 209, 0.05)', color: '#91d5ff', '& .MuiAlert-icon': { color: '#91d5ff' } }}>
|
||||
{t('settings.cli_restart_warning')}
|
||||
</Alert>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
@@ -358,5 +358,16 @@
|
||||
"delete_db_confirm": "'{{db}}' veritabanını silmek istediğinize emin misiniz? Bu işlem geri alınamaz!",
|
||||
"delete_user_confirm": "'{{user}}' kullanıcısını silmek istediğinize emin misiniz?",
|
||||
"no_active_version": "Lütfen önce yüklü bir MariaDB sürümü seçin veya bir sürüm indirin."
|
||||
},
|
||||
"updates": {
|
||||
"title": "Güncellemeler",
|
||||
"check_btn": "Güncellemeleri Denetle",
|
||||
"checking": "Güncellemeler denetleniyor...",
|
||||
"available": "Yeni bir güncelleme mevcut: {{version}}",
|
||||
"not_available": "Uygulama zaten güncel.",
|
||||
"downloading": "Güncelleme indiriliyor...",
|
||||
"downloaded": "Güncelleme indirildi. Yüklemek için yeniden başlatın.",
|
||||
"install_btn": "Yeniden Başlat ve Yükle",
|
||||
"error": "Güncelleme hatası: {{error}}"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user