fixes on terminal

This commit is contained in:
biersoeckli
2024-11-28 07:41:19 +00:00
parent 75dfaaa894
commit f6bdfa755d
5 changed files with 118 additions and 78 deletions
@@ -11,6 +11,7 @@ import { Terminal } from '@xterm/xterm'
import '@xterm/xterm/css/xterm.css'
import { podTerminalSocket } from "@/frontend/sockets/sockets";
import { StreamUtils } from "@/shared/utils/stream.utils";
import { Button } from "@/components/ui/button";
export default function TerminalStreamed({
terminalInfo,
@@ -18,18 +19,25 @@ export default function TerminalStreamed({
terminalInfo: TerminalSetupInfoModel;
}) {
const [isConnected, setIsConnected] = useState(false);
const [logs, setLogs] = useState<string>('');
const terminalWindow = useRef<HTMLDivElement>(null);
const [terminal, setTerminal] = useState<Terminal | undefined>(undefined);
const [sessionTerminalInfo, setSessionTerminalInfo] = useState<TerminalSetupInfoModel | undefined>(undefined);
useEffect(() => {
const startTerminalSession = (terminalType: 'sh' | 'bash') => {
if (!terminalInfo || !terminalWindow || !terminalWindow.current) {
return;
}
const terminalInputKey = StreamUtils.getInputStreamName(terminalInfo);
const terminalOutputKey = StreamUtils.getOutputStreamName(terminalInfo);
const terminalSessionKey = `${terminalInfo.namespace}-${terminalInfo.podName}-${terminalInfo.containerName}-${terminalType}-${new Date().getTime()}`;
const termInfo = {
...terminalInfo,
terminalSessionKey,
terminalType,
};
const terminalInputKey = StreamUtils.getInputStreamName(termInfo);
const terminalOutputKey = StreamUtils.getOutputStreamName(termInfo);
console.log(`InputKey ${terminalInputKey}`);
console.log(`OutputKey ${terminalOutputKey}`);
var term = new Terminal();
term.open(terminalWindow.current);
@@ -41,22 +49,30 @@ export default function TerminalStreamed({
console.log('Received data:', data);
term.write(data);
});
podTerminalSocket.emit('openTerminal', terminalInfo);
podTerminalSocket.emit('openTerminal', termInfo);
term.write('Terminal is ready');
setTerminal(term);
setSessionTerminalInfo(termInfo);
};
useEffect(() => {
return () => {
console.log('Disconnecting from terminal...');
term.dispose();
podTerminalSocket.emit('closeTerminal', terminalInfo);
//terminal?.dispose();
//if (sessionTerminalInfo) podTerminalSocket.emit('closeTerminal', sessionTerminalInfo);
};
}, [terminalInfo]);
});
return <>
<div className="space-y-4">
{!sessionTerminalInfo && <>
<div className="flex gap-4">
<Button variant="secondary" onClick={() => startTerminalSession('sh')}>Start sh</Button>
<Button variant="secondary" onClick={() => startTerminalSession('bash')}>Start bash</Button>
</div>
</>}
<div ref={terminalWindow} ></div>
</div>
-1
View File
@@ -3,5 +3,4 @@
import { Manager } from "socket.io-client";
const manager = new Manager();
export const podTerminalSocket = manager.socket("/pod-terminal");
+86 -63
View File
@@ -1,91 +1,114 @@
import { TerminalSetupInfoModel, terminalSetupInfoZodModel } from "../../shared/model/terminal-setup-info.model";
import { DefaultEventsMap, Socket } from "socket.io";
import setupPodService from "./setup-services/setup-pod.service";
import k3s from "../adapter/kubernetes-api.adapter";
import * as k8s from '@kubernetes/client-node';
import stream from 'stream';
import { StreamUtils } from "@/shared/utils/stream.utils";
import WebSocket from "ws";
import crypto from 'crypto';
interface TerminalStrean {
stdoutStream: stream.PassThrough;
stderrStream: stream.PassThrough;
stdinStream: stream.PassThrough;
streamInputKey: string;
streamOutputKey: string;
websocket: WebSocket.WebSocket;
terminalSessionKey: string;
websocket?: WebSocket.WebSocket;
}
export class TerminalService {
activeStreams = new Map<string, { logStream: stream.PassThrough, clients: number, k3sStreamRequest: any }>();
async streamLogs(socket: Socket<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, any>) {
console.log('Client connected:', socket.id);
console.log('[NEW] Client connected:', socket.id);
const streamsOfSocket: TerminalStrean[] = [];
socket.on('openTerminal', async (podInfo) => {
console.warn('openTerminal', podInfo);
try {
const terminalInfo = terminalSetupInfoZodModel.parse(podInfo);
if (!terminalInfo.terminalSessionKey) {
console.warn('terminalSessionKey not provided. Setting as undefined.');
}
console.log(terminalInfo)
const streamInputKey = StreamUtils.getInputStreamName(terminalInfo);
const streamOutputKey = StreamUtils.getOutputStreamName(terminalInfo);
const terminalInfo = terminalSetupInfoZodModel.parse(podInfo);
const streamInputKey = StreamUtils.getInputStreamName(terminalInfo);
const streamOutputKey = StreamUtils.getOutputStreamName(terminalInfo);
/*const podReachable = await setupPodService.waitUntilPodIsRunningFailedOrSucceded(terminalInfo.namespace, terminalInfo.podName);
if (!podReachable) {
socket.emit(streamOutputKey, 'Pod is not reachable.');
return;
}*/
const podReachable = await setupPodService.waitUntilPodIsRunningFailedOrSucceded(terminalInfo.namespace, terminalInfo.podName);
if (!podReachable) {
socket.emit(streamOutputKey);
return;
const exec = new k8s.Exec(k3s.getKubeConfig());
const stdoutStream = new stream.PassThrough();
const stderrStream = new stream.PassThrough();
const stdinStream = new stream.PassThrough();
console.log('starting exec')
exec.exec(
terminalInfo.namespace,
terminalInfo.podName,
terminalInfo.containerName,
[terminalInfo.terminalType === 'sh' ? '/bin/sh' : '/bin/bash'],
/* process.stdout,
process.stderr,
process.stdin,*/
stdoutStream,
stderrStream,
stdinStream,
false /* tty */,
(status: k8s.V1Status) => {
console.log('Exited with status:');
console.log(JSON.stringify(status, null, 2));
stderrStream!.end();
stdoutStream!.end();
stdinStream!.end();
},
);
stdoutStream.on('data', (chunk) => {
console.log(chunk)
socket.emit(streamOutputKey, chunk.toString());
});
stdoutStream.on('error', (error) => {
console.error("Error in terminal stream:", error);
});
stdoutStream.on('end', () => {
//console.log(`[END] Log stream ended for ${streamKey} by ${streamEndedByClient ? 'client' : 'server'}`);
});
stderrStream.on('data', (chunk) => {
console.log(chunk)
socket.emit(streamOutputKey, chunk.toString());
});
socket.on(streamInputKey, (data) => {
console.log('Received data:', data);
stdinStream!.write(data);
});
streamsOfSocket.push({
stdoutStream,
stderrStream,
stdinStream,
terminalSessionKey: terminalInfo.terminalSessionKey ?? '',
//websocket
});
console.log(`Client ${socket.id} joined terminal stream for:`);
console.log(`Input: ${streamInputKey}`);
console.log(`Output: ${streamOutputKey}`);
} catch (error) {
console.error('Error while initializing terminal session', podInfo, error);
}
const exec = new k8s.Exec(k3s.getKubeConfig());
const stdoutStream = new stream.PassThrough();
const stderrStream = new stream.PassThrough();
const stdinStream = new stream.PassThrough();
const websocket = await exec.exec(
terminalInfo.namespace,
terminalInfo.podName,
terminalInfo.containerName,
['/bin/sh'],
process.stdout,
process.stderr,
process.stdin,
/* stdoutStream,
stderrStream,
stdinStream,*/
true /* tty */,
(status: k8s.V1Status) => {
console.log('Exited with status:');
console.log(JSON.stringify(status, null, 2));
stderrStream!.end();
stdoutStream!.end();
stdinStream!.end();
},
);
stdoutStream.on('data', (chunk) => {
console.log(chunk)
socket.emit(streamOutputKey, chunk.toString());
});
stderrStream.on('data', (chunk) => {
console.log(chunk)
socket.emit(streamOutputKey, chunk.toString());
});
socket.on(streamInputKey, (data) => {
stdinStream!.write(data);
});
streamsOfSocket.push({ stdoutStream, stderrStream, stdinStream, streamInputKey, streamOutputKey, websocket });
console.log(`Client ${socket.id} joined terminal stream for ${streamInputKey}`);
});
socket.on('closeTerminal', (podInfo) => {
console.warn('closeTerminal', podInfo);
const terminalInfo = terminalSetupInfoZodModel.parse(podInfo);
const streamInputKey = StreamUtils.getInputStreamName(terminalInfo);
const streams = streamsOfSocket.find(stream => stream.streamInputKey === streamInputKey);
const streams = streamsOfSocket.find(stream => stream.terminalSessionKey === terminalInfo.terminalSessionKey);
if (streams) {
this.deleteLogStream(streams);
}
@@ -101,12 +124,12 @@ export class TerminalService {
private deleteLogStream(streams: TerminalStrean) {
/* streams.stderrStream.end();
streams.stdoutStream.end();
streams.stdinStream.end();
streams.websocket.close();*/
/* streams.stderrStream.end();
streams.stdoutStream.end();
streams.stdinStream.end();
streams.websocket.close();*/
console.log(`Stopped log stream for ${streams.streamInputKey}.`);
console.log(`Stopped log stream for ${streams.terminalSessionKey}.`);
}
/*
private async createLogStreamForPod(socket: Socket<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, any>,
@@ -4,6 +4,8 @@ export const terminalSetupInfoZodModel = z.object({
namespace: z.string().min(1),
podName: z.string().min(1),
containerName: z.string().min(1),
terminalType: z.enum(['sh', 'bash']).default('bash').nullish(),
terminalSessionKey: z.string().nullish(),
});
export type TerminalSetupInfoModel = z.infer<typeof terminalSetupInfoZodModel>;
+2 -2
View File
@@ -3,10 +3,10 @@ import { TerminalSetupInfoModel } from "../model/terminal-setup-info.model";
export class StreamUtils {
static getInputStreamName(terminalInfo: TerminalSetupInfoModel) {
return `${terminalInfo.namespace}_${terminalInfo.podName}_${terminalInfo.containerName}_input`;
return `${terminalInfo.terminalSessionKey}_input`;
}
static getOutputStreamName(terminalInfo: TerminalSetupInfoModel) {
return `${terminalInfo.namespace}_${terminalInfo.podName}_${terminalInfo.containerName}_output`;
return `${terminalInfo.terminalSessionKey}_output`;
}
}