Address PR review feedback by moving container logs to a full-page viewer using xterm (matching /manage/tools/logs), with per-row actions for better UX when browsing long container lists. Harden log streaming with multiplex decode fallbacks, stream cleanup on parse errors, and decode unit tests.
90 lines
2.1 KiB
TypeScript
90 lines
2.1 KiB
TypeScript
const DOCKER_STREAM_HEADER_SIZE = 8;
|
|
const DOCKER_STREAM_TYPES = new Set([1, 2]);
|
|
|
|
export const decodeDockerLogs = (logs: Buffer | string) => {
|
|
if (typeof logs === "string") {
|
|
return logs;
|
|
}
|
|
|
|
if (logs.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
let cursor = 0;
|
|
const parts: string[] = [];
|
|
|
|
while (cursor < logs.length) {
|
|
if (cursor + DOCKER_STREAM_HEADER_SIZE > logs.length) {
|
|
break;
|
|
}
|
|
|
|
const streamType = logs.readUInt8(cursor);
|
|
if (!DOCKER_STREAM_TYPES.has(streamType)) {
|
|
return logs.toString("utf-8");
|
|
}
|
|
|
|
const length = logs.readUInt32BE(cursor + 4);
|
|
const start = cursor + DOCKER_STREAM_HEADER_SIZE;
|
|
const end = start + length;
|
|
if (end > logs.length) {
|
|
break;
|
|
}
|
|
|
|
parts.push(logs.subarray(start, end).toString("utf-8"));
|
|
cursor = end;
|
|
}
|
|
|
|
const decoded = parts.join("");
|
|
if (decoded.length === 0) {
|
|
return logs.toString("utf-8");
|
|
}
|
|
|
|
return decoded;
|
|
};
|
|
|
|
export const createDockerLogStreamProcessor = (
|
|
onData: (data: string) => void,
|
|
onError: (err: Error) => void,
|
|
maxMessageSize: number,
|
|
) => {
|
|
let buffer = Buffer.alloc(0);
|
|
|
|
const processChunk = (chunk: Buffer) => {
|
|
buffer = Buffer.concat([buffer, chunk]);
|
|
|
|
while (buffer.length > 0) {
|
|
if (buffer.length < DOCKER_STREAM_HEADER_SIZE) {
|
|
break;
|
|
}
|
|
|
|
const streamType = buffer.readUInt8(0);
|
|
if (!DOCKER_STREAM_TYPES.has(streamType)) {
|
|
onData(buffer.toString("utf-8"));
|
|
buffer = Buffer.alloc(0);
|
|
break;
|
|
}
|
|
|
|
const length = buffer.readUInt32BE(4);
|
|
if (length > maxMessageSize) {
|
|
onError(
|
|
new Error(`Docker log message size (${length} bytes) exceeds maximum allowed (${maxMessageSize} bytes)`),
|
|
);
|
|
return false;
|
|
}
|
|
|
|
const fullMessageSize = DOCKER_STREAM_HEADER_SIZE + length;
|
|
if (buffer.length < fullMessageSize) {
|
|
break;
|
|
}
|
|
|
|
const payload = buffer.subarray(DOCKER_STREAM_HEADER_SIZE, fullMessageSize);
|
|
onData(payload.toString("utf-8"));
|
|
buffer = buffer.subarray(fullMessageSize);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
return processChunk;
|
|
};
|