feat: implement process management service with IPC and configuration templates
This commit is contained in:
@@ -9,14 +9,14 @@ http {
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
server_names_hash_bucket_size 64;
|
||||
|
||||
{{PROJECTS}}
|
||||
|
||||
server {
|
||||
listen {{LISTEN_ADDRESS}}:{{PORT}};
|
||||
server_name localhost;
|
||||
root "{{ROOT}}";
|
||||
index index.php index.html index.htm;
|
||||
autoindex on;
|
||||
|
||||
location / {
|
||||
# Try global root first, fallback to @project_fallback if not found
|
||||
@@ -38,10 +38,8 @@ http {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Subdirectory access for projects (legacy support)
|
||||
{{PROJECT_LOCATIONS}}
|
||||
|
||||
|
||||
location /phpmyadmin {
|
||||
alias "{{PHPMYADMIN_DIR}}";
|
||||
index index.php;
|
||||
@@ -60,8 +58,7 @@ http {
|
||||
fastcgi_pass 127.0.0.1:{{PHP_PORT}};
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
include "{{CONF_DIR}}/fastcgi_params";
|
||||
}
|
||||
}
|
||||
|
||||
{{PROJECTS}}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ sys_temp_dir = "{{TEMP_DIR}}"
|
||||
extension_dir = "{{EXT_DIR}}"
|
||||
fastcgi.impersonate = 1
|
||||
cgi.force_redirect = 0
|
||||
cgi.fix_pathinfo = 1
|
||||
cgi.fix_pathinfo = 0
|
||||
|
||||
[ExtensionList]
|
||||
{{EXTENSIONS}}
|
||||
|
||||
+1263
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"nginxPort": "8081",
|
||||
"apachePort": "8080",
|
||||
"apachePort": "8082",
|
||||
"serverType": "nginx",
|
||||
"phpPort": "9001",
|
||||
"mariaDbPort": "3306",
|
||||
|
||||
+66
-34
@@ -76,7 +76,7 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin
|
||||
|
||||
// Ensure directories exist
|
||||
const projectsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects')
|
||||
const locationsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects_locations')
|
||||
const locationsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects_nginx')
|
||||
if (!fs.existsSync(projectsConfDir)) fs.mkdirSync(projectsConfDir, { recursive: true })
|
||||
if (!fs.existsSync(locationsConfDir)) fs.mkdirSync(locationsConfDir, { recursive: true })
|
||||
|
||||
@@ -128,8 +128,9 @@ async function rebuildNginxConfig(forceProjects: boolean = false): Promise<{ bin
|
||||
PORT: settings.nginxPort
|
||||
})
|
||||
|
||||
// Also generate the subdirectory/alias-style config for localhost fallback
|
||||
await configService.generateConfig('nginx_project_location.conf.template', `projects_locations/${p.host}.conf`, {
|
||||
// Use the correct location-based template for subdirectories
|
||||
// This prevents the 'server directive not allowed here' error
|
||||
await configService.generateConfig('nginx_project_location.conf.template', `projects_nginx/${p.host}.conf`, {
|
||||
HOST: p.host, // Use simple host slug for location path
|
||||
PATH: normalizedPath,
|
||||
PHP_PORT: phpPort.toString(),
|
||||
@@ -459,6 +460,11 @@ export function registerIpcHandlers(): void {
|
||||
return { success: false, message: `Nginx başlatılamadı.\n\n${errorLogs.join('\n')}` }
|
||||
}
|
||||
const ready = await waitForPort(settings.nginxPort, 5000);
|
||||
if (ready) {
|
||||
processManager.setServiceState('nginx', 'running')
|
||||
} else {
|
||||
processManager.setServiceState('nginx', 'stopped')
|
||||
}
|
||||
return { success: ready, message: ready ? 'Nginx başlatıldı.' : 'Nginx başlatıldı ancak port cevabı alınamadı.' }
|
||||
}
|
||||
|
||||
@@ -473,6 +479,11 @@ export function registerIpcHandlers(): void {
|
||||
return { success: false, message: `Apache başlatılamadı.\n\n${errorLogs.join('\n')}` }
|
||||
}
|
||||
const ready = await waitForPort(parseInt(settings.apachePort || '8080'), 5000);
|
||||
if (ready) {
|
||||
processManager.setServiceState('apache', 'running')
|
||||
} else {
|
||||
processManager.setServiceState('apache', 'stopped')
|
||||
}
|
||||
return { success: ready, message: ready ? 'Apache başlatıldı.' : 'Apache başlatıldı ancak port cevabı alınamadı.' }
|
||||
}
|
||||
|
||||
@@ -536,7 +547,7 @@ export function registerIpcHandlers(): void {
|
||||
extensionsList = mustHaves.map(m => `extension=${prefix}${m}${suffix}`).join('\n')
|
||||
}
|
||||
|
||||
const configPath = await configService.generateConfig('php.ini.template', `php_${phpVersion || 'default'}.ini`, {
|
||||
const configPath = await configService.generateConfig('php.ini.template', `php/${phpVersion || 'default'}/php.ini`, {
|
||||
EXT_DIR: extDir,
|
||||
GD_EXT: gdExtName,
|
||||
SESSION_DIR: sessionDir,
|
||||
@@ -548,6 +559,7 @@ export function registerIpcHandlers(): void {
|
||||
|
||||
// Add a small delay to ensure previous instance (if any) is fully killed
|
||||
await sleep(1000)
|
||||
// Set PHPRC to the directory containing the project-specific php.ini
|
||||
const success = await processManager.startService(serviceName, binPath, ['-b', `127.0.0.1:${phpPort}`, '-c', configPath], false, { PHPRC: path.dirname(configPath) })
|
||||
|
||||
if (!success) {
|
||||
@@ -556,6 +568,11 @@ export function registerIpcHandlers(): void {
|
||||
}
|
||||
|
||||
const ready = await waitForPort(phpPort, 5000);
|
||||
if (ready) {
|
||||
processManager.setServiceState(serviceName, 'running')
|
||||
} else {
|
||||
processManager.setServiceState(serviceName, 'stopped')
|
||||
}
|
||||
return { success: ready, message: ready ? `${phpVersion || ''} PHP başlatıldı.` : `${phpVersion || ''} PHP başlatıldı ancak port cevabı alınamadı.` }
|
||||
}
|
||||
|
||||
@@ -635,17 +652,19 @@ export function registerIpcHandlers(): void {
|
||||
|
||||
if (success) {
|
||||
// Quick health check
|
||||
await new Promise(r => setTimeout(r, 1500))
|
||||
if (!processManager.isServiceRunning(serviceName)) {
|
||||
const ready = await waitForPort(parseInt(dbPort), 7000);
|
||||
if (!ready || !processManager.isServiceRunning(serviceName)) {
|
||||
const errorLogs = await processManager.getLastErrorLogs(serviceName)
|
||||
return { success: false, message: `${dbVersion || ''} MariaDB başlatıldı ancak çalışma hatası verdi (ExitCode: 1).\n\n${errorLogs.length > 0 ? 'Önemli Hata Logları:\n' + errorLogs.join('\n') : 'Lütfen log sekmesini kontrol edin.'}` }
|
||||
processManager.setServiceState(serviceName, 'stopped')
|
||||
return { success: false, message: `${dbVersion || ''} MariaDB başlatıldı ancak çalışma hatası verdi.\n\n${errorLogs.length > 0 ? 'Önemli Hata Logları:\n' + errorLogs.join('\n') : 'Lütfen log sekmesini kontrol edin.'}` }
|
||||
}
|
||||
processManager.setServiceState(serviceName, 'running')
|
||||
return { success: true, message: `${dbVersion || ''} MariaDB başlatıldı.` }
|
||||
} else {
|
||||
const errorLogs = await processManager.getLastErrorLogs(serviceName)
|
||||
processManager.setServiceState(serviceName, 'stopped')
|
||||
return { success: false, message: `${dbVersion || ''} MariaDB başlatılamadı.\n\n${errorLogs.join('\n')}` }
|
||||
}
|
||||
|
||||
return { success, message: success ? `${dbVersion || ''} MariaDB başlatıldı.` : `${dbVersion || ''} MariaDB devreye alınamadı.` }
|
||||
}
|
||||
|
||||
return { success: false, message: 'Bilinmeyen servis.' }
|
||||
@@ -656,8 +675,12 @@ export function registerIpcHandlers(): void {
|
||||
})
|
||||
|
||||
ipcMain.handle('services:stop', async (_event, serviceName: string) => {
|
||||
try {
|
||||
const success = await processManager.stopService(serviceName)
|
||||
return { success, message: success ? `${serviceName} durduruldu.` : `${serviceName} zaten durmuş.` }
|
||||
return { success, message: success ? `${serviceName} durduruldu.` : `${serviceName} durdurulurken hata oluştu veya zaten durmuş.` }
|
||||
} catch (error: any) {
|
||||
return { success: false, message: `Durdurma hatası: ${error.message}` }
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('services:status', async () => {
|
||||
@@ -769,15 +792,23 @@ export function registerIpcHandlers(): void {
|
||||
})
|
||||
|
||||
ipcMain.handle('php:config:get', async (_event, version: string) => {
|
||||
const phpIniPath = path.join(configService.getBasePath(), 'generated_configs', `php_${version}.ini`)
|
||||
const phpIniPath = path.join(configService.getBasePath(), 'generated_configs', 'php', version, 'php.ini')
|
||||
if (fs.existsSync(phpIniPath)) {
|
||||
return { content: fs.readFileSync(phpIniPath, 'utf8') }
|
||||
}
|
||||
// Fallback to old path for migration support
|
||||
const oldPath = path.join(configService.getBasePath(), 'generated_configs', `php_${version}.ini`)
|
||||
if (fs.existsSync(oldPath)) {
|
||||
return { content: fs.readFileSync(oldPath, 'utf8') }
|
||||
}
|
||||
return { content: '' }
|
||||
})
|
||||
|
||||
ipcMain.handle('php:config:save', async (_event, { version, content }) => {
|
||||
const phpIniPath = path.join(configService.getBasePath(), 'generated_configs', `php_${version}.ini`)
|
||||
const phpIniDir = path.join(configService.getBasePath(), 'generated_configs', 'php', version)
|
||||
if (!fs.existsSync(phpIniDir)) fs.mkdirSync(phpIniDir, { recursive: true })
|
||||
const phpIniPath = path.join(phpIniDir, 'php.ini')
|
||||
|
||||
// Add manual edit marker to prevent overwriting
|
||||
let finalContent = content
|
||||
if (!finalContent.includes('; MANUAL_EDIT')) {
|
||||
@@ -1017,39 +1048,40 @@ export function registerIpcHandlers(): void {
|
||||
|
||||
ipcMain.handle('project:get-config', async (_event, { host, serverType }) => {
|
||||
const projectsDir = serverType === 'apache' ? 'projects_apache' : 'projects'
|
||||
const baseDir = path.join(configService.getBasePath(), 'generated_configs', projectsDir)
|
||||
|
||||
// Try both host.conf and host.local.conf
|
||||
const paths = [
|
||||
path.join(baseDir, `${host}.conf`),
|
||||
path.join(baseDir, `${host}.local.conf`)
|
||||
]
|
||||
// Prefer projects_nginx for Nginx as requested by user, fallback to projects
|
||||
const dirs = serverType === 'nginx' ? ['projects_nginx', 'projects'] : [projectsDir]
|
||||
const baseDir = configService.getBasePath()
|
||||
|
||||
for (const p of paths) {
|
||||
if (fs.existsSync(p)) {
|
||||
return { content: fs.readFileSync(p, 'utf8'), path: p }
|
||||
for (const dir of dirs) {
|
||||
const pathToCheck = path.join(baseDir, 'generated_configs', dir, `${host}.conf`)
|
||||
if (fs.existsSync(pathToCheck)) {
|
||||
return { content: fs.readFileSync(pathToCheck, 'utf8'), path: pathToCheck }
|
||||
}
|
||||
|
||||
// Try .local.conf as well
|
||||
const localPath = path.join(baseDir, 'generated_configs', dir, `${host}.local.conf`)
|
||||
if (fs.existsSync(localPath)) {
|
||||
return { content: fs.readFileSync(localPath, 'utf8'), path: localPath }
|
||||
}
|
||||
}
|
||||
|
||||
return { content: '', message: `Konfigürasyon dosyası bulunamadı. Aranan yollar: ${paths.join(', ')}` }
|
||||
return { content: '', message: `Konfigürasyon dosyası bulunamadı. Aranan klasörler: ${dirs.join(', ')}` }
|
||||
})
|
||||
|
||||
ipcMain.handle('project:save-config', async (_event, { host, serverType, content }) => {
|
||||
const projectsDir = serverType === 'apache' ? 'projects_apache' : 'projects'
|
||||
const baseDir = path.join(configService.getBasePath(), 'generated_configs', projectsDir)
|
||||
const dirs = serverType === 'nginx' ? ['projects_nginx', 'projects'] : [projectsDir]
|
||||
const baseDir = configService.getBasePath()
|
||||
|
||||
// Try to find existing file first (to overwrite the right one)
|
||||
const paths = [
|
||||
path.join(baseDir, `${host}.conf`),
|
||||
path.join(baseDir, `${host}.local.conf`)
|
||||
]
|
||||
// Find existing file to overwrite, or default to the first folder
|
||||
let configPath = path.join(baseDir, 'generated_configs', dirs[0], `${host}.conf`)
|
||||
|
||||
let configPath = paths[0]
|
||||
for (const p of paths) {
|
||||
if (fs.existsSync(p)) {
|
||||
configPath = p
|
||||
break
|
||||
}
|
||||
for (const dir of dirs) {
|
||||
const p1 = path.join(baseDir, 'generated_configs', dir, `${host}.conf`)
|
||||
const p2 = path.join(baseDir, 'generated_configs', dir, `${host}.local.conf`)
|
||||
if (fs.existsSync(p1)) { configPath = p1; break; }
|
||||
if (fs.existsSync(p2)) { configPath = p2; break; }
|
||||
}
|
||||
|
||||
let finalContent = content
|
||||
|
||||
@@ -58,6 +58,11 @@ export class ConfigService {
|
||||
content = content.replace(new RegExp(`{{${key}}}`, 'g'), () => escapedValue)
|
||||
}
|
||||
|
||||
const targetDir = path.dirname(targetPath)
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true })
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetPath, content)
|
||||
return targetPath
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import { t } from '../utils/i18n'
|
||||
|
||||
export class ProcessManager {
|
||||
private processes: Map<string, ChildProcess> = new Map()
|
||||
private states: Map<string, 'starting' | 'running' | 'stopping' | 'stopped'> = new Map()
|
||||
private logs: Map<string, string[]> = new Map()
|
||||
private logDir: string
|
||||
|
||||
constructor() {
|
||||
this.processes = new Map()
|
||||
this.states = new Map()
|
||||
this.logDir = ''
|
||||
}
|
||||
|
||||
@@ -67,6 +69,15 @@ export class ProcessManager {
|
||||
return this.logs.get(name) || []
|
||||
}
|
||||
|
||||
setServiceState(name: string, state: 'starting' | 'running' | 'stopping' | 'stopped') {
|
||||
console.log(`[STATE] ${name} -> ${state}`)
|
||||
this.states.set(name, state)
|
||||
}
|
||||
|
||||
getServiceState(name: string): string {
|
||||
return this.states.get(name) || (this.processes.has(name) ? 'running' : 'stopped')
|
||||
}
|
||||
|
||||
async runOnce(binPath: string, args: string[], name?: string): Promise<{ success: boolean; output: string }> {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(binPath, args, { stdio: 'pipe', shell: false, windowsHide: true })
|
||||
@@ -102,15 +113,18 @@ export class ProcessManager {
|
||||
|
||||
async startService(name: string, binPath: string, args: string[], shell: boolean = true, envVars: Record<string, string> = {}): Promise<boolean> {
|
||||
if (this.processes.has(name)) {
|
||||
this.states.set(name, 'running')
|
||||
return true
|
||||
}
|
||||
|
||||
if (!fs.existsSync(binPath)) {
|
||||
this.addLog(name, `${t('common.error')}: ${t('common.executable_not_found')} (${binPath})`)
|
||||
this.states.set(name, 'stopped')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
this.states.set(name, 'starting')
|
||||
this.addLog(name, `${t('common.starting')}... (${binPath})`)
|
||||
|
||||
const serviceEnv = { ...process.env, ...envVars }
|
||||
@@ -129,6 +143,11 @@ export class ProcessManager {
|
||||
env: serviceEnv
|
||||
})
|
||||
|
||||
if (!child.pid) {
|
||||
this.states.set(name, 'stopped')
|
||||
return false
|
||||
}
|
||||
|
||||
console.log(`[PROCESS] Started ${name} with PID ${child.pid}`)
|
||||
this.addLog(name, `${t('common.running')} (PID: ${child.pid})`)
|
||||
serviceEventLogger.log({ service: name, action: 'start', status: 'success' })
|
||||
@@ -140,6 +159,7 @@ export class ProcessManager {
|
||||
this.addLog(name, `Failed to start: ${err.message}`)
|
||||
serviceEventLogger.log({ service: name, action: 'start', status: 'failure', details: err.message })
|
||||
this.processes.delete(name)
|
||||
this.states.set(name, 'stopped')
|
||||
})
|
||||
|
||||
child.on('exit', (code) => {
|
||||
@@ -150,38 +170,72 @@ export class ProcessManager {
|
||||
this.checkErrorLogs(name)
|
||||
}
|
||||
this.processes.delete(name)
|
||||
this.states.set(name, 'stopped')
|
||||
})
|
||||
|
||||
this.processes.set(name, child)
|
||||
|
||||
// State remains 'starting' until the IPC handler confirms port listener
|
||||
return true
|
||||
} catch (error: any) {
|
||||
this.addLog(name, `Spawn Error: ${error.message}`)
|
||||
serviceEventLogger.log({ service: name, action: 'start', status: 'failure', details: error.message })
|
||||
this.states.set(name, 'stopped')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async stopService(name: string): Promise<boolean> {
|
||||
this.states.set(name, 'stopping')
|
||||
const child = this.processes.get(name)
|
||||
|
||||
if (child) {
|
||||
this.addLog(name, `${t('common.stopping')}...`)
|
||||
serviceEventLogger.log({ service: name, action: 'stop', status: 'info' })
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let resolved = false
|
||||
const timer = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
this.addLog(name, 'Stop timeout, forcing kill...')
|
||||
this.forceKillAll(name).then(() => {
|
||||
this.states.set(name, 'stopped')
|
||||
resolve(true)
|
||||
})
|
||||
resolved = true
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
child.on('exit', () => {
|
||||
if (!resolved) {
|
||||
clearTimeout(timer)
|
||||
this.processes.delete(name)
|
||||
this.states.set(name, 'stopped')
|
||||
resolve(true)
|
||||
resolved = true
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
child.kill()
|
||||
} else {
|
||||
// On Unix, try SIGTERM first, then SIGKILL if needed
|
||||
child.kill('SIGTERM')
|
||||
}
|
||||
} catch (e) {
|
||||
this.addLog(name, `Stop error: ${e instanceof Error ? e.message : String(e)}`)
|
||||
this.addLog(name, `Stop signal error: ${e instanceof Error ? e.message : String(e)}`)
|
||||
this.forceKillAll(name).then(() => {
|
||||
this.states.set(name, 'stopped')
|
||||
resolve(true)
|
||||
})
|
||||
resolved = true
|
||||
}
|
||||
this.processes.delete(name)
|
||||
})
|
||||
}
|
||||
|
||||
// Also force kill lingering processes
|
||||
return this.forceKillAll(name)
|
||||
// Also force kill lingering processes even if not in map
|
||||
const result = await this.forceKillAll(name)
|
||||
this.states.set(name, 'stopped')
|
||||
return result
|
||||
}
|
||||
|
||||
async forceKillAll(name: string): Promise<boolean> {
|
||||
@@ -210,13 +264,11 @@ export class ProcessManager {
|
||||
.on('close', () => resolve(true))
|
||||
.on('error', () => resolve(true))
|
||||
} else {
|
||||
// Unix: pkill -f matches full command line (data directory path usually contains the dbId)
|
||||
spawn('pkill', ['-f', dbId], { windowsHide: true })
|
||||
.on('close', () => resolve(true))
|
||||
.on('error', () => resolve(true))
|
||||
}
|
||||
} else {
|
||||
const isWindows = process.platform === 'win32';
|
||||
if (isWindows) {
|
||||
const executableName = name === 'nginx' ? 'nginx.exe' : name === 'apache' ? 'httpd.exe' : name === 'php' ? 'php-cgi.exe' : 'mariadbd.exe'
|
||||
spawn('taskkill', ['/F', '/IM', executableName, '/T'], { windowsHide: true })
|
||||
@@ -230,7 +282,6 @@ export class ProcessManager {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// macOS / Linux
|
||||
if (childProcess && childProcess.pid) {
|
||||
try { process.kill(childProcess.pid, 'SIGKILL') } catch (e) { }
|
||||
this.processes.delete(name)
|
||||
@@ -286,36 +337,51 @@ export class ProcessManager {
|
||||
}
|
||||
|
||||
isServiceRunning(name: string): boolean {
|
||||
return this.processes.has(name)
|
||||
const child = this.processes.get(name)
|
||||
return !!(child && child.pid)
|
||||
}
|
||||
|
||||
async getStatuses(): Promise<Record<string, { status: string; cpu?: number; memory?: number }>> {
|
||||
const statuses: Record<string, { status: string; cpu?: number; memory?: number }> = {}
|
||||
|
||||
// Add all tracked states first
|
||||
for (const [name, state] of this.states.entries()) {
|
||||
statuses[name] = { status: state }
|
||||
}
|
||||
|
||||
const entries = Array.from(this.processes.entries())
|
||||
for (const [name, child] of entries) {
|
||||
const currentState = this.states.get(name)
|
||||
if (child.pid) {
|
||||
try {
|
||||
const stats = await pidusage(child.pid)
|
||||
statuses[name] = {
|
||||
status: 'running',
|
||||
cpu: Math.round(stats.cpu * 10) / 10, // Round to 1 decimal
|
||||
status: currentState === 'stopping' ? 'stopping' : 'running',
|
||||
cpu: Math.round(stats.cpu * 10) / 10,
|
||||
memory: stats.memory
|
||||
}
|
||||
if (currentState !== 'stopping' && currentState !== 'starting') {
|
||||
this.states.set(name, 'running')
|
||||
}
|
||||
} catch (e) {
|
||||
statuses[name] = { status: 'running' }
|
||||
// Clean up pidusage if error (usually process died)
|
||||
if (currentState !== 'starting' && currentState !== 'stopping') {
|
||||
statuses[name] = { status: 'stopped' }
|
||||
this.states.set(name, 'stopped')
|
||||
}
|
||||
pidusage.clear()
|
||||
}
|
||||
} else {
|
||||
statuses[name] = { status: 'running' }
|
||||
if (currentState !== 'starting' && currentState !== 'stopping') {
|
||||
statuses[name] = { status: 'stopped' }
|
||||
this.states.set(name, 'stopped')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure defaults are present if stopped
|
||||
// Ensure defaults are present
|
||||
const defaultServices = ['nginx', 'apache', 'mariadb']
|
||||
defaultServices.forEach(s => {
|
||||
if (!statuses[s]) statuses[s] = { status: 'stopped' }
|
||||
if (!statuses[s]) statuses[s] = { status: this.states.get(s) || 'stopped' }
|
||||
})
|
||||
|
||||
return statuses
|
||||
@@ -353,7 +419,6 @@ export class ProcessManager {
|
||||
this.initLogs()
|
||||
const foundLogs: string[] = []
|
||||
|
||||
// 1. Check in-memory logs for [CORE ERROR] or other error markers
|
||||
const memLogs = this.logs.get(name) || []
|
||||
const errorMarkers = ['ERROR', 'failed', 'Fatal', 'error']
|
||||
const recentErrors = memLogs.filter(line =>
|
||||
@@ -362,7 +427,6 @@ export class ProcessManager {
|
||||
|
||||
foundLogs.push(...recentErrors)
|
||||
|
||||
// 2. Check physical log files if memory logs are insufficient
|
||||
const sanitizedName = name.replace(/:/g, '_')
|
||||
const possibleFiles = [
|
||||
path.join(this.logDir, `${sanitizedName}.log`),
|
||||
@@ -381,7 +445,6 @@ export class ProcessManager {
|
||||
if (!foundLogs.includes(l)) foundLogs.push(l)
|
||||
})
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ export default function ProjectDetails({
|
||||
|
||||
<Box sx={{ p: 0, flexGrow: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
||||
{projectDetailTab === 0 && (
|
||||
<Box sx={{ p: 4 }}>
|
||||
<Box sx={{ p: 4, overflowY: 'auto', flexGrow: 1 }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
@@ -313,7 +313,7 @@ export default function ProjectDetails({
|
||||
)}
|
||||
|
||||
{projectDetailTab === 1 && (
|
||||
<Box sx={{ p: 4 }}>
|
||||
<Box sx={{ p: 4, overflowY: 'auto', flexGrow: 1 }}>
|
||||
<Box sx={{ maxWidth: 600 }}>
|
||||
<Box sx={{ p: 3, border: '1px solid rgba(255,255,255,0.05)', borderRadius: 2, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}>
|
||||
|
||||
Reference in New Issue
Block a user