Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a way to clean logs in container's log page #9528

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**********************************************************************
* Copyright (C) 2024 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 { render, screen } from '@testing-library/svelte';
import * as xterm from '@xterm/xterm';
import { beforeAll, expect, test, vi } from 'vitest';

import ContainerDetailsLogs from './ContainerDetailsLogs.svelte';
import type { ContainerInfoUI } from './ContainerInfoUI';

vi.mock('@xterm/xterm', () => {
const writeMock = vi.fn();
return {
writeMock,
Terminal: vi.fn().mockReturnValue({
loadAddon: vi.fn(),
open: vi.fn().mockImplementation((div: HTMLDivElement) => {
// create a dummy div element
const h = document.createElement('H1');
const t = document.createTextNode('dummy element');
h.appendChild(t);
div.appendChild(h);
}),
write: writeMock,
clear: vi.fn(),
dispose: vi.fn(),
}),
};
});

beforeAll(() => {
// Mock returned values with fake ones
const mockComputedStyle = {
getPropertyValue: vi.fn().mockReturnValue('#ffffff'),
};
Object.defineProperty(global, 'window', {
value: {
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
logsContainer: vi.fn(),
getConfigurationValue: vi.fn(),
getComputedStyle: vi.fn().mockReturnValue(mockComputedStyle),
dispatchEvent: vi.fn(),
},
writable: true,
});
});

const container: ContainerInfoUI = {
id: 'foo',
} as unknown as ContainerInfoUI;

test('Render container logs ', async () => {
// grab the xterm mock
let writeMock: unknown | undefined;
if ('writeMock' in xterm) {
writeMock = xterm.writeMock;
}

// Mock compose has no containers, so expect No Log to appear
render(ContainerDetailsLogs, { container });

// expect a call to logsContainer
await vi.waitFor(() => {
expect(window.logsContainer).toHaveBeenCalled();
});
// now, get the callback of the method
const params = vi.mocked(window.logsContainer).mock.calls[0][0];
// call the callback with an empty array
params.callback('data', 'hello world');

// expect logs to have been called
await vi.waitFor(() => {
expect(vi.mocked(writeMock)).toHaveBeenCalled();
});

// expect the button to clear
const clearButton = screen.getByRole('button', { name: 'Clear logs' });
expect(clearButton).toBeInTheDocument();
});
30 changes: 26 additions & 4 deletions packages/renderer/src/lib/container/ContainerDetailsLogs.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import '@xterm/xterm/css/xterm.css';
import { EmptyScreen } from '@podman-desktop/ui-svelte';
import type { Terminal } from '@xterm/xterm';
import { onMount } from 'svelte';
import { mount, onDestroy, onMount } from 'svelte';
import { isMultiplexedLog } from '../stream/stream-utils';
import NoLogIcon from '../ui/NoLogIcon.svelte';
import TerminalWindow from '../ui/TerminalWindow.svelte';
import ContainerDetailsLogsClear from './ContainerDetailsLogsClear.svelte';
import type { ContainerInfoUI } from './ContainerInfoUI';
export let container: ContainerInfoUI;
Expand All @@ -28,6 +29,7 @@ $: {
}
refContainer = container;
}
let terminalParentDiv: HTMLDivElement;
let logsTerminal: Terminal;
Expand All @@ -54,8 +56,27 @@ async function fetchContainerLogs() {
await window.logsContainer({ engineId: container.engineId, containerId: container.id, callback });
}
function afterTerminalInit(): void {
// mount the svelte5 component to the terminal xterm element
let xtermElement = terminalParentDiv.querySelector('.xterm');
if (!xtermElement) {
xtermElement = terminalParentDiv;
}
// add svelte component using this xterm element
mount(ContainerDetailsLogsClear, {
target: xtermElement,
props: {
terminal: logsTerminal,
},
});
}
onMount(async () => {
fetchContainerLogs();
await fetchContainerLogs();
});
onDestroy(() => {
logsTerminal?.dispose();
});
</script>

Expand All @@ -65,6 +86,7 @@ onMount(async () => {
class="min-w-full flex flex-col"
class:invisible={noLogs === true}
class:h-0={noLogs === true}
class:h-full={noLogs === false}>
<TerminalWindow class="h-full" bind:terminal={logsTerminal} convertEol disableStdIn />
class:h-full={noLogs === false}
bind:this={terminalParentDiv}>
<TerminalWindow on:init={afterTerminalInit} class="h-full" bind:terminal={logsTerminal} convertEol disableStdIn />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**********************************************************************
* Copyright (C) 2024 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 { fireEvent, render, screen, waitFor } from '@testing-library/svelte';
import { Terminal } from '@xterm/xterm';
import { expect, test, vi } from 'vitest';

import ContainerDetailsLogsClear from './ContainerDetailsLogsClear.svelte';

vi.mock('@xterm/xterm', () => {
const writeMock = vi.fn();
return {
writeMock,
Terminal: vi
.fn()
.mockReturnValue({ loadAddon: vi.fn(), open: vi.fn(), write: writeMock, clear: vi.fn(), dispose: vi.fn() }),
};
});

test('expect clear button is working', async () => {
const terminal = new Terminal();

render(ContainerDetailsLogsClear, { terminal });

// expect the button to clear
const clearButton = screen.getByRole('button', { name: 'Clear logs' });
expect(clearButton).toBeInTheDocument();

// click the button
await fireEvent.click(clearButton);

// check we have called the clear function
await waitFor(() => expect(terminal.clear).toHaveBeenCalled());
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script lang="ts">
import { faEraser } from '@fortawesome/free-solid-svg-icons';
import type { Terminal } from '@xterm/xterm';
import Fa from 'svelte-fa';
const { terminal }: { terminal: Terminal } = $props();
function clear(): void {
terminal.clear();
}
</script>

<div class="absolute top-0 right-0 px-1 z-50 m-1 opacity-50 space-x-1">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first Logs I went to I wouldn't have known the button was there if I wasn't looking. Maybe a little more opacity?
Screenshot 2024-10-22 at 3 51 42 PM

Likewise, it is over top of the mem tooltip. z-order should be lowered so that tooltips remain on top.
Screenshot 2024-10-22 at 3 53 41 PM

<button title="Clear logs" onclick={clear}>
<Fa
class="cursor-pointer rounded-full bg-[var(--pd-button-disabled)] min-h-8 w-8 p-1.5"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a hover effect.

The icon isn't as bright as the actions on top, but if I remove the opacity it is too bright. I think it's missing a text- and regular opacity would help with the previous comment.

icon={faEraser}
/>
</button>
</div>