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,
|
||||
"win": {
|
||||
"icon": "build/icon.ico",
|
||||
"requestedExecutionLevel": "highestAvailable",
|
||||
"target": [
|
||||
"nsis"
|
||||
]
|
||||
@@ -101,7 +102,7 @@
|
||||
"installerIcon": "build/icon.ico",
|
||||
"uninstallerIcon": "build/icon.ico",
|
||||
"installerHeaderIcon": "build/icon.ico",
|
||||
"perMachine": false,
|
||||
"perMachine": true,
|
||||
"multiLanguageInstaller": true,
|
||||
"installerLanguages": [
|
||||
"tr_TR",
|
||||
|
||||
@@ -1515,4 +1515,11 @@ export function registerIpcHandlers(): void {
|
||||
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>
|
||||
}
|
||||
|
||||
public hasPermissionError = false
|
||||
public failedPath = ''
|
||||
|
||||
constructor() {
|
||||
this.templateDir = path.join(app.getAppPath(), 'config')
|
||||
const base = this.getDataPath()
|
||||
this.configDir = path.join(base, 'generated_configs')
|
||||
this.settingsPath = path.join(base, 'settings.json')
|
||||
|
||||
console.log(`[ConfigService] Base data path: ${base}`)
|
||||
console.log(`[ConfigService] Config dir: ${this.configDir}`)
|
||||
|
||||
// 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)) {
|
||||
try {
|
||||
fs.mkdirSync(this.configDir, { recursive: true })
|
||||
} catch (e) {
|
||||
console.error('Failed to create config directory:', e)
|
||||
} catch (e: any) {
|
||||
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)) {
|
||||
try {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -51,27 +77,33 @@ export class ConfigService {
|
||||
* If not writable (e.g. Program Files), falls back to app.getPath('userData').
|
||||
*/
|
||||
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()
|
||||
}
|
||||
|
||||
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.
|
||||
const winProgramFiles = (process.env.ProgramFiles || 'C:\\Program Files').toLowerCase()
|
||||
const winProgramFilesX86 = (process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)').toLowerCase()
|
||||
const currentExeDir = exeDir.toLowerCase()
|
||||
|
||||
// Check if we are in Program Files
|
||||
const isProgramFiles = exeDir.toLowerCase().includes('program files')
|
||||
|
||||
if (isProgramFiles) {
|
||||
if (currentExeDir.startsWith(winProgramFiles) || currentExeDir.startsWith(winProgramFilesX86)) {
|
||||
return app.getPath('userData')
|
||||
}
|
||||
|
||||
// Check for write access
|
||||
// Priority 2: Check for actual write access (Portable Mode check)
|
||||
try {
|
||||
const testFile = path.join(exeDir, '.write_test')
|
||||
const testFile = path.join(exeDir, `.write_test_${Date.now()}`)
|
||||
fs.writeFileSync(testFile, 'test')
|
||||
fs.unlinkSync(testFile)
|
||||
return exeDir // Writable (Portable Mode)
|
||||
} 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)
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
fetchProjects()
|
||||
fetchPhpVersions()
|
||||
@@ -327,6 +344,7 @@ function App(): JSX.Element {
|
||||
fetchSettings()
|
||||
fetchCliConfig()
|
||||
fetchPmaStatus()
|
||||
checkPermissions()
|
||||
|
||||
const removeUpdateStatusListener = window.api.onUpdateStatus ? window.api.onUpdateStatus((st: string, info: any) => {
|
||||
setUpdateStatus(st)
|
||||
|
||||
@@ -45,7 +45,9 @@
|
||||
"browse": "Durchsuchen",
|
||||
"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_btn": "Ja, Zurücksetzen"
|
||||
"reset_btn": "Ja, Zurücksetzen",
|
||||
"status": "Status",
|
||||
"admin_required": "Bitte versuchen Sie, die Anwendung als Administrator auszuführen."
|
||||
},
|
||||
"dashboard": {
|
||||
"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_btn": "Yes, Reset",
|
||||
"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": {
|
||||
"title": "Dashboard",
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
"reset_btn": "Да, сбросить",
|
||||
"operating": "Операция выполняется...",
|
||||
"please_wait": "Пожалуйста, подождите...",
|
||||
"status": "Статус"
|
||||
"status": "Статус",
|
||||
"admin_required": "Пожалуйста, попробуйте запустить приложение от имени администратора."
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Панель управления",
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
"reset_btn": "Evet, Sıfırla",
|
||||
"operating": "İşlem devam ediyor...",
|
||||
"please_wait": "Lütfen bekleyin...",
|
||||
"status": "Durum"
|
||||
"status": "Durum",
|
||||
"admin_required": "Lütfen uygulamayı yönetici olarak çalıştırmayı deneyin."
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Özet",
|
||||
|
||||
Reference in New Issue
Block a user