mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-01-02 17:51:47 -06:00
added terminal frontend test
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 >
|
||||
</>;
|
||||
|
||||
44
src/app/project/app/[appId]/overview/terminal-overlay.tsx
Normal file
44
src/app/project/app/[appId]/overview/terminal-overlay.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
64
src/app/project/app/[appId]/overview/terminal-streamed.tsx
Normal file
64
src/app/project/app/[appId]/overview/terminal-streamed.tsx
Normal 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>
|
||||
</>;
|
||||
}
|
||||
@@ -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}.`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user