added terminal frontend test

This commit is contained in:
biersoeckli
2024-11-27 16:59:59 +00:00
parent 65abd04ee7
commit 21ebe47848
5 changed files with 138 additions and 13 deletions

View File

@@ -14,6 +14,7 @@ import { BuildJobModel } from "@/shared/model/build-job";
import BuildsTab from "./overview/deployments";
import Logs from "./overview/logs";
import InternalHostnames from "./domains/internal-hostnames";
import TerminalStreamed from "./overview/terminal-streamed";
export default function AppTabs({
app,

View File

@@ -1,7 +1,6 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { AppExtendedModel } from "@/shared/model/app-extended.model";
import { useEffect, useState } from "react";
import { podLogsSocket } from "@/frontend/sockets/sockets";
import LogsStreamed from "../../../../../components/custom/logs-streamed";
import { getPodsForApp } from "./actions";
import { PodsInfoModel } from "@/shared/model/pods-info.model";
@@ -11,13 +10,14 @@ 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 { TerminalDialog } from "./terminal-overlay";
export default function Logs({
app
}: {
app: AppExtendedModel;
}) {
const [selectedPod, setSelectedPod] = useState<string | undefined>(undefined);
const [selectedPod, setSelectedPod] = useState<PodsInfoModel | undefined>(undefined);
const [appPods, setAppPods] = useState<PodsInfoModel[] | undefined>(undefined);
const updateBuilds = async () => {
@@ -42,15 +42,15 @@ export default function Logs({
}, [app]);
useEffect(() => {
if (appPods && selectedPod && !appPods.find(p => p.podName === selectedPod)) {
if (appPods && selectedPod && !appPods.find(p => p.podName === selectedPod.podName)) {
// current selected pod is not in the list anymore
setSelectedPod(undefined);
if (appPods.length > 0) {
setSelectedPod(appPods[0].podName);
setSelectedPod(appPods[0]);
}
} else if (!selectedPod && appPods && appPods.length > 0) {
// no pod selected yet, initialize with first pod
setSelectedPod(appPods[0].podName);
setSelectedPod(appPods[0]);
}
}, [appPods]);
@@ -65,7 +65,7 @@ export default function Logs({
{appPods && appPods.length === 0 && <div>No running pods found for this app.</div>}
{selectedPod && appPods && <div className="flex gap-4">
<div className="flex-1">
<Select value={selectedPod} onValueChange={(val) => setSelectedPod(val)}>
<Select value={selectedPod.podName} onValueChange={(val) => setSelectedPod(appPods.find(p => p.podName === val))}>
<SelectTrigger className="w-full" >
<SelectValue placeholder="Pod wählen" />
</SelectTrigger>
@@ -75,14 +75,25 @@ export default function Logs({
</Select>
</div>
<div>
<LogsDialog namespace={app.projectId} podName={selectedPod}>
<TerminalDialog terminalInfo={{
podName: selectedPod.podName,
containerName: selectedPod.containerName,
namespace: app.projectId
}} >
<Button variant="secondary">
Terminal
</Button>
</TerminalDialog>
</div>
<div>
<LogsDialog namespace={app.projectId} podName={selectedPod.podName}>
<Button variant="secondary">
<Expand />
</Button>
</LogsDialog>
</div>
</div>}
{app.projectId && selectedPod && <LogsStreamed namespace={app.projectId} podName={selectedPod} />}
{app.projectId && selectedPod && <LogsStreamed namespace={app.projectId} podName={selectedPod.podName} />}
</CardContent>
</Card >
</>;

View File

@@ -0,0 +1,44 @@
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import React, { useEffect } from "react";
import { TerminalSetupInfoModel } from "@/shared/model/terminal-setup-info.model";
import TerminalStreamed from "./terminal-streamed";
export function TerminalDialog({
terminalInfo,
children
}: {
terminalInfo: TerminalSetupInfoModel;
children: React.ReactNode;
}) {
const [isOpen, setIsOpen] = React.useState(false);
return (
<Dialog open={isOpen} onOpenChange={(isO) => {
setIsOpen(isO);
}}>
<DialogTrigger asChild>
{children}
</DialogTrigger>
<DialogContent className="sm:max-w-[1300px]">
<DialogHeader>
<DialogTitle>Terminal</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{terminalInfo ? <TerminalStreamed terminalInfo={terminalInfo} /> : 'Currently there is no Terminal available'}
</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1,64 @@
import { useEffect, useRef, useState } from "react";
import { Textarea } from "@/components/ui/textarea";
import React from "react";
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card"
import { TerminalSetupInfoModel } from "@/shared/model/terminal-setup-info.model";
import { Terminal } from '@xterm/xterm'
import '@xterm/xterm/css/xterm.css'
import { podTerminalSocket } from "@/frontend/sockets/sockets";
import { StreamUtils } from "@/shared/utils/stream.utils";
export default function TerminalStreamed({
terminalInfo,
}: {
terminalInfo: TerminalSetupInfoModel;
}) {
const [isConnected, setIsConnected] = useState(false);
const [logs, setLogs] = useState<string>('');
const terminalWindow = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!terminalInfo || !terminalWindow || !terminalWindow.current) {
return;
}
const terminalInputKey = StreamUtils.getInputStreamName(terminalInfo);
const terminalOutputKey = StreamUtils.getOutputStreamName(terminalInfo);
var term = new Terminal();
term.open(terminalWindow.current);
term.onData((data) => {
podTerminalSocket.emit(terminalInputKey, data);
});
podTerminalSocket.on(terminalOutputKey, (data: string) => {
console.log('Received data:', data);
term.write(data);
});
podTerminalSocket.emit('openTerminal', terminalInfo);
term.write('Terminal is ready');
return () => {
console.log('Disconnecting from terminal...');
term.dispose();
podTerminalSocket.emit('closeTerminal', terminalInfo);
};
}, [terminalInfo]);
return <>
<div className="space-y-4">
<div ref={terminalWindow} ></div>
</div>
</>;
}

View File

@@ -47,9 +47,12 @@ export class TerminalService {
terminalInfo.podName,
terminalInfo.containerName,
['/bin/sh'],
stdoutStream,
process.stdout,
process.stderr,
process.stdin,
/* stdoutStream,
stderrStream,
stdinStream,
stdinStream,*/
true /* tty */,
(status: k8s.V1Status) => {
console.log('Exited with status:');
@@ -61,9 +64,11 @@ export class TerminalService {
);
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) => {
@@ -73,7 +78,7 @@ export class TerminalService {
streamsOfSocket.push({ stdoutStream, stderrStream, stdinStream, streamInputKey, streamOutputKey, websocket });
console.log(`Client ${socket.id} joined log stream for ${stdoutStream}`);
console.log(`Client ${socket.id} joined terminal stream for ${streamInputKey}`);
});
socket.on('closeTerminal', (podInfo) => {
@@ -96,10 +101,10 @@ export class TerminalService {
private deleteLogStream(streams: TerminalStrean) {
streams.stderrStream.end();
/* streams.stderrStream.end();
streams.stdoutStream.end();
streams.stdinStream.end();
streams.websocket.close();
streams.websocket.close();*/
console.log(`Stopped log stream for ${streams.streamInputKey}.`);
}