diff --git a/packages/extension/src/types/disposable.ts b/packages/common/src/types/disposable.ts similarity index 96% rename from packages/extension/src/types/disposable.ts rename to packages/common/src/types/disposable.ts index 668211ce..46f580df 100644 --- a/packages/extension/src/types/disposable.ts +++ b/packages/common/src/types/disposable.ts @@ -15,6 +15,7 @@ * * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ +export const IDisposable = Symbol.for('IDisposable'); export interface IDisposable { dispose(): void; diff --git a/packages/extension/src/manager/contexts-states-dispatcher.spec.ts b/packages/extension/src/manager/contexts-states-dispatcher.spec.ts index c40f4937..40dfd379 100644 --- a/packages/extension/src/manager/contexts-states-dispatcher.spec.ts +++ b/packages/extension/src/manager/contexts-states-dispatcher.spec.ts @@ -18,7 +18,7 @@ import { beforeAll, beforeEach, expect, test, vi } from 'vitest'; -import type { IDisposable } from '/@/types/disposable.js'; +import type { IDisposable } from '/@common/types/disposable.js'; import type { ContextPermission } from '/@common/model/kubernetes-contexts-permissions.js'; import type { ContextHealthState } from './context-health-checker.js'; @@ -31,7 +31,7 @@ import type { RpcExtension } from '/@common/rpc/rpc.js'; import { CONTEXTS_HEALTHS, CONTEXTS_PERMISSIONS, RESOURCES_COUNT, UPDATE_RESOURCE } from '/@common/channels.js'; import type { ExtensionContext, TelemetryLogger } from '@podman-desktop/api'; import type { Container } from 'inversify'; -import { InversifyBinding } from '../inject/inversify-binding.js'; +import { InversifyBinding } from '/@/inject/inversify-binding.js'; let container: Container; const contextsManagerMock: ContextsManager = { diff --git a/packages/extension/src/types/emitter.ts b/packages/extension/src/types/emitter.ts index 48b49a41..de60eb79 100644 --- a/packages/extension/src/types/emitter.ts +++ b/packages/extension/src/types/emitter.ts @@ -18,7 +18,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-function-type */ -import type { IDisposable } from './disposable.js'; +import type { IDisposable } from '/@common/types/disposable.js'; export type DisposableGroup = { push(disposable: IDisposable): void } | { add(disposable: IDisposable): void }; diff --git a/packages/webview/src/Main.svelte b/packages/webview/src/Main.svelte index ed8f5649..fcf5d036 100644 --- a/packages/webview/src/Main.svelte +++ b/packages/webview/src/Main.svelte @@ -1,6 +1,30 @@ -Kubernetes Dashboard +{#if mainContext} + +{/if} diff --git a/packages/webview/src/MainContextAware.svelte b/packages/webview/src/MainContextAware.svelte new file mode 100644 index 00000000..cbe4238a --- /dev/null +++ b/packages/webview/src/MainContextAware.svelte @@ -0,0 +1,28 @@ + + +{#if initialized} + Kubernetes Dashboard + +
+ +
+{/if} diff --git a/packages/webview/src/component/ResourcesCount.svelte b/packages/webview/src/component/ResourcesCount.svelte new file mode 100644 index 00000000..b488e397 --- /dev/null +++ b/packages/webview/src/component/ResourcesCount.svelte @@ -0,0 +1,14 @@ + + +{#if resourcesCount.data?.counts} + +{/if} diff --git a/packages/webview/src/inject/inversify-binding.ts b/packages/webview/src/inject/inversify-binding.ts new file mode 100644 index 00000000..b4a261f6 --- /dev/null +++ b/packages/webview/src/inject/inversify-binding.ts @@ -0,0 +1,48 @@ +/********************************************************************** + * Copyright (C) 2025 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 'reflect-metadata'; + +import type { WebviewApi } from '@podman-desktop/webview-api'; +import { Container } from 'inversify'; + +import { RpcBrowser } from '/@common/rpc/rpc'; + +import { statesModule } from '/@/state/state-module'; + +export class InversifyBinding { + #container: Container | undefined; + + #rpcBrowser: RpcBrowser; + #webviewApi: WebviewApi; + + constructor(rpcBrowser: RpcBrowser, webviewApi: WebviewApi) { + this.#rpcBrowser = rpcBrowser; + this.#webviewApi = webviewApi; + } + + public async initBindings(): Promise { + this.#container = new Container(); + this.#container.bind(RpcBrowser).toConstantValue(this.#rpcBrowser); + this.#container.bind('WebviewApi').toConstantValue(this.#webviewApi); + + await this.#container.load(statesModule); + + return this.#container; + } +} diff --git a/packages/webview/src/main.ts b/packages/webview/src/main.ts new file mode 100644 index 00000000..0e259551 --- /dev/null +++ b/packages/webview/src/main.ts @@ -0,0 +1,65 @@ +/********************************************************************** + * Copyright (C) 2025 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 { RpcBrowser } from '/@common/rpc/rpc'; + +import { InversifyBinding } from './inject/inversify-binding'; +import { IDisposable } from '/@common/types/disposable'; +import { States } from './state/states'; +import { StateObject } from './state/util/state-object.svelte'; + +export interface MainContext { + states: States; +} + +export class Main implements IDisposable { + private disposables: IDisposable[] = []; + + async init(): Promise { + const webViewApi = acquirePodmanDesktopApi(); + + const rpcBrowser: RpcBrowser = new RpcBrowser(window, webViewApi); + + const inversifyBinding = new InversifyBinding(rpcBrowser, webViewApi); + const container = await inversifyBinding.initBindings(); + + // Grab all state object instances + const stateObjectInstances = container.getAll>(StateObject); + + // Init all state object instances + for (const stateObjectInstance of stateObjectInstances) { + await stateObjectInstance.init(); + } + + // Register all disposables + const disposables = await container.getAllAsync(IDisposable); + this.disposables.push(...disposables); + + const mainContext: MainContext = { + states: await container.getAsync(States), + }; + + return mainContext; + } + + dispose(): void { + for (const disposable of this.disposables) { + disposable.dispose(); + } + } +} diff --git a/packages/webview/src/state/resources-count.svelte.ts b/packages/webview/src/state/resources-count.svelte.ts new file mode 100644 index 00000000..9a53ca28 --- /dev/null +++ b/packages/webview/src/state/resources-count.svelte.ts @@ -0,0 +1,40 @@ +/********************************************************************** + * Copyright (C) 2025 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 { inject, injectable } from 'inversify'; + +import { RESOURCES_COUNT } from '/@common/channels'; +import { RpcBrowser } from '/@common/rpc/rpc'; + +import { AbsStateObjectImpl, type StateObject } from './util/state-object.svelte'; +import type { ResourcesCountInfo } from '/@common/model/resources-count-info'; + +// Define a state for the ResourcesCountInfo +@injectable() +export class StateResourcesCountInfo + extends AbsStateObjectImpl + implements StateObject +{ + constructor(@inject(RpcBrowser) rpcBrowser: RpcBrowser) { + super(rpcBrowser); + } + + async init(): Promise { + await this.initChannel(RESOURCES_COUNT); + } +} diff --git a/packages/webview/src/state/state-module.ts b/packages/webview/src/state/state-module.ts new file mode 100644 index 00000000..c7374423 --- /dev/null +++ b/packages/webview/src/state/state-module.ts @@ -0,0 +1,34 @@ +/********************************************************************** + * Copyright (C) 2025 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 { ContainerModule } from 'inversify'; + +import { States } from './states'; +import { StateObject } from './util/state-object.svelte'; +import { IDisposable } from '/@common/types/disposable'; +import { StateResourcesCountInfo } from './resources-count.svelte'; + +const statesModule = new ContainerModule(options => { + options.bind(States).toSelf().inSingletonScope(); + + options.bind(StateResourcesCountInfo).toSelf().inSingletonScope(); + options.bind(StateObject).toService(StateResourcesCountInfo); + options.bind(IDisposable).toService(StateResourcesCountInfo); +}); + +export { statesModule }; diff --git a/packages/webview/src/state/states.ts b/packages/webview/src/state/states.ts new file mode 100644 index 00000000..418c47ac --- /dev/null +++ b/packages/webview/src/state/states.ts @@ -0,0 +1,30 @@ +/********************************************************************** + * Copyright (C) 2025 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 { inject, injectable } from 'inversify'; +import { StateResourcesCountInfo } from './resources-count.svelte'; + +@injectable() +export class States { + @inject(StateResourcesCountInfo) + private _stateResourcesCountInfoUI: StateResourcesCountInfo; + + get stateResourcesCountInfoUI(): StateResourcesCountInfo { + return this._stateResourcesCountInfoUI; + } +} diff --git a/packages/webview/src/state/util/state-object.svelte.ts b/packages/webview/src/state/util/state-object.svelte.ts new file mode 100644 index 00000000..5dcf189a --- /dev/null +++ b/packages/webview/src/state/util/state-object.svelte.ts @@ -0,0 +1,56 @@ +/********************************************************************** + * Copyright (C) 2025 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 type { RpcBrowser, RpcChannel } from '/@common/rpc/rpc'; +import type { IDisposable } from '/@common/types/disposable'; + +export const StateObject = Symbol.for('StateObject'); +export interface StateObject extends IDisposable { + get data(): T | undefined; + init(): Promise; +} + +// Allow to receive event for a given object +export abstract class AbsStateObjectImpl implements StateObject { + #data = $state<{ value: T | undefined }>({ value: undefined }); + + #rpcBrowser: RpcBrowser; + + #disposable: IDisposable | undefined; + + constructor(rpcBrowser: RpcBrowser) { + this.#rpcBrowser = rpcBrowser; + this.#data.value = undefined; + } + + get data(): T | undefined { + return this.#data.value; + } + + protected async initChannel(channel: RpcChannel): Promise { + this.#disposable = this.#rpcBrowser.on(channel, value => { + this.#data.value = value; + }); + } + + dispose(): void { + this.#disposable?.dispose(); + } + + abstract init(): Promise; +}