diff --git a/Dockerfile b/Dockerfile index 6382fac..f37c069 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on the preferred package manager -COPY bun.lockb package.json ./ +COPY yarn.lock package.json ./ RUN yarn install diff --git a/package.json b/package.json index 2e7471e..1b6328d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "next dev", - "dev-live": "tsc src/server.ts", + "dev-live": "tsc --project tsconfig.server.json && node dist/server.js", "build": "next build && tsc --project tsconfig.server.json", "start": "next start", "start-prod": "cross-env NODE_ENV=production node dist/server.js", diff --git a/src/app/project/app/[appId]/overview/logs.tsx b/src/app/project/app/[appId]/overview/logs.tsx index bd15cd8..3a1ea9a 100644 --- a/src/app/project/app/[appId]/overview/logs.tsx +++ b/src/app/project/app/[appId]/overview/logs.tsx @@ -9,7 +9,7 @@ import FullLoadingSpinner from "@/components/ui/full-loading-spinnter"; import { toast } from "sonner"; import { LogsDialog } from "@/components/custom/logs-overlay"; import { Button } from "@/components/ui/button"; -import { Expand, SquareArrowUp, SquareArrowUpRight } from "lucide-react"; +import { Expand, SquareArrowUp, SquareArrowUpRight, Terminal } from "lucide-react"; import { TerminalDialog } from "./terminal-overlay"; export default function Logs({ @@ -81,7 +81,7 @@ export default function Logs({ namespace: app.projectId }} > diff --git a/src/app/project/app/[appId]/overview/terminal-streamed.tsx b/src/app/project/app/[appId]/overview/terminal-streamed.tsx index 33ebb14..571f604 100644 --- a/src/app/project/app/[appId]/overview/terminal-streamed.tsx +++ b/src/app/project/app/[appId]/overview/terminal-streamed.tsx @@ -46,34 +46,31 @@ export default function TerminalStreamed({ }); podTerminalSocket.on(terminalOutputKey, (data: string) => { - console.log('Received data:', data); term.write(data); }); podTerminalSocket.emit('openTerminal', termInfo); - term.write('Terminal is ready'); setTerminal(term); setSessionTerminalInfo(termInfo); }; - - useEffect(() => { - return () => { - console.log('Disconnecting from terminal...'); - //terminal?.dispose(); - //if (sessionTerminalInfo) podTerminalSocket.emit('closeTerminal', sessionTerminalInfo); - }; - }); + const disconnectTerminalSession = () => { + terminal?.dispose(); + if (sessionTerminalInfo) { + podTerminalSocket.emit('closeTerminal', sessionTerminalInfo); + setSessionTerminalInfo(undefined); + } + } return <>
- {!sessionTerminalInfo && <> + {!sessionTerminalInfo ? <>
- } -
+ : } +
; diff --git a/src/server/services/terminal.service.ts b/src/server/services/terminal.service.ts index e522978..3c6d5f6 100644 --- a/src/server/services/terminal.service.ts +++ b/src/server/services/terminal.service.ts @@ -5,6 +5,7 @@ import * as k8s from '@kubernetes/client-node'; import stream from 'stream'; import { StreamUtils } from "../../shared/utils/stream.utils"; import WebSocket from "ws"; +import setupPodService from "./setup-services/setup-pod.service"; interface TerminalStrean { stdoutStream: stream.PassThrough; @@ -33,68 +34,60 @@ export class TerminalService { const streamInputKey = StreamUtils.getInputStreamName(terminalInfo); const streamOutputKey = StreamUtils.getOutputStreamName(terminalInfo); - /*const podReachable = await setupPodService.waitUntilPodIsRunningFailedOrSucceded(terminalInfo.namespace, terminalInfo.podName); + const podReachable = await setupPodService.waitUntilPodIsRunningFailedOrSucceded(terminalInfo.namespace, terminalInfo.podName); if (!podReachable) { socket.emit(streamOutputKey, 'Pod is not reachable.'); 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') - await exec.exec( + + const socketStreamInfo = { + stdoutStream, + stderrStream, + stdinStream, + terminalSessionKey: terminalInfo.terminalSessionKey ?? '', + } as TerminalStrean; + streamsOfSocket.push(socketStreamInfo); + + const websocket = await 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 */, + true /* tty */, (status: k8s.V1Status) => { console.log('[EXIT] Exited with status:'); console.log(JSON.stringify(status, null, 2)); - stderrStream!.end(); - stdoutStream!.end(); - stdinStream!.end(); + if (status.status === 'Failure') { + socket.emit(streamOutputKey, `\n[ERROR] Error while opening Terminal session\n`); + socket.emit(streamOutputKey, `\n${status.message}\n`); + } else { + socket.emit(streamOutputKey, `\n[INFO] Terminal session closed\n`); + } + this.cleanupLogStream(socketStreamInfo); }, ); + socketStreamInfo.websocket = websocket; 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}`); @@ -109,49 +102,26 @@ export class TerminalService { const streams = streamsOfSocket.find(stream => stream.terminalSessionKey === terminalInfo.terminalSessionKey); if (streams) { - this.deleteLogStream(streams); + this.cleanupLogStream(streams); } }); socket.on('disconnecting', () => { // Stop all log streams for this client for (const stream of streamsOfSocket) { - this.deleteLogStream(stream); + this.cleanupLogStream(stream); } }); } - private deleteLogStream(streams: TerminalStrean) { - /* streams.stderrStream.end(); - streams.stdoutStream.end(); - streams.stdinStream.end(); - streams.websocket.close();*/ - - console.log(`Stopped log stream for ${streams.terminalSessionKey}.`); + private cleanupLogStream(stream: TerminalStrean) { + stream.stderrStream.end(); + stream.stdoutStream.end(); + stream.stdinStream.end(); + stream.websocket?.close(); + console.log(`Stopped terminal stream for ${stream.terminalSessionKey}.`); } - /* - private async createLogStreamForPod(socket: Socket, - streamKey: string, inputInfo: TerminalSetupInfoModel) { - - - - logStream.on('data', (chunk) => { - socket.emit(streamKey, chunk.toString()); - }); - - logStream.on('data', (chunk) => { - socket.to(streamKey).emit(`${streamKey}`, chunk.toString()); - }); - - let k3sStreamRequest = await k3s.log.log(app.projectId, pod.podName, pod.containerName, logStream, { - follow: true, - pretty: false, - tailLines: 100, - }); - const retVal = { logStream, clients: 0, k3sStreamRequest }; - return retVal; - }*/ } const terminalService = new TerminalService();