Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: use tty for containers started with TTY/interactive mode
note: if you start with interactive mode, if you start a container, you are redirected to the tty tab of the container if you click on the details of a container, you're redirected to the tty tab if the container has been launched using tty/interactive else you see the logs In the details of a container, tty tab is only available if container is started with tty/interactive mode else it's hidden. fixes #970 Signed-off-by: Florent Benoit <fbenoit@redhat.com>
- Loading branch information
Showing
8 changed files
with
418 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
packages/renderer/src/lib/container/ContainerDetailsTtyTerminal.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/********************************************************************** | ||
* Copyright (C) 2023 Red Hat, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
***********************************************************************/ | ||
|
||
import '@testing-library/jest-dom/vitest'; | ||
import { test, expect, vi, beforeAll } from 'vitest'; | ||
import { render, waitFor } from '@testing-library/svelte'; | ||
import type { ContainerInfoUI } from './ContainerInfoUI'; | ||
import ContainerDetailsTtyTerminal from './ContainerDetailsTtyTerminal.svelte'; | ||
|
||
const getConfigurationValueMock = vi.fn(); | ||
const attachContainerMock = vi.fn(); | ||
|
||
beforeAll(() => { | ||
(window as any).getConfigurationValue = getConfigurationValueMock; | ||
(window as any).attachContainer = attachContainerMock; | ||
(window as any).attachContainerSend = vi.fn(); | ||
|
||
(window as any).matchMedia = vi.fn().mockReturnValue({ | ||
addListener: vi.fn(), | ||
}); | ||
}); | ||
|
||
test('expect being able to attach terminal ', async () => { | ||
const container: ContainerInfoUI = { | ||
id: 'myContainer', | ||
state: 'RUNNING', | ||
engineId: 'podman', | ||
} as unknown as ContainerInfoUI; | ||
|
||
let onDataCallback: (data: Buffer) => void = () => {}; | ||
|
||
const sendCallbackId = 12345; | ||
attachContainerMock.mockImplementation( | ||
( | ||
_engineId: string, | ||
_containerId: string, | ||
onData: (data: Buffer) => void, | ||
_onError: (error: string) => void, | ||
_onEnd: () => void, | ||
) => { | ||
onDataCallback = onData; | ||
// return a callback id | ||
return sendCallbackId; | ||
}, | ||
); | ||
|
||
// render the component with a terminal | ||
const renderObject = render(ContainerDetailsTtyTerminal, { container, screenReaderMode: true }); | ||
|
||
// wait attachContainerMock is called | ||
await waitFor(() => expect(attachContainerMock).toHaveBeenCalled()); | ||
|
||
// write some data on the terminal | ||
onDataCallback(Buffer.from('hello\nworld')); | ||
|
||
// wait 1s | ||
await new Promise(resolve => setTimeout(resolve, 1000)); | ||
|
||
// search a div having aria-live="assertive" attribute | ||
const terminalLinesLiveRegion = renderObject.container.querySelector('div[aria-live="assertive"]'); | ||
|
||
// check the content | ||
expect(terminalLinesLiveRegion).toHaveTextContent('hello world'); | ||
|
||
// check we have called attachContainer | ||
expect(attachContainerMock).toHaveBeenCalledTimes(1); | ||
}); |
112 changes: 112 additions & 0 deletions
112
packages/renderer/src/lib/container/ContainerDetailsTtyTerminal.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
<script lang="ts"> | ||
import type { ContainerInfoUI } from './ContainerInfoUI'; | ||
import { TerminalSettings } from '../../../../main/src/plugin/terminal-settings'; | ||
import { router } from 'tinro'; | ||
import { onDestroy, onMount } from 'svelte'; | ||
import { Terminal } from 'xterm'; | ||
import { FitAddon } from 'xterm-addon-fit'; | ||
import 'xterm/css/xterm.css'; | ||
import { getPanelDetailColor } from '../color/color'; | ||
import EmptyScreen from '../ui/EmptyScreen.svelte'; | ||
import NoLogIcon from '../ui/NoLogIcon.svelte'; | ||
export let container: ContainerInfoUI; | ||
export let screenReaderMode = false; | ||
let terminalXtermDiv: HTMLDivElement; | ||
let attachContainerTerminal: Terminal; | ||
let currentRouterPath: string; | ||
let closed = false; | ||
// update current route scheme | ||
router.subscribe(route => { | ||
currentRouterPath = route.path; | ||
}); | ||
// update terminal when receiving data | ||
function receiveDataCallback(data: Buffer) { | ||
attachContainerTerminal.write(data.toString()); | ||
} | ||
function receiveEndCallback() { | ||
closed = true; | ||
} | ||
// call exec command | ||
async function attachToContainer() { | ||
if (container.state !== 'RUNNING') { | ||
return; | ||
} | ||
// attach to the container | ||
const callbackId = await window.attachContainer( | ||
container.engineId, | ||
container.id, | ||
receiveDataCallback, | ||
() => {}, | ||
receiveEndCallback, | ||
); | ||
// pass data from xterm to container | ||
attachContainerTerminal?.onData(data => { | ||
window.attachContainerSend(callbackId, data); | ||
}); | ||
} | ||
// refresh | ||
async function refreshTerminal() { | ||
// missing element, return | ||
if (!terminalXtermDiv) { | ||
return; | ||
} | ||
// grab font size | ||
const fontSize = await window.getConfigurationValue<number>( | ||
TerminalSettings.SectionName + '.' + TerminalSettings.FontSize, | ||
); | ||
const lineHeight = await window.getConfigurationValue<number>( | ||
TerminalSettings.SectionName + '.' + TerminalSettings.LineHeight, | ||
); | ||
attachContainerTerminal = new Terminal({ | ||
fontSize, | ||
lineHeight, | ||
screenReaderMode, | ||
theme: { | ||
background: getPanelDetailColor(), | ||
}, | ||
}); | ||
const fitAddon = new FitAddon(); | ||
attachContainerTerminal.loadAddon(fitAddon); | ||
attachContainerTerminal.open(terminalXtermDiv); | ||
// call fit addon each time we resize the window | ||
window.addEventListener('resize', () => { | ||
if (currentRouterPath === `/containers/${container.id}/tty-terminal`) { | ||
fitAddon.fit(); | ||
} | ||
}); | ||
fitAddon.fit(); | ||
} | ||
onMount(async () => { | ||
await refreshTerminal(); | ||
await attachToContainer(); | ||
}); | ||
onDestroy(() => {}); | ||
</script> | ||
|
||
<div class="h-full" bind:this="{terminalXtermDiv}" class:hidden="{container.state !== 'RUNNING'}"></div> | ||
|
||
<EmptyScreen | ||
hidden="{!closed && !(container.state === 'RUNNING')}" | ||
icon="{NoLogIcon}" | ||
title="No TTY" | ||
message="Tty has stopped" /> | ||
|
||
<EmptyScreen | ||
hidden="{container.state === 'RUNNING'}" | ||
icon="{NoLogIcon}" | ||
title="No TTY" | ||
message="Container is not running" /> |
Oops, something went wrong.