feat: implement real-time container log streaming via SSE

This commit is contained in:
SaelixCode
2026-03-06 15:41:06 -05:00
parent 12aab3a5ae
commit b765403dcf
5 changed files with 183 additions and 1 deletions
+11
View File
@@ -598,6 +598,17 @@ app.get('/api/stacks/:stackName/containers', async (req: Request, res: Response)
}
});
app.get('/api/containers/:id/logs', async (req: Request, res: Response) => {
try {
const id = req.params.id as string;
const dockerController = DockerController.getInstance();
// Pass both req and res so we can listen for the client disconnect
await dockerController.streamContainerLogs(id, req, res);
} catch (error) {
res.status(500).json({ error: 'Failed to initialize log stream' });
}
});
app.post('/api/containers/:id/start', async (req: Request, res: Response) => {
try {
const id = req.params.id as string;
+46
View File
@@ -286,6 +286,52 @@ class DockerController {
}
}
public async streamContainerLogs(containerId: string, req: any, res: any): Promise<void> {
const container = this.docker.getContainer(containerId);
// 1. Set SSE Headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
try {
const logStream = await container.logs({
follow: true,
stdout: true,
stderr: true,
tail: 100 // Send the last 100 lines immediately for context
});
// 2. Process and forward the stream
logStream.on('data', (chunk: Buffer) => {
// Docker multiplexes stdout/stderr with an 8-byte header if TTY is false.
let data = chunk;
if (chunk.length > 8 && (chunk[0] === 1 || chunk[0] === 2)) {
data = chunk.slice(8);
}
const text = data.toString('utf-8');
const lines = text.split('\n');
lines.forEach(line => {
if (line.trim()) {
res.write(`data: ${JSON.stringify(line)}\n\n`);
}
});
});
// 3. Cleanup on disconnect
req.on('close', () => {
(logStream as any).destroy();
});
} catch (error: any) {
res.write(`data: ${JSON.stringify('[Sencho] Error fetching logs: ' + error.message)}\n\n`);
res.end();
}
}
// State-safe: silently ignores 304 "already started" errors
public async startContainer(containerId: string) {
try {