mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-05-24 10:18:53 +00:00
chore: remove old preferences/extensions page
now extensions are managed with the navbar/extensions Signed-off-by: Florent Benoit <fbenoit@redhat.com>
This commit is contained in:
parent
3e75660d89
commit
2a773db7ca
9 changed files with 0 additions and 739 deletions
|
|
@ -51,9 +51,5 @@ test('Test rendering of the preferences navigation bar and its items', () => {
|
|||
expect(registries).toBeVisible();
|
||||
const authentication = screen.getByRole('link', { name: 'Authentication' });
|
||||
expect(authentication).toBeVisible();
|
||||
const extensions = screen.getByRole('link', { name: 'Extensions' });
|
||||
expect(extensions).toBeVisible();
|
||||
const desktop = screen.getByRole('link', { name: 'Desktop Extensions' });
|
||||
expect(desktop).toBeVisible();
|
||||
// ToDo: adding configuration section/items mocks for preferences, issue #2966
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import type { TinroRouteMeta } from 'tinro';
|
|||
import { CONFIGURATION_DEFAULT_SCOPE } from '../../main/src/plugin/configuration-registry-constants';
|
||||
import SettingsNavItem from './lib/preferences/SettingsNavItem.svelte';
|
||||
import { configurationProperties } from './stores/configurationProperties';
|
||||
import { extensionInfos } from './stores/extensions';
|
||||
|
||||
export let meta: TinroRouteMeta;
|
||||
|
||||
|
|
@ -62,24 +61,6 @@ onMount(async () => {
|
|||
|
||||
<SettingsNavItem title="Kubernetes" href="/preferences/kubernetes-contexts" bind:meta="{meta}" />
|
||||
|
||||
<SettingsNavItem
|
||||
title="Extensions"
|
||||
href="/preferences/extensions"
|
||||
section="{true}"
|
||||
bind:meta="{meta}"
|
||||
bind:expanded="{sectionExpanded['extensionsCatalog']}" />
|
||||
{#if sectionExpanded['extensionsCatalog']}
|
||||
{#each $extensionInfos as extension}
|
||||
<SettingsNavItem
|
||||
title="{extension.displayName}"
|
||||
href="/preferences/extension/{extension.id}"
|
||||
child="{true}"
|
||||
bind:meta="{meta}" />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<SettingsNavItem title="Desktop Extensions" href="/preferences/ddExtensions" bind:meta="{meta}" />
|
||||
|
||||
<!-- Default configuration properties start -->
|
||||
{#each Object.entries(configProperties) as [configSection, configItems]}
|
||||
<SettingsNavItem
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
/**********************************************************************
|
||||
* 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
|
||||
***********************************************************************/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { beforeAll, describe, expect, test } from 'vitest';
|
||||
|
||||
import PreferencesPageDockerExtensions from './PreferencesPageDockerExtensions.svelte';
|
||||
|
||||
// fake the window.events object
|
||||
beforeAll(() => {
|
||||
(window.events as unknown) = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
receive: (_channel: string, func: any) => {
|
||||
func();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const buttonText = 'Install extension from the OCI image';
|
||||
|
||||
describe('PreferencesPageDockerExtensions', () => {
|
||||
test('Expect that textbox is available and button is displayed', async () => {
|
||||
render(PreferencesPageDockerExtensions, {});
|
||||
|
||||
const input = screen.getByRole('textbox', { name: 'OCI Image Name' });
|
||||
expect(input).toBeInTheDocument();
|
||||
const button = screen.getByRole('button', { name: buttonText });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
test('Expect that whitespace does not enable button', async () => {
|
||||
render(PreferencesPageDockerExtensions, { ociImage: ' ' });
|
||||
|
||||
const button = screen.getByRole('button', { name: buttonText });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
test('Expect that valid entry enables button', async () => {
|
||||
render(PreferencesPageDockerExtensions, { ociImage: 'some-valid-image-name' });
|
||||
|
||||
const button = screen.getByRole('button', { name: buttonText });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Input } from '@podman-desktop/ui-svelte';
|
||||
import { afterUpdate } from 'svelte';
|
||||
|
||||
import { contributions } from '../../stores/contribs';
|
||||
import SettingsPage from '../preferences/SettingsPage.svelte';
|
||||
import Button from '../ui/Button.svelte';
|
||||
import ErrorMessage from '../ui/ErrorMessage.svelte';
|
||||
|
||||
export let ociImage: string | undefined = undefined;
|
||||
|
||||
let installInProgress = false;
|
||||
let errorInstall = '';
|
||||
let logs: string[] = [];
|
||||
|
||||
let logElement: HTMLDivElement;
|
||||
|
||||
async function installDDExtensionFromImage() {
|
||||
if (!ociImage) {
|
||||
errorInstall = 'no OCI image provided';
|
||||
return;
|
||||
}
|
||||
|
||||
errorInstall = '';
|
||||
logs.length = 0;
|
||||
installInProgress = true;
|
||||
|
||||
// do a trim on the image name
|
||||
ociImage = ociImage.trim();
|
||||
|
||||
// download image
|
||||
await window.ddExtensionInstall(
|
||||
ociImage,
|
||||
(data: string) => {
|
||||
logs = [...logs, data];
|
||||
},
|
||||
(error: string) => {
|
||||
installInProgress = false;
|
||||
errorInstall = error;
|
||||
},
|
||||
);
|
||||
logs = [...logs, '☑️ installation finished !'];
|
||||
installInProgress = false;
|
||||
ociImage = '';
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
if (logElement.scroll) {
|
||||
logElement.scroll({ top: logElement.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
|
||||
function deleteContribution(extensionName: string) {
|
||||
window.ddExtensionDelete(extensionName);
|
||||
}
|
||||
</script>
|
||||
|
||||
<SettingsPage title="Docker Desktop Extensions">
|
||||
<div class="bg-charcoal-600 rounded-md p-3">
|
||||
<p class="text-xs">There is an ongoing support of Docker Desktop UI extensions from Podman Desktop.</p>
|
||||
<p class="text-xs italic">
|
||||
Not all are guaranteed to work but you can add their OCI Image below to try and load them.
|
||||
</p>
|
||||
<p class="text-xs italic">
|
||||
Example: aquasec/trivy-docker-extension:latest for Trivy extension or redhatdeveloper/openshift-dd-ext:latest for
|
||||
the OpenShift extension.
|
||||
</p>
|
||||
|
||||
<div class="container w-full mt-4 flex-col">
|
||||
<div class="flex flex-col mb-4">
|
||||
<label for="ociImage" class="block mb-2 text-sm font-medium text-gray-400">Image name:</label>
|
||||
<Input
|
||||
name="ociImage"
|
||||
id="ociImage"
|
||||
aria-label="OCI Image Name"
|
||||
bind:value="{ociImage}"
|
||||
placeholder="Name of the Image"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
on:click="{() => installDDExtensionFromImage()}"
|
||||
inProgress="{installInProgress}"
|
||||
disabled="{ociImage === undefined || ociImage.trim() === ''}"
|
||||
icon="{faArrowCircleDown}">
|
||||
Install extension from the OCI image
|
||||
</Button>
|
||||
|
||||
<div
|
||||
class:opacity-0="{logs.length === 0}"
|
||||
bind:this="{logElement}"
|
||||
class="bg-zinc-700 text-gray-300 mt-2 h-16 p-1 overflow-y-auto">
|
||||
{#each logs as log}
|
||||
<p class="font-light text-sm">{log}</p>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<ErrorMessage class="p-1 text-sm" error="{errorInstall}" />
|
||||
</div>
|
||||
|
||||
{#if $contributions.length > 0}
|
||||
<div class="flex border-t-2 border-purple-500 flex-1 flex-col mt-4 p-2">
|
||||
<p>Installed extensions:</p>
|
||||
<div class="grid gap-4 grid-cols-4 pt-4">
|
||||
{#each $contributions as contribution, index}
|
||||
<div class="flex flex-col bg-purple-600 h-[100px]">
|
||||
<div class="flex justify-end flex-wrap">
|
||||
<button
|
||||
class="inline-block text-gray-100 hover:text-gray-700 focus:outline-none rounded-lg text-sm p-1.5"
|
||||
type="button">
|
||||
<i
|
||||
class="fas fa-times"
|
||||
on:click="{() => deleteContribution(contribution.extensionId)}"
|
||||
aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col p-3">
|
||||
<p class="text-sm">{index + 1}. {contribution.extensionId}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</SettingsPage>
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/**********************************************************************
|
||||
* 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
|
||||
***********************************************************************/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { beforeAll, describe, expect, test } from 'vitest';
|
||||
|
||||
import PreferencesExtensionList from './PreferencesExtensionList.svelte';
|
||||
|
||||
// fake the window.events object
|
||||
beforeAll(() => {
|
||||
(window.events as unknown) = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
receive: (_channel: string, func: any) => {
|
||||
func();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const buttonText = 'Install extension from the OCI image';
|
||||
|
||||
describe('PreferencesExtensionList', () => {
|
||||
test('Expect that textbox is available and button is displayed', async () => {
|
||||
render(PreferencesExtensionList, {});
|
||||
|
||||
const entry = screen.getByPlaceholderText('Name of the Image');
|
||||
expect(entry).toBeInTheDocument();
|
||||
const button = screen.getByRole('button', { name: buttonText });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
test('Expect that whitespace does not enable button', async () => {
|
||||
render(PreferencesExtensionList, { ociImage: ' ' });
|
||||
|
||||
const button = screen.getByRole('button', { name: buttonText });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
test('Expect that valid entry enables button', async () => {
|
||||
render(PreferencesExtensionList, { ociImage: 'some-valid-image' });
|
||||
|
||||
const button = screen.getByRole('button', { name: buttonText });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { faArrowCircleDown, faPlay, faStop, faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Input } from '@podman-desktop/ui-svelte';
|
||||
import { afterUpdate } from 'svelte';
|
||||
import Fa from 'svelte-fa';
|
||||
|
||||
import type { ExtensionInfo } from '../../../../main/src/plugin/api/extension-info';
|
||||
import { extensionInfos } from '../../stores/extensions';
|
||||
import FeaturedExtensions from '../featured/FeaturedExtensions.svelte';
|
||||
import SettingsPage from '../preferences/SettingsPage.svelte';
|
||||
import Button from '../ui/Button.svelte';
|
||||
import ErrorMessage from '../ui/ErrorMessage.svelte';
|
||||
import ExtensionStatus from '../ui/ExtensionStatus.svelte';
|
||||
import ExtensionIcon from './ExtensionIcon.svelte';
|
||||
|
||||
export let ociImage: string | undefined = undefined;
|
||||
|
||||
let installInProgress = false;
|
||||
let errorInstall = '';
|
||||
let logs: string[] = [];
|
||||
|
||||
let logElement: HTMLDivElement;
|
||||
|
||||
const buttonClass =
|
||||
'm-0.5 text-gray-400 hover:bg-charcoal-600 hover:text-violet-600 font-medium rounded-full inline-flex items-center px-2 py-2 text-center';
|
||||
|
||||
async function installExtensionFromImage() {
|
||||
if (!ociImage) {
|
||||
errorInstall = 'no OCI image provided';
|
||||
return;
|
||||
}
|
||||
|
||||
errorInstall = '';
|
||||
logs.length = 0;
|
||||
installInProgress = true;
|
||||
|
||||
// do a trim on the image name
|
||||
ociImage = ociImage.trim();
|
||||
|
||||
// download image
|
||||
await window.extensionInstallFromImage(
|
||||
ociImage,
|
||||
(data: string) => {
|
||||
logs = [...logs, data];
|
||||
},
|
||||
(error: string) => {
|
||||
console.log('got an error', error);
|
||||
installInProgress = false;
|
||||
errorInstall = error;
|
||||
},
|
||||
);
|
||||
logs = [...logs, '☑️ installation finished !'];
|
||||
installInProgress = false;
|
||||
ociImage = '';
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
if (logElement.scroll) {
|
||||
logElement.scroll({ top: logElement.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
|
||||
async function stopExtension(extension: ExtensionInfo) {
|
||||
await window.stopExtension(extension.id);
|
||||
}
|
||||
async function startExtension(extension: ExtensionInfo) {
|
||||
await window.startExtension(extension.id);
|
||||
}
|
||||
|
||||
async function removeExtension(extension: ExtensionInfo) {
|
||||
await window.removeExtension(extension.id);
|
||||
}
|
||||
|
||||
async function updateExtension(extension: ExtensionInfo, ociUri: string) {
|
||||
await window.updateExtension(extension.id, ociUri);
|
||||
}
|
||||
</script>
|
||||
|
||||
<SettingsPage title="Extensions">
|
||||
<div class="bg-charcoal-600 rounded-md p-3">
|
||||
<div class="bg-charcoal-700 mb-4 rounded-md p-3">
|
||||
<FeaturedExtensions />
|
||||
</div>
|
||||
|
||||
<div class="bg-charcoal-700 mt-5 rounded-md p-3" role="region" aria-label="OCI image installation box">
|
||||
<h1 class="text-lg mb-2">Install a new extension from OCI Image</h1>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div
|
||||
class="flex flex-row mb-2 w-full space-x-4 items-center"
|
||||
role="region"
|
||||
aria-label="Install Extension From OCI">
|
||||
<Input
|
||||
name="ociImage"
|
||||
id="ociImage"
|
||||
aria-label="OCI Image Name"
|
||||
bind:value="{ociImage}"
|
||||
placeholder="Name of the Image"
|
||||
required />
|
||||
|
||||
<Button
|
||||
on:click="{() => installExtensionFromImage()}"
|
||||
disabled="{ociImage === undefined || ociImage.trim() === ''}"
|
||||
inProgress="{installInProgress}"
|
||||
icon="{faArrowCircleDown}">
|
||||
Install extension from the OCI image
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="container w-full flex-col">
|
||||
<div
|
||||
class:opacity-0="{logs.length === 0}"
|
||||
bind:this="{logElement}"
|
||||
class="bg-zinc-700 text-gray-300 mt-4 mb-3 p-1 h-16 overflow-y-auto">
|
||||
{#each logs as log}
|
||||
<p class="font-light text-sm">{log}</p>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<ErrorMessage error="{errorInstall}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-charcoal-600 mt-5 rounded-md p-3">
|
||||
<table class="min-w-full" aria-label="Installed Extensions">
|
||||
<tbody>
|
||||
{#each $extensionInfos as extension}
|
||||
<tr class="border-y border-gray-900" aria-label="{extension.name}">
|
||||
<td class="px-6 py-2" aria-label="Extension Details">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center h-10 w-10">
|
||||
<ExtensionIcon extension="{extension}" />
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="flex flex-row" aria-label="Extension Details">
|
||||
<div class="text-sm text-gray-300">
|
||||
{extension.displayName}
|
||||
{extension.removable ? '(user)' : '(default extension)'}
|
||||
<span class="text-xs font-extra-light text-gray-900">v{extension.version}</span>
|
||||
{#if extension.update}
|
||||
{@const extensionUpdate = extension.update}
|
||||
<button
|
||||
class="mx-2 px-2 text-xs font-medium text-center text-white bg-violet-600 rounded-sm hover:bg-dustypurple-800 focus:ring-2 focus:outline-none focus:ring-dustypurple-700"
|
||||
title="Update to {extension.update.version}"
|
||||
aria-label="Extension Action Update"
|
||||
on:click="{() => updateExtension(extension, extensionUpdate.ociUri)}">
|
||||
Update</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row" aria-label="Extension Description">
|
||||
<div class="text-sm text-gray-700 italic">
|
||||
{extension.description}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<ExtensionStatus status="{extension.state}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-2 whitespace-nowrap" aria-label="Extension Actions">
|
||||
<div class="flex flex-row justify-end">
|
||||
<button
|
||||
title="Start extension"
|
||||
aria-label="Extension Action Start"
|
||||
on:click="{() => startExtension(extension)}"
|
||||
class="{buttonClass}"
|
||||
class:hidden="{extension.state !== 'stopped'}"><Fa class="h-4 w-4" icon="{faPlay}" /></button>
|
||||
<button
|
||||
title="Stop extension"
|
||||
aria-label="Extension Action Stop"
|
||||
class="{buttonClass}"
|
||||
on:click="{() => stopExtension(extension)}"
|
||||
hidden
|
||||
class:hidden="{extension.state !== 'started'}"><Fa class="h-4 w-4" icon="{faStop}" /></button>
|
||||
|
||||
{#if extension.removable}
|
||||
{#if extension.state === 'stopped'}
|
||||
<button
|
||||
title="Remove extension"
|
||||
aria-label="Extension Action Remove"
|
||||
class="{buttonClass}"
|
||||
on:click="{() => removeExtension(extension)}"><Fa class="h-4 w-4" icon="{faTrash}" /></button>
|
||||
{:else}
|
||||
<div
|
||||
class="m-0.5 text-gray-900 rounded-full inline-flex items-center px-2 py-2 text-center"
|
||||
title="Running extension cannot be removed.">
|
||||
<Fa class="h-4 w-4" icon="{faTrash}" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div
|
||||
class="m-0.5 text-gray-900 rounded-full inline-flex items-center px-2 py-2 text-center"
|
||||
title="Default extension. Cannot be removed.">
|
||||
<Fa class="h-4 w-4" icon="{faTrash}" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div></SettingsPage>
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
/**********************************************************************
|
||||
* 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
|
||||
***********************************************************************/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
|
||||
import { render, screen, waitForElementToBeRemoved } from '@testing-library/svelte';
|
||||
import { beforeAll, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { extensionInfos } from '../../stores/extensions';
|
||||
import PreferencesExtensionRendering from './PreferencesExtensionRendering.svelte';
|
||||
|
||||
const getOnboardingMock = vi.fn();
|
||||
// fake the window.events object
|
||||
beforeAll(() => {
|
||||
(window as any).getOnboarding = getOnboardingMock;
|
||||
(window.events as unknown) = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
receive: (_channel: string, func: any) => {
|
||||
func();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// extension page needs a mock extension to work correctly
|
||||
function setup(state: string): string {
|
||||
const extensionInfo = {
|
||||
id: 'test',
|
||||
name: 'test',
|
||||
version: '1.0',
|
||||
displayName: 'Test extension',
|
||||
state: state,
|
||||
description: 'An extension for testing',
|
||||
path: '/test',
|
||||
publisher: 'podman-desktop',
|
||||
readme: '',
|
||||
removable: true,
|
||||
};
|
||||
|
||||
extensionInfos.set([extensionInfo]);
|
||||
return extensionInfo.id;
|
||||
}
|
||||
describe('PreferencesExtensionRendering', () => {
|
||||
test('Expect that the buttons have the correct state when an extension is stopped', async () => {
|
||||
const id = setup('stopped');
|
||||
render(PreferencesExtensionRendering, { extensionId: id });
|
||||
|
||||
const start = screen.getByRole('button', { name: 'Enable' });
|
||||
expect(start).toBeInTheDocument();
|
||||
expect(start).toBeEnabled();
|
||||
const stop = screen.getByRole('button', { name: 'Disable' });
|
||||
expect(stop).toBeInTheDocument();
|
||||
expect(stop).toBeDisabled();
|
||||
const remove = screen.getByRole('button', { name: 'Remove' });
|
||||
expect(remove).toBeInTheDocument();
|
||||
expect(remove).toBeEnabled();
|
||||
});
|
||||
|
||||
test('Expect that the buttons have the correct state when an extension is started', async () => {
|
||||
const id = setup('started');
|
||||
render(PreferencesExtensionRendering, { extensionId: id });
|
||||
|
||||
const start = screen.getByRole('button', { name: 'Enable' });
|
||||
expect(start).toBeInTheDocument();
|
||||
expect(start).toBeDisabled();
|
||||
const stop = screen.getByRole('button', { name: 'Disable' });
|
||||
expect(stop).toBeInTheDocument();
|
||||
expect(stop).toBeEnabled();
|
||||
const remove = screen.getByRole('button', { name: 'Remove' });
|
||||
expect(remove).toBeInTheDocument();
|
||||
expect(remove).toBeDisabled();
|
||||
});
|
||||
|
||||
test('Expect that the buttons have the correct state when an extension is starting', async () => {
|
||||
const id = setup('starting');
|
||||
render(PreferencesExtensionRendering, { extensionId: id });
|
||||
|
||||
const start = screen.getByRole('button', { name: 'Enable' });
|
||||
expect(start).toBeInTheDocument();
|
||||
expect(start).toBeDisabled();
|
||||
const stop = screen.getByRole('button', { name: 'Disable' });
|
||||
expect(stop).toBeInTheDocument();
|
||||
expect(stop).toBeDisabled();
|
||||
const remove = screen.getByRole('button', { name: 'Remove' });
|
||||
expect(remove).toBeInTheDocument();
|
||||
expect(remove).toBeDisabled();
|
||||
});
|
||||
|
||||
test('Expect that the buttons have the correct state when an extension is stopping', async () => {
|
||||
const id = setup('stopping');
|
||||
render(PreferencesExtensionRendering, { extensionId: id });
|
||||
|
||||
const start = screen.getByRole('button', { name: 'Enable' });
|
||||
expect(start).toBeInTheDocument();
|
||||
expect(start).toBeDisabled();
|
||||
const stop = screen.getByRole('button', { name: 'Disable' });
|
||||
expect(stop).toBeInTheDocument();
|
||||
expect(stop).toBeDisabled();
|
||||
const remove = screen.getByRole('button', { name: 'Remove' });
|
||||
expect(remove).toBeInTheDocument();
|
||||
expect(remove).toBeDisabled();
|
||||
});
|
||||
|
||||
test('Expect empty screen if there is no matching extension (could be during providerInfos is loading)', async () => {
|
||||
// clear store
|
||||
extensionInfos.set([]);
|
||||
|
||||
// start without extension in the stores, should be empty
|
||||
render(PreferencesExtensionRendering, { extensionId: 'test' });
|
||||
|
||||
// check empty page is displayed if we do not have matching of the extension
|
||||
const emptyHeading = screen.getByRole('heading', { name: 'Extension not found', level: 1 });
|
||||
expect(emptyHeading).toBeInTheDocument();
|
||||
|
||||
// now register the extension in the store
|
||||
setup('started');
|
||||
|
||||
// wait empty page disappear
|
||||
await waitForElementToBeRemoved(() => screen.queryByRole('heading', { name: 'Extension not found', level: 1 }));
|
||||
|
||||
// now check disable button is displayed as extension is started
|
||||
const start = screen.getByRole('button', { name: 'Disable' });
|
||||
expect(start).toBeInTheDocument();
|
||||
expect(start).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { faPlay, faPuzzlePiece, faStop, faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import type { ExtensionInfo } from '../../../../main/src/plugin/api/extension-info';
|
||||
import Route from '../../Route.svelte';
|
||||
import { extensionInfos } from '../../stores/extensions';
|
||||
import Button from '../ui/Button.svelte';
|
||||
import EmptyScreen from '../ui/EmptyScreen.svelte';
|
||||
import ExtensionStatus from '../ui/ExtensionStatus.svelte';
|
||||
import SettingsPage from './SettingsPage.svelte';
|
||||
|
||||
export let extensionId: string | undefined = undefined;
|
||||
|
||||
let extensionInfo: ExtensionInfo | undefined;
|
||||
|
||||
$: extensionInfo = $extensionInfos.find(extension => extension.id === extensionId);
|
||||
|
||||
async function stopExtension() {
|
||||
if (extensionInfo) {
|
||||
await window.stopExtension(extensionInfo.id);
|
||||
}
|
||||
}
|
||||
async function startExtension() {
|
||||
if (extensionInfo) {
|
||||
await window.startExtension(extensionInfo.id);
|
||||
}
|
||||
}
|
||||
async function removeExtension() {
|
||||
if (extensionInfo) {
|
||||
window.location.href = '#/preferences/extensions';
|
||||
await window.removeExtension(extensionInfo.id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !extensionInfo}
|
||||
<EmptyScreen title="Extension not found" icon="{faPuzzlePiece}" message="No extension found with id {extensionId}" />
|
||||
{:else}
|
||||
<SettingsPage title="{extensionInfo.displayName} Extension">
|
||||
<span slot="subtitle">
|
||||
{extensionInfo.description}
|
||||
</span>
|
||||
<div class="flex flex-col bg-charcoal-600 rounded-md p-3">
|
||||
{#if extensionInfo}
|
||||
<Route path="/*" breadcrumb="{extensionInfo.displayName}">
|
||||
<!-- Manage lifecycle-->
|
||||
<div class="flex pb-2">
|
||||
<div class="pr-2">Status</div>
|
||||
<ExtensionStatus status="{extensionInfo.state}" />
|
||||
</div>
|
||||
|
||||
<div class="py-2 flex flex-row items-center">
|
||||
<!-- start is enabled only when stopped or failed -->
|
||||
<div class="px-2 text-sm italic text-gray-700">
|
||||
<Button
|
||||
disabled="{extensionInfo.state !== 'stopped' && extensionInfo.state !== 'failed'}"
|
||||
on:click="{() => startExtension()}"
|
||||
icon="{faPlay}">
|
||||
Enable
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- stop is enabled only when started -->
|
||||
<div class="px-2 text-sm italic text-gray-700">
|
||||
<Button disabled="{extensionInfo.state !== 'started'}" on:click="{() => stopExtension()}" icon="{faStop}">
|
||||
Disable
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- delete is enabled only when stopped or failed -->
|
||||
{#if extensionInfo.removable}
|
||||
<div class="px-2 text-sm italic text-gray-700">
|
||||
<Button
|
||||
disabled="{extensionInfo.state !== 'stopped' && extensionInfo.state !== 'failed'}"
|
||||
on:click="{() => removeExtension()}"
|
||||
icon="{faTrash}">
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-gray-900 items-center px-2 text-sm">Default extension, cannot be removed</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if extensionInfo.error}
|
||||
<div class="flex flex-col">
|
||||
<div class="py-2">Extension error: {extensionInfo.error.message}</div>
|
||||
{#if extensionInfo.error.stack}
|
||||
<div class="py-2">Stack trace</div>
|
||||
<div class="py-2">{extensionInfo.error.stack}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Route>
|
||||
{/if}
|
||||
</div></SettingsPage>
|
||||
{/if}
|
||||
|
|
@ -6,13 +6,10 @@ import PreferencesContainerConnectionEdit from '/@/lib/preferences/PreferencesCo
|
|||
import type { IConfigurationPropertyRecordedSchema } from '../../../../main/src/plugin/configuration-registry';
|
||||
import Route from '../../Route.svelte';
|
||||
import { configurationProperties } from '../../stores/configurationProperties';
|
||||
import PreferencesPageDockerExtensions from '../docker-extension/PreferencesPageDockerExtensions.svelte';
|
||||
import Onboarding from '../onboarding/Onboarding.svelte';
|
||||
import PreferencesAuthenticationProvidersRendering from './PreferencesAuthenticationProvidersRendering.svelte';
|
||||
import PreferencesCliToolsRendering from './PreferencesCliToolsRendering.svelte';
|
||||
import PreferencesContainerConnectionRendering from './PreferencesContainerConnectionRendering.svelte';
|
||||
import PreferencesExtensionList from './PreferencesExtensionList.svelte';
|
||||
import PreferencesExtensionRendering from './PreferencesExtensionRendering.svelte';
|
||||
import PreferencesKubernetesConnectionRendering from './PreferencesKubernetesConnectionRendering.svelte';
|
||||
import PreferencesKubernetesContextsRendering from './PreferencesKubernetesContextsRendering.svelte';
|
||||
import PreferencesProviderRendering from './PreferencesProviderRendering.svelte';
|
||||
|
|
@ -52,12 +49,6 @@ onMount(async () => {
|
|||
<Route path="/default/:key/*" breadcrumb="Preferences" let:meta>
|
||||
<PreferencesRendering key="{meta.params.key}" properties="{properties}" />
|
||||
</Route>
|
||||
<Route path="/ddExtensions" breadcrumb="Docker Desktop Extensions">
|
||||
<PreferencesPageDockerExtensions />
|
||||
</Route>
|
||||
<Route path="/extension/:extensionId/*" breadcrumb="Extensions" let:meta>
|
||||
<PreferencesExtensionRendering extensionId="{meta.params.extensionId}" />
|
||||
</Route>
|
||||
<Route path="/provider/:providerInternalId/*" breadcrumb="Resources" let:meta navigationHint="details">
|
||||
<PreferencesProviderRendering providerInternalId="{meta.params.providerInternalId}" properties="{properties}" />
|
||||
</Route>
|
||||
|
|
@ -85,9 +76,6 @@ onMount(async () => {
|
|||
<Route path="/proxies" breadcrumb="Proxy">
|
||||
<PreferencesProxiesRendering />
|
||||
</Route>
|
||||
<Route path="/extensions" breadcrumb="Extensions">
|
||||
<PreferencesExtensionList />
|
||||
</Route>
|
||||
|
||||
<Route path="/onboarding/:extensionId" breadcrumb="Extension Onboarding" let:meta navigationHint="details">
|
||||
<Onboarding extensionIds="{[meta.params.extensionId]}" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue