chore: use Xterm serialization to save container terminal content (#8498)

* chore: use Xterm serialization to save container terminal content
Signed-off-by: Sonia Sandler <ssandler@redhat.com>
This commit is contained in:
SoniaSandler 2024-08-22 09:01:00 -04:00 committed by GitHub
parent 2a26a867ca
commit 018a03910b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 54 additions and 33 deletions

View file

@ -25,6 +25,7 @@
"@types/js-yaml": "^4.0.9",
"@types/validator": "^13.12.0",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@xterm/addon-serialize": "^0.13.0",
"autoprefixer": "^10.4.20",
"eslint-plugin-svelte": "^2.43.0",
"filesize": "^10.1.4",

View file

@ -67,7 +67,7 @@ test('expect being able to reconnect ', async () => {
);
// render the component with a terminal
const renderObject = render(ContainerDetailsTerminal, { container, screenReaderMode: true });
let renderObject = render(ContainerDetailsTerminal, { container, screenReaderMode: true });
// wait shellInContainerMock is called
await waitFor(() => expect(shellInContainerMock).toHaveBeenCalled());
@ -96,11 +96,19 @@ test('expect being able to reconnect ', async () => {
expect(terminalsAfterDestroy.length).toBe(1);
// ok, now render a new terminal widget, it should reuse data from the store
render(ContainerDetailsTerminal, { container, screenReaderMode: true });
renderObject = render(ContainerDetailsTerminal, { container, screenReaderMode: true });
// wait shellInContainerMock is called
await waitFor(() => expect(shellInContainerMock).toHaveBeenCalledTimes(2));
// wait 1s that everything is done
await new Promise(resolve => setTimeout(resolve, 1000));
// no new call to shellInContainerMock should be done
expect(shellInContainerMock).toHaveBeenCalledTimes(1);
const terminalLinesLiveRegion2 = renderObject.container.querySelector('div[aria-live="assertive"]');
// check the content
expect(terminalLinesLiveRegion2).toHaveTextContent('hello world');
// creating a new terminal requires new shellInContainer call
expect(shellInContainerMock).toHaveBeenCalledTimes(2);
});

View file

@ -3,6 +3,7 @@ import '@xterm/xterm/css/xterm.css';
import { EmptyScreen } from '@podman-desktop/ui-svelte';
import { FitAddon } from '@xterm/addon-fit';
import { SerializeAddon } from '@xterm/addon-serialize';
import { Terminal } from '@xterm/xterm';
import { onDestroy, onMount } from 'svelte';
import { router } from 'tinro';
@ -20,6 +21,8 @@ let terminalXtermDiv: HTMLDivElement;
let shellTerminal: Terminal;
let currentRouterPath: string;
let sendCallbackId: number | undefined;
let terminalContent: string = '';
let serializeAddon: SerializeAddon;
// update current route scheme
router.subscribe(route => {
@ -50,24 +53,22 @@ async function executeShellIntoContainer() {
return;
}
if (!sendCallbackId) {
// grab logs of the container
const callbackId = await window.shellInContainer(
container.engineId,
container.id,
receiveDataCallback,
() => {},
receiveEndCallback,
);
await window.shellInContainerResize(callbackId, shellTerminal.cols, shellTerminal.rows);
// pass data from xterm to container
shellTerminal?.onData(data => {
window.shellInContainerSend(callbackId, data);
});
// grab logs of the container
const callbackId = await window.shellInContainer(
container.engineId,
container.id,
receiveDataCallback,
() => {},
receiveEndCallback,
);
await window.shellInContainerResize(callbackId, shellTerminal.cols, shellTerminal.rows);
// pass data from xterm to container
shellTerminal?.onData(data => {
window.shellInContainerSend(callbackId, data);
});
// store it
sendCallbackId = callbackId;
}
// store it
sendCallbackId = callbackId;
}
// refresh
@ -88,24 +89,24 @@ async function refreshTerminal() {
// get terminal if any
const existingTerminal = getExistingTerminal(container.engineId, container.id);
shellTerminal = new Terminal({
fontSize,
lineHeight,
screenReaderMode,
theme: getTerminalTheme(),
});
if (existingTerminal) {
sendCallbackId = existingTerminal.callbackId;
shellTerminal = existingTerminal.terminal;
shellTerminal.options = {
fontSize,
lineHeight,
};
} else {
shellTerminal = new Terminal({
fontSize,
lineHeight,
screenReaderMode,
theme: getTerminalTheme(),
});
shellTerminal.write(existingTerminal.terminal);
}
const fitAddon = new FitAddon();
serializeAddon = new SerializeAddon();
shellTerminal.loadAddon(fitAddon);
shellTerminal.loadAddon(serializeAddon);
shellTerminal.open(terminalXtermDiv);
@ -126,13 +127,16 @@ onMount(async () => {
});
onDestroy(() => {
terminalContent = serializeAddon.serialize();
// register terminal for reusing it
registerTerminal({
engineId: container.engineId,
containerId: container.id,
terminal: shellTerminal,
terminal: terminalContent,
callbackId: sendCallbackId,
});
serializeAddon?.dispose();
shellTerminal?.dispose();
});
</script>

View file

@ -16,7 +16,6 @@
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
import type { Terminal } from '@xterm/xterm';
import type { Writable } from 'svelte/store';
import { get, writable } from 'svelte/store';
@ -33,7 +32,7 @@ export interface TerminalOfContainer {
// id of the callbacks
callbackId?: number;
terminal: Terminal;
terminal: string;
}
/**
@ -68,6 +67,10 @@ containersInfos.subscribe(containers => {
export function registerTerminal(terminal: TerminalOfContainer) {
containerTerminals.update(terminals => {
// remove old instance(s) of terminal if exists
terminals = terminals.filter(
term => !(terminal.containerId === term.containerId && terminal.engineId === term.engineId),
);
terminals.push(terminal);
return terminals;
});

View file

@ -6974,6 +6974,11 @@
resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.5.0.tgz#275fb8f6e14afa6e8a0c05d4ebc94523ff775396"
integrity sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==
"@xterm/addon-serialize@^0.13.0":
version "0.13.0"
resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.13.0.tgz#f6e687708cacae67c4fc717fad37b9d11065897b"
integrity sha512-kGs8o6LWAmN1l2NpMp01/YkpxbmO4UrfWybeGu79Khw5K9+Krp7XhXbBTOTc3GJRRhd6EmILjpR8k5+odY39YQ==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"