diff --git a/packages/renderer/src/lib/ContainerList.spec.ts b/packages/renderer/src/lib/ContainerList.spec.ts
index a77a0a1504c47..ff877dee6ae71 100644
--- a/packages/renderer/src/lib/ContainerList.spec.ts
+++ b/packages/renderer/src/lib/ContainerList.spec.ts
@@ -334,3 +334,50 @@ test('Try to delete a pod without deleting container', async () => {
// and the standalone container has not been deleted
expect(deleteContainerMock).not.toHaveBeenCalled();
});
+
+test('Expect filter empty screen', async () => {
+ getProviderInfosMock.mockResolvedValue([
+ {
+ name: 'podman',
+ status: 'started',
+ internalId: 'podman-internal-id',
+ containerConnections: [
+ {
+ name: 'podman-machine-default',
+ status: 'started',
+ },
+ ],
+ },
+ ]);
+
+ const singleContainer = {
+ Id: 'sha256:1234567890123',
+ Image: 'sha256:123',
+ Names: ['foo'],
+ Status: 'Running',
+ engineId: 'podman',
+ engineName: 'podman',
+ };
+
+ // one single container
+ const mockedContainers = [singleContainer];
+
+ listContainersMock.mockResolvedValue(mockedContainers);
+
+ window.dispatchEvent(new CustomEvent('extensions-already-started'));
+ window.dispatchEvent(new CustomEvent('provider-lifecycle-change'));
+ window.dispatchEvent(new CustomEvent('tray:update-provider'));
+
+ // wait store are populated
+ while (get(containersInfos).length === 0) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ while (get(providerInfos).length === 0) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+ await waitRender({ searchTerm: 'No match' });
+
+ const filterButton = screen.getByRole('button', { name: 'Clear filter' });
+ expect(filterButton).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/ContainerList.svelte b/packages/renderer/src/lib/ContainerList.svelte
index c57c609999079..6d8068b352825 100644
--- a/packages/renderer/src/lib/ContainerList.svelte
+++ b/packages/renderer/src/lib/ContainerList.svelte
@@ -11,7 +11,9 @@ import { router } from 'tinro';
import { ContainerGroupInfoTypeUI, type ContainerGroupInfoUI, type ContainerInfoUI } from './container/ContainerInfoUI';
import ContainerActions from './container/ContainerActions.svelte';
import PodActions from './pod/PodActions.svelte';
+import ContainerIcon from './images/ContainerIcon.svelte';
import ContainerEmptyScreen from './container/ContainerEmptyScreen.svelte';
+import FilteredEmptyScreen from './ui/FilteredEmptyScreen.svelte';
import Modal from './dialogs/Modal.svelte';
import { ContainerUtils } from './container/container-utils';
import { providerInfos } from '../stores/providers';
@@ -641,7 +643,11 @@ function errorCallback(container: ContainerInfoUI, errorMessage: string): void {
{#if providerConnections.length === 0}
{:else if $filtered.length === 0}
-
+ {#if searchTerm}
+
+ {:else}
+
+ {/if}
{/if}
diff --git a/packages/renderer/src/lib/ImagesList.svelte b/packages/renderer/src/lib/ImagesList.svelte
index e51a7ac15234f..7f36c2638cddd 100644
--- a/packages/renderer/src/lib/ImagesList.svelte
+++ b/packages/renderer/src/lib/ImagesList.svelte
@@ -2,6 +2,7 @@
import { filtered, searchPattern, imagesInfos } from '../stores/images';
import { onDestroy, onMount } from 'svelte';
import ImageEmptyScreen from './image/ImageEmptyScreen.svelte';
+import FilteredEmptyScreen from './ui/FilteredEmptyScreen.svelte';
import { router } from 'tinro';
import type { ImageInfoUI } from './image/ImageInfoUI';
@@ -27,7 +28,7 @@ import Checkbox from './ui/Checkbox.svelte';
import Button from './ui/Button.svelte';
import { faArrowCircleDown, faCube, faTrash } from '@fortawesome/free-solid-svg-icons';
-let searchTerm = '';
+export let searchTerm = '';
$: searchPattern.set(searchTerm);
let images: ImageInfoUI[] = [];
@@ -328,7 +329,11 @@ function computeInterval(): number {
{#if providerConnections.length === 0}
{:else if $filtered.length === 0}
-
+ {#if searchTerm}
+
+ {:else}
+
+ {/if}
{/if}
{#if pushImageModal && pushImageModalImageInfo}
diff --git a/packages/renderer/src/lib/ImagestList.spec.ts b/packages/renderer/src/lib/ImagestList.spec.ts
index ef7bda99400d3..e3bcc41486655 100644
--- a/packages/renderer/src/lib/ImagestList.spec.ts
+++ b/packages/renderer/src/lib/ImagestList.spec.ts
@@ -135,3 +135,48 @@ test('Expect images being ordered by newest first', async () => {
expect(fedoraRecent.compareDocumentPosition(veryOld)).toBe(4);
expect(fedoraOld.compareDocumentPosition(veryOld)).toBe(4);
});
+
+test('Expect filter empty screen', async () => {
+ getProviderInfosMock.mockResolvedValue([
+ {
+ name: 'podman',
+ status: 'started',
+ internalId: 'podman-internal-id',
+ containerConnections: [
+ {
+ name: 'podman-machine-default',
+ status: 'started',
+ },
+ ],
+ },
+ ]);
+
+ listImagesMock.mockResolvedValue([
+ {
+ Id: 'sha256:1234567890123',
+ RepoTags: ['fedora:old'],
+ Created: 1644009612,
+ Size: 123,
+ Status: 'Running',
+ engineId: 'podman',
+ engineName: 'podman',
+ },
+ ]);
+
+ window.dispatchEvent(new CustomEvent('extensions-already-started'));
+ window.dispatchEvent(new CustomEvent('provider-lifecycle-change'));
+ window.dispatchEvent(new CustomEvent('image-build'));
+
+ // wait store are populated
+ while (get(imagesInfos).length === 0) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+ while (get(providerInfos).length === 0) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ await waitRender({ searchTerm: 'No match' });
+
+ const filterButton = screen.getByRole('button', { name: 'Clear filter' });
+ expect(filterButton).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/pod/PodsList.spec.ts b/packages/renderer/src/lib/pod/PodsList.spec.ts
index 2688960ce5420..948370e1adaf1 100644
--- a/packages/renderer/src/lib/pod/PodsList.spec.ts
+++ b/packages/renderer/src/lib/pod/PodsList.spec.ts
@@ -231,3 +231,23 @@ test('Expect 2 kubernetes pods being displayed', async () => {
const pod2Details = screen.getByRole('cell', { name: 'kubepod2 e8129c57 0 container k8s context2 tooltip' });
expect(pod2Details).toBeInTheDocument();
});
+
+test('Expect filter empty screen', async () => {
+ getProvidersInfoMock.mockResolvedValue([provider]);
+ listPodsMock.mockResolvedValue([pod1]);
+ kubernetesListPodsMock.mockResolvedValue([]);
+ window.dispatchEvent(new CustomEvent('provider-lifecycle-change'));
+ window.dispatchEvent(new CustomEvent('extensions-already-started'));
+
+ while (get(providerInfos).length !== 1) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ while (get(podsInfos).length !== 1) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ render(PodsList, { searchTerm: 'No match' });
+ const filterButton = screen.getByRole('button', { name: 'Clear filter' });
+ expect(filterButton).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/pod/PodsList.svelte b/packages/renderer/src/lib/pod/PodsList.svelte
index 893a17679a726..22e25784e3bcf 100644
--- a/packages/renderer/src/lib/pod/PodsList.svelte
+++ b/packages/renderer/src/lib/pod/PodsList.svelte
@@ -11,6 +11,7 @@ import { PodUtils } from './pod-utils';
import type { PodInfo } from '../../../../main/src/plugin/api/pod-info';
import NoContainerEngineEmptyScreen from '../image/NoContainerEngineEmptyScreen.svelte';
import PodEmptyScreen from './PodEmptyScreen.svelte';
+import FilteredEmptyScreen from '../ui/FilteredEmptyScreen.svelte';
import StatusIcon from '../images/StatusIcon.svelte';
import PodIcon from '../images/PodIcon.svelte';
import PodActions from './PodActions.svelte';
@@ -24,7 +25,7 @@ import Checkbox from '../ui/Checkbox.svelte';
import Button from '../ui/Button.svelte';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
-let searchTerm = '';
+export let searchTerm = '';
$: searchPattern.set(searchTerm);
let pods: PodInfoUI[] = [];
@@ -306,7 +307,11 @@ function errorCallback(pod: PodInfoUI, errorMessage: string): void {
{#if $filtered.length === 0 && providerConnections.length === 0}
{:else if $filtered.length === 0}
-
+ {#if searchTerm}
+
+ {:else}
+
+ {/if}
{/if}
diff --git a/packages/renderer/src/lib/ui/EmptyScreen.svelte b/packages/renderer/src/lib/ui/EmptyScreen.svelte
index e32b3ae27b8e6..935793ef768da 100644
--- a/packages/renderer/src/lib/ui/EmptyScreen.svelte
+++ b/packages/renderer/src/lib/ui/EmptyScreen.svelte
@@ -6,6 +6,7 @@ import { onMount } from 'svelte';
export let icon: any;
export let title = 'No title';
export let message = 'Message';
+export let detail = '';
export let commandline = '';
export let hidden = false;
@@ -46,7 +47,10 @@ let copyTextDivElement: HTMLDivElement;
{title}
{message}
- {#if commandline.length > 0}
+ {#if detail}
+ {detail}
+ {/if}
+ {#if commandline}
{commandline}
diff --git a/packages/renderer/src/lib/ui/FilteredEmptyScreen.svelte b/packages/renderer/src/lib/ui/FilteredEmptyScreen.svelte
new file mode 100644
index 0000000000000..a8d1356977f5d
--- /dev/null
+++ b/packages/renderer/src/lib/ui/FilteredEmptyScreen.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/packages/renderer/src/lib/volume/VolumesList.spec.ts b/packages/renderer/src/lib/volume/VolumesList.spec.ts
index 10802a1e4d87c..cd83761802206 100644
--- a/packages/renderer/src/lib/volume/VolumesList.spec.ts
+++ b/packages/renderer/src/lib/volume/VolumesList.spec.ts
@@ -257,3 +257,62 @@ describe('Create volume', () => {
expect(window.location.pathname).toBe('/volumes/create');
});
});
+
+test('Expect filter empty screen', async () => {
+ getProviderInfosMock.mockResolvedValue([
+ {
+ name: 'podman',
+ status: 'started',
+ internalId: 'podman-internal-id',
+ containerConnections: [
+ {
+ name: 'podman-machine-default',
+ status: 'started',
+ },
+ ],
+ },
+ ]);
+
+ listVolumesMock.mockResolvedValue([
+ {
+ Volumes: [
+ {
+ Driver: 'local',
+ Labels: {},
+ Mountpoint: '/var/lib/containers/storage/volumes/fedora/_data',
+ Name: '0052074a2ade930338c00aea982a90e4243e6cf58ba920eb411c388630b8c967',
+ Options: {},
+ Scope: 'local',
+ engineName: 'Podman',
+ engineId: 'podman.Podman Machine',
+ UsageData: { RefCount: 1, Size: -1 },
+ containersUsage: [],
+ },
+ ],
+ },
+ ]);
+
+ window.dispatchEvent(new CustomEvent('extensions-already-started'));
+ window.dispatchEvent(new CustomEvent('provider-lifecycle-change'));
+
+ // ask to fetch the volumes
+ const volumesEventStoreInfo = volumesEventStore.setup();
+
+ await volumesEventStoreInfo.fetch();
+
+ // first call is with listing without details
+ expect(listVolumesMock).toHaveBeenNthCalledWith(1, false);
+
+ // wait store are populated
+ while (get(volumeListInfos).length === 0) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+ while (get(providerInfos).length === 0) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ await waitRender({ searchTerm: 'No match' });
+
+ const filterButton = screen.getByRole('button', { name: 'Clear filter' });
+ expect(filterButton).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/volume/VolumesList.svelte b/packages/renderer/src/lib/volume/VolumesList.svelte
index 3992b76bb8f87..8d6d2b2ea9eb8 100644
--- a/packages/renderer/src/lib/volume/VolumesList.svelte
+++ b/packages/renderer/src/lib/volume/VolumesList.svelte
@@ -10,6 +10,7 @@ import NavPage from '../ui/NavPage.svelte';
import { VolumeUtils } from './volume-utils';
import NoContainerEngineEmptyScreen from '../image/NoContainerEngineEmptyScreen.svelte';
import VolumeEmptyScreen from './VolumeEmptyScreen.svelte';
+import FilteredEmptyScreen from '../ui/FilteredEmptyScreen.svelte';
import VolumeActions from './VolumeActions.svelte';
import VolumeIcon from '../images/VolumeIcon.svelte';
import StatusIcon from '../images/StatusIcon.svelte';
@@ -20,7 +21,7 @@ import Checkbox from '../ui/Checkbox.svelte';
import Button from '../ui/Button.svelte';
import { faPieChart, faPlusCircle, faTrash } from '@fortawesome/free-solid-svg-icons';
-let searchTerm = '';
+export let searchTerm = '';
$: searchPattern.set(searchTerm);
let volumes: VolumeInfoUI[] = [];
@@ -288,7 +289,11 @@ function gotoCreateVolume(): void {
{#if providerConnections.length === 0}
{:else if $filtered.map(volumeInfo => volumeInfo.Volumes).flat().length === 0}
-
+ {#if searchTerm}
+
+ {:else}
+
+ {/if}
{/if}