feat: implement Nginx and Apache service management with automated configuration generation and IPC handlers
This commit is contained in:
+2
-1
@@ -88,6 +88,7 @@
|
|||||||
"asar": true,
|
"asar": true,
|
||||||
"win": {
|
"win": {
|
||||||
"icon": "build/icon.ico",
|
"icon": "build/icon.ico",
|
||||||
|
"requestedExecutionLevel": "highestAvailable",
|
||||||
"target": [
|
"target": [
|
||||||
"nsis"
|
"nsis"
|
||||||
]
|
]
|
||||||
@@ -101,7 +102,7 @@
|
|||||||
"installerIcon": "build/icon.ico",
|
"installerIcon": "build/icon.ico",
|
||||||
"uninstallerIcon": "build/icon.ico",
|
"uninstallerIcon": "build/icon.ico",
|
||||||
"installerHeaderIcon": "build/icon.ico",
|
"installerHeaderIcon": "build/icon.ico",
|
||||||
"perMachine": false,
|
"perMachine": true,
|
||||||
"multiLanguageInstaller": true,
|
"multiLanguageInstaller": true,
|
||||||
"installerLanguages": [
|
"installerLanguages": [
|
||||||
"tr_TR",
|
"tr_TR",
|
||||||
|
|||||||
@@ -1515,4 +1515,11 @@ export function registerIpcHandlers(): void {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('app:get-permissions-status', () => {
|
||||||
|
return {
|
||||||
|
hasError: configService.hasPermissionError,
|
||||||
|
path: configService.failedPath
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,25 +21,51 @@ export class ConfigService {
|
|||||||
mariaDbTuning: {} as Record<string, string>
|
mariaDbTuning: {} as Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasPermissionError = false
|
||||||
|
public failedPath = ''
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.templateDir = path.join(app.getAppPath(), 'config')
|
this.templateDir = path.join(app.getAppPath(), 'config')
|
||||||
const base = this.getDataPath()
|
const base = this.getDataPath()
|
||||||
this.configDir = path.join(base, 'generated_configs')
|
this.configDir = path.join(base, 'generated_configs')
|
||||||
this.settingsPath = path.join(base, 'settings.json')
|
this.settingsPath = path.join(base, 'settings.json')
|
||||||
|
|
||||||
|
console.log(`[ConfigService] Base data path: ${base}`)
|
||||||
|
console.log(`[ConfigService] Config dir: ${this.configDir}`)
|
||||||
|
|
||||||
// Ensure directories exist
|
// Ensure directories exist
|
||||||
|
if (!fs.existsSync(base)) {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(base, { recursive: true })
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.code === 'EPERM' || e.code === 'EACCES') {
|
||||||
|
this.hasPermissionError = true
|
||||||
|
this.failedPath = base
|
||||||
|
}
|
||||||
|
console.error(`Failed to create base directory (${base}):`, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(this.configDir)) {
|
if (!fs.existsSync(this.configDir)) {
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(this.configDir, { recursive: true })
|
fs.mkdirSync(this.configDir, { recursive: true })
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error('Failed to create config directory:', e)
|
if (e.code === 'EPERM' || e.code === 'EACCES') {
|
||||||
|
this.hasPermissionError = true
|
||||||
|
this.failedPath = this.configDir
|
||||||
|
}
|
||||||
|
console.error(`Failed to create config directory (${this.configDir}):`, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(this.settingsPath)) {
|
if (!fs.existsSync(this.settingsPath)) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(this.settingsPath, JSON.stringify(this.defaultSettings, null, 2))
|
fs.writeFileSync(this.settingsPath, JSON.stringify(this.defaultSettings, null, 2))
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
|
if (e.code === 'EPERM' || e.code === 'EACCES') {
|
||||||
|
this.hasPermissionError = true
|
||||||
|
this.failedPath = this.settingsPath
|
||||||
|
}
|
||||||
console.error('Failed to create settings file:', e)
|
console.error('Failed to create settings file:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,27 +77,33 @@ export class ConfigService {
|
|||||||
* If not writable (e.g. Program Files), falls back to app.getPath('userData').
|
* If not writable (e.g. Program Files), falls back to app.getPath('userData').
|
||||||
*/
|
*/
|
||||||
getDataPath(): string {
|
getDataPath(): string {
|
||||||
if (!app.isPackaged) {
|
const exeDir = path.dirname(app.getPath('exe'))
|
||||||
|
const isAsar = app.getAppPath().includes('app.asar')
|
||||||
|
|
||||||
|
// If not packaged and not running from asar, we are in development
|
||||||
|
if (!app.isPackaged && !isAsar) {
|
||||||
return process.cwd()
|
return process.cwd()
|
||||||
}
|
}
|
||||||
|
|
||||||
const exeDir = path.dirname(app.getPath('exe'))
|
// We are in production/packaged mode.
|
||||||
|
// Priority 1: Do not even try to use Program Files if we have alternatives.
|
||||||
// Check if we are in Program Files
|
const winProgramFiles = (process.env.ProgramFiles || 'C:\\Program Files').toLowerCase()
|
||||||
const isProgramFiles = exeDir.toLowerCase().includes('program files')
|
const winProgramFilesX86 = (process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)').toLowerCase()
|
||||||
|
const currentExeDir = exeDir.toLowerCase()
|
||||||
if (isProgramFiles) {
|
|
||||||
|
if (currentExeDir.startsWith(winProgramFiles) || currentExeDir.startsWith(winProgramFilesX86)) {
|
||||||
return app.getPath('userData')
|
return app.getPath('userData')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for write access
|
// Priority 2: Check for actual write access (Portable Mode check)
|
||||||
try {
|
try {
|
||||||
const testFile = path.join(exeDir, '.write_test')
|
const testFile = path.join(exeDir, `.write_test_${Date.now()}`)
|
||||||
fs.writeFileSync(testFile, 'test')
|
fs.writeFileSync(testFile, 'test')
|
||||||
fs.unlinkSync(testFile)
|
fs.unlinkSync(testFile)
|
||||||
return exeDir // Writable (Portable Mode)
|
return exeDir // Writable (Portable Mode)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return app.getPath('userData') // Not writable, use AppData
|
// Not writable, use AppData
|
||||||
|
return app.getPath('userData')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ const api = {
|
|||||||
electronAPI.ipcRenderer.on('update-progress', listener)
|
electronAPI.ipcRenderer.on('update-progress', listener)
|
||||||
return () => electronAPI.ipcRenderer.removeListener('update-progress', listener)
|
return () => electronAPI.ipcRenderer.removeListener('update-progress', listener)
|
||||||
},
|
},
|
||||||
|
getPermissionsStatus: () => electronAPI.ipcRenderer.invoke('app:get-permissions-status'),
|
||||||
invoke: (channel: string, ...args: any[]) => electronAPI.ipcRenderer.invoke(channel, ...args)
|
invoke: (channel: string, ...args: any[]) => electronAPI.ipcRenderer.invoke(channel, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -318,6 +318,23 @@ function App(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkPermissions = async () => {
|
||||||
|
if (window.api && window.api.getPermissionsStatus) {
|
||||||
|
const status = await window.api.getPermissionsStatus()
|
||||||
|
if (status.hasError) {
|
||||||
|
Swal.fire({
|
||||||
|
title: t('common.warning'),
|
||||||
|
text: t('common.admin_required'),
|
||||||
|
icon: 'warning',
|
||||||
|
confirmButtonText: t('common.ok'),
|
||||||
|
confirmButtonColor: '#3085d6',
|
||||||
|
background: '#1a1d21',
|
||||||
|
color: '#fff'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetchStatus()
|
fetchStatus()
|
||||||
fetchProjects()
|
fetchProjects()
|
||||||
fetchPhpVersions()
|
fetchPhpVersions()
|
||||||
@@ -327,6 +344,7 @@ function App(): JSX.Element {
|
|||||||
fetchSettings()
|
fetchSettings()
|
||||||
fetchCliConfig()
|
fetchCliConfig()
|
||||||
fetchPmaStatus()
|
fetchPmaStatus()
|
||||||
|
checkPermissions()
|
||||||
|
|
||||||
const removeUpdateStatusListener = window.api.onUpdateStatus ? window.api.onUpdateStatus((st: string, info: any) => {
|
const removeUpdateStatusListener = window.api.onUpdateStatus ? window.api.onUpdateStatus((st: string, info: any) => {
|
||||||
setUpdateStatus(st)
|
setUpdateStatus(st)
|
||||||
|
|||||||
@@ -45,7 +45,9 @@
|
|||||||
"browse": "Durchsuchen",
|
"browse": "Durchsuchen",
|
||||||
"reset_confirm_title": "Sind Sie sicher?",
|
"reset_confirm_title": "Sind Sie sicher?",
|
||||||
"reset_confirm_text": "Möchten Sie die {{service}}-Daten zurücksetzen? Dieser Vorgang löscht alle Daten und führt eine Neuinstallation durch.",
|
"reset_confirm_text": "Möchten Sie die {{service}}-Daten zurücksetzen? Dieser Vorgang löscht alle Daten und führt eine Neuinstallation durch.",
|
||||||
"reset_btn": "Ja, Zurücksetzen"
|
"reset_btn": "Ja, Zurücksetzen",
|
||||||
|
"status": "Status",
|
||||||
|
"admin_required": "Bitte versuchen Sie, die Anwendung als Administrator auszuführen."
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Trunçgil Multi-PHP Server",
|
"title": "Trunçgil Multi-PHP Server",
|
||||||
|
|||||||
@@ -51,7 +51,9 @@
|
|||||||
"reset_confirm_text": "Do you want to reset {{service}} data? This operation deletes all data and performs a clean installation.",
|
"reset_confirm_text": "Do you want to reset {{service}} data? This operation deletes all data and performs a clean installation.",
|
||||||
"reset_btn": "Yes, Reset",
|
"reset_btn": "Yes, Reset",
|
||||||
"operating": "Operation in progress...",
|
"operating": "Operation in progress...",
|
||||||
"please_wait": "Please wait..."
|
"please_wait": "Please wait...",
|
||||||
|
"status": "Status",
|
||||||
|
"admin_required": "Please try running the application as an administrator."
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
|
|||||||
@@ -52,7 +52,8 @@
|
|||||||
"reset_btn": "Да, сбросить",
|
"reset_btn": "Да, сбросить",
|
||||||
"operating": "Операция выполняется...",
|
"operating": "Операция выполняется...",
|
||||||
"please_wait": "Пожалуйста, подождите...",
|
"please_wait": "Пожалуйста, подождите...",
|
||||||
"status": "Статус"
|
"status": "Статус",
|
||||||
|
"admin_required": "Пожалуйста, попробуйте запустить приложение от имени администратора."
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Панель управления",
|
"title": "Панель управления",
|
||||||
|
|||||||
@@ -53,7 +53,8 @@
|
|||||||
"reset_btn": "Evet, Sıfırla",
|
"reset_btn": "Evet, Sıfırla",
|
||||||
"operating": "İşlem devam ediyor...",
|
"operating": "İşlem devam ediyor...",
|
||||||
"please_wait": "Lütfen bekleyin...",
|
"please_wait": "Lütfen bekleyin...",
|
||||||
"status": "Durum"
|
"status": "Durum",
|
||||||
|
"admin_required": "Lütfen uygulamayı yönetici olarak çalıştırmayı deneyin."
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Özet",
|
"title": "Özet",
|
||||||
|
|||||||
Reference in New Issue
Block a user