refactor: update ComposeService and DockerController for improved path handling and environment variable management; enhance EditorLayout for dynamic button rendering based on stack state

This commit is contained in:
SaelixCode
2026-02-20 22:20:34 -05:00
parent 888283d515
commit ad8c663629
4 changed files with 93 additions and 36 deletions
+1
View File
@@ -7,6 +7,7 @@ import DockerController from './services/DockerController';
import { FileSystemService } from './services/FileSystemService';
import { ComposeService } from './services/ComposeService';
import { ConfigService } from './services/ConfigService';
// @ts-ignore - composerize lacks proper type definitions
import composerize from 'composerize';
import si from 'systeminformation';
import http from 'http';
+13 -4
View File
@@ -6,7 +6,7 @@ export class ComposeService {
private baseDir: string;
constructor() {
this.baseDir = process.env.COMPOSE_DIR || path.join(process.cwd(), '..', 'mock_data', 'docker', 'compose');
this.baseDir = process.env.COMPOSE_DIR || path.join(process.cwd(), '..', 'docker', 'compose');
}
/**
@@ -25,7 +25,10 @@ export class ComposeService {
const child = spawn('docker', args, {
cwd: stackDir, // CRITICAL: Set working directory to stack folder
shell: true
env: {
...process.env,
PATH: process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
}
});
if (ws) {
@@ -65,7 +68,10 @@ export class ComposeService {
await new Promise<void>((resolve, reject) => {
const pullProcess = spawn('docker', ['compose', 'pull'], {
cwd: stackDir,
shell: true
env: {
...process.env,
PATH: process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
}
});
pullProcess.stdout.on('data', (data: Buffer) => {
@@ -97,7 +103,10 @@ export class ComposeService {
await new Promise<void>((resolve, reject) => {
const upProcess = spawn('docker', ['compose', 'up', '-d'], {
cwd: stackDir,
shell: true
env: {
...process.env,
PATH: process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
}
});
upProcess.stdout.on('data', (data: Buffer) => {
+49 -12
View File
@@ -1,6 +1,12 @@
import Docker from 'dockerode';
import WebSocket from 'ws';
import { Duplex } from 'stream';
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
const execAsync = promisify(exec);
const COMPOSE_DIR = process.env.COMPOSE_DIR || '/app/compose';
class DockerController {
private static instance: DockerController;
@@ -30,19 +36,50 @@ class DockerController {
}
public async getContainersByStack(stackName: string) {
const containers = await this.docker.listContainers({ all: true });
// Normalize the stack name: remove all non-alphanumeric characters and lowercase
// Docker Compose strips hyphens and underscores from project names
const normalizedStackName = stackName.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
return containers.filter(container => {
if (!container.Labels || !container.Labels['com.docker.compose.project']) {
return false;
try {
const stackDir = path.join(COMPOSE_DIR, stackName);
const { stdout } = await execAsync('docker compose ps --format json -a', {
cwd: stackDir,
env: {
...process.env,
PATH: process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
}
});
// Robust JSON parsing - handle both JSON array and newline-separated JSON objects
// Docker Compose v2 may return either format depending on version
interface ComposeContainer {
ID?: string;
Name?: string;
State?: string;
Status?: string;
}
// Normalize the Docker label for comparison
const projectLabel = container.Labels['com.docker.compose.project'].replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
return projectLabel === normalizedStackName;
});
let containers: ComposeContainer[];
try {
// Try parsing as a standard JSON array
const parsed = JSON.parse(stdout);
containers = Array.isArray(parsed) ? parsed : [parsed];
} catch {
// Fallback: parse newline-separated JSON objects
const lines = stdout.trim().split('\n');
containers = lines.map(line => JSON.parse(line) as ComposeContainer);
}
// Map to frontend's expected interface
// Note: docker compose ps returns Name (singular), but frontend expects Names (array)
// Dockerode returns Names with leading slash, so we add it for compatibility
return containers.map((c) => ({
Id: c.ID || '',
Names: ['/' + (c.Name || '')], // Add leading slash to match Dockerode format
State: c.State || 'unknown',
Status: c.Status || ''
}));
} catch (error) {
// If command fails (e.g., stack not deployed), return empty array
console.error('Failed to get containers for stack:', stackName, error);
return [];
}
}
public async startContainer(containerId: string) {