mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-05-24 10:18:53 +00:00
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:
parent
2a26a867ca
commit
018a03910b
5 changed files with 54 additions and 33 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue