Skip to content

Commit

Permalink
feat: first attempt of terminal
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Lauber <[email protected]>
  • Loading branch information
janlauber committed Jul 9, 2024
1 parent 1279866 commit c0a2d7e
Show file tree
Hide file tree
Showing 20 changed files with 635 additions and 8 deletions.
25 changes: 24 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/adapter-static": "^3.0.1",
"@webcontainer/api": "^1.1.9",
"@xterm/xterm": "^5.5.0",
"@xyflow/svelte": "^0.1.3",
"chart.js": "^4.4.3",
"diff": "^5.2.0",
Expand All @@ -67,6 +68,8 @@
"svelte-french-toast": "^1.2.0",
"svelte-local-storage-store": "^0.6.4",
"svelte-monaco": "^0.3.0",
"svelte-purify": "^1.1.27"
"svelte-purify": "^1.1.27",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0"
}
}
157 changes: 157 additions & 0 deletions frontend/src/lib/components/map/Console.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import darkTheme from "$lib/stores/theme";
import { terminal_size } from "$lib/stores/terminal";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
export let podName: string;
export let projectId: string;
const terminal = new Terminal({
convertEol: true,
disableStdin: false,
cursorBlink: true,
fontFamily: "monospace",
fontSize: 14,
theme: $darkTheme
? {
foreground: "#d2d2d2",
background: "#2B3441",
cursor: "#adadad",
black: "#000000",
red: "#d81e00",
green: "#5ea702",
yellow: "#cfae00",
blue: "#427ab3",
magenta: "#89658e",
cyan: "#00a7aa",
white: "#dbded8",
brightBlack: "#686a66",
brightRed: "#f54235",
brightGreen: "#99e343",
brightYellow: "#fdeb61",
brightBlue: "#84b0d8",
brightMagenta: "#bc94b7",
brightCyan: "#37e6e8",
brightWhite: "#f1f1f0"
}
: {
foreground: "#d2d2d2",
background: "#2B3441",
cursor: "#adadad",
black: "#000000",
red: "#d81e00",
green: "#5ea702",
yellow: "#cfae00",
blue: "#427ab3",
magenta: "#89658e",
cyan: "#00a7aa",
white: "#dbded8",
brightBlack: "#686a66",
brightRed: "#f54235",
brightGreen: "#99e343",
brightYellow: "#fdeb61",
brightBlue: "#84b0d8",
brightMagenta: "#bc94b7",
brightCyan: "#37e6e8",
brightWhite: "#f1f1f0"
},
scrollOnUserInput: true
});
let socket: WebSocket;
const fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);
const connectWebSocket = () => {
let host = window.location.host;
if (host.includes("localhost")) {
host = "localhost:8090";
}
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
socket = new WebSocket(`${protocol}://${host}/ws/k8s/terminal`);
socket.binaryType = "arraybuffer";
socket.onopen = () => {
const message = JSON.stringify({ projectId, podName });
socket.send(message);
terminal.onData((data) => {
socket.send(new TextEncoder().encode(data));
});
};
socket.onmessage = (event: MessageEvent) => {
if (event.data instanceof ArrayBuffer) {
terminal.write(new TextDecoder().decode(event.data));
}
};
socket.onerror = (error: Event) => {
const errorMessage = (error as ErrorEvent).message || "An error occurred";
terminal.write(`\r\nWebSocket error: ${errorMessage}\r\n`);
};
socket.onclose = () => {
terminal.write("\r\nWebSocket closed. Attempting to reconnect...\r\n");
setTimeout(connectWebSocket, 5000);
};
};
terminal.onResize((size) => {
const terminal_size = {
cols: size.cols,
rows: size.rows,
y: size.rows,
x: size.cols
};
if (socket.readyState === WebSocket.OPEN) {
socket.send(new TextEncoder().encode("\x01" + JSON.stringify(terminal_size)));
}
});
let div: HTMLDivElement;
onMount(() => {
connectWebSocket();
terminal.open(div);
setTimeout(() => {
fitAddon.fit();
}, 300);
});
onDestroy(() => {
socket?.close();
terminal.dispose();
});
export const update_height = () => {
fitAddon.fit();
};
$: {
$terminal_size;
setTimeout(() => {
update_height();
}, 300);
}
</script>

