mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-05-14 05:48:40 -05:00
fixes on terminal
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
import { Manager } from "socket.io-client";
|
||||
|
||||
const manager = new Manager();
|
||||
|
||||
export const podTerminalSocket = manager.socket("/pod-terminal");
|
||||
@@ -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>;
|
||||
@@ -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`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user