<div bind:this={div} style="height: 100%; width: 100%;" />

<style>
div {
height: 100%;
width: 100%;
}
div :global(.xterm) {
height: 100%;
padding: 5px;
}
div :global(.xterm-viewport) {
overflow-y: hidden !important;
}
</style>
14 changes: 14 additions & 0 deletions frontend/src/lib/components/map/Desktop.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script lang="ts">
import darkTheme from "$lib/stores/theme";
import type { ComponentType, SvelteComponent } from "svelte";
// eslint-disable-next-line @typescript-eslint/naming-convention
export let Console: ComponentType<SvelteComponent>;
export let podName: string;
export let projectId: string;
</script>

{#key $darkTheme}
<div class="h-full overflow-y-auto">
<svelte:component this={Console} {podName} {projectId} />
</div>
{/key}
9 changes: 9 additions & 0 deletions frontend/src/lib/components/map/PlaceholderComponent.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div />

<style>
div {
background-color: var(--sk-back-1);
width: 100%;
height: 100%;
}
</style>
26 changes: 26 additions & 0 deletions frontend/src/lib/components/map/ShellObject.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts">
import { onDestroy, onMount, type ComponentType, type SvelteComponent } from "svelte";
import PlaceholderComponent from "./PlaceholderComponent.svelte";
import Desktop from "./Desktop.svelte";
export let podName: string;
export let projectId: string;
let Console: ComponentType<SvelteComponent> = PlaceholderComponent;
let clear: number;
// let loadingCodeEditor = false;
onMount(async () => {
// loadingCodeEditor = true;
Console = (await import("$lib/components/map/Console.svelte")).default;
});
onDestroy(() => {
// loadingCodeEditor = false;
Console = PlaceholderComponent;
clearInterval(clear);
});
</script>

<div class="p-2 rounded-lg bg-gray-800">
<Desktop {Console} {podName} {projectId} />
</div>
6 changes: 6 additions & 0 deletions frontend/src/lib/stores/terminal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { writable } from "svelte/store";

export const terminal_size = writable({ height: 65 });

// get pathnames from url
export const pathname = writable(window.location.pathname);
5 changes: 3 additions & 2 deletions frontend/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import "../app.postcss";
import "../app.css";
import "../styles/app.postcss";
import "../styles/app.css";
import "../styles/xterm.css";
import { metadata } from "$lib/stores/metadata";
import { site } from "$lib/config";
import { beforeNavigate } from "$app/navigation";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Lock,
NetworkIcon,
ScrollText,
TerminalSquare,
Trash,
X
} from "lucide-svelte";
Expand All @@ -29,6 +30,8 @@
import EventStream from "$lib/components/map/EventStream.svelte";
import toast from "svelte-french-toast";
import selectedDeploymentId from "$lib/stores/deployment";
import ShellObject from "$lib/components/map/ShellObject.svelte";
import selectedProjectId from "$lib/stores/project";
let transitionParamsRight = {
x: 320,
Expand Down Expand Up @@ -599,6 +602,13 @@
</div>
<LogStream podName={$selectedNode?.name ?? ""} />
</TabItem>
<TabItem>
<div slot="title" class="flex items-center gap-2">
<TerminalSquare />
Shell
</div>
<ShellObject podName={$selectedNode?.name ?? ""} projectId={$selectedProjectId} />
</TabItem>
<TabItem>
<div slot="title" class="flex items-center gap-2">
<Bell />
Expand Down
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit c0a2d7e

Please sign in to comment.