Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist
output
packages/extension/media

/.vs
15 changes: 9 additions & 6 deletions packages/channels/src/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import { createRpcChannel } from '@kubernetes-dashboard/rpc';
import type {
ContextsHealthsInfo,
ContextsPermissionsInfo,
ResourcesCountInfo,
} from '@podman-desktop/kubernetes-dashboard-extension-api';
import type { ContextsApi } from './interface/contexts-api';
import type { NavigationApi } from './interface/navigation-api';
import type { PodLogsApi } from './interface/pod-logs-api';
Expand All @@ -33,13 +39,8 @@ import type { PodTerminalChunk } from './model/pod-terminal-chunk';
import type { PortForwardsInfo } from './model/port-forward-info';
import type { ResourceDetailsInfo } from './model/resource-details-info';
import type { ResourceEventsInfo } from './model/resource-events-info';
import type { TerminalSettings } from './model/terminal-settings';
import type { UpdateResourceInfo } from './model/update-resource-info';
import { createRpcChannel } from '@kubernetes-dashboard/rpc';
import type {
ContextsHealthsInfo,
ContextsPermissionsInfo,
ResourcesCountInfo,
} from '@podman-desktop/kubernetes-dashboard-extension-api';

// RPC channels (used by the webview to send requests to the extension)
export const API_CONTEXTS = createRpcChannel<ContextsApi>('ContextsApi');
Expand Down Expand Up @@ -68,3 +69,5 @@ export const POD_LOGS = createRpcChannel<PodLogsChunk>('PodLogs');

export const API_POD_TERMINALS = createRpcChannel<PodTerminalsApi>('PodTerminalsApi');
export const POD_TERMINAL_DATA = createRpcChannel<PodTerminalChunk>('PodTerminalData');

export const TERMINAL_SETTINGS = createRpcChannel<TerminalSettings>('TerminalSettings');
10 changes: 9 additions & 1 deletion packages/channels/src/interface/pod-logs-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@

export const PodLogsApi = Symbol.for('PodLogsApi');

export type PodLogsOptions = {
stream?: boolean;
previous?: boolean;
tailLines?: number;
sinceSeconds?: number;
timestamps?: boolean;
};

export interface PodLogsApi {
streamPodLogs(podName: string, namespace: string, containerName: string): Promise<void>;
streamPodLogs(podName: string, namespace: string, containerName: string, options?: PodLogsOptions): Promise<void>;
stopStreamPodLogs(podName: string, namespace: string, containerName: string): Promise<void>;
}
4 changes: 3 additions & 1 deletion packages/channels/src/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ export * from './kubernetes-providers-info';
export * from './kubernetes-troubleshooting';
export * from './openshift-types';
export * from './pod-logs-chunk';
export * from './pod-logs-options';
export * from './pod-terminal-chunk';
export * from './port-forward-info';
export * from './port-forward';
export * from './port-forward-info';
export * from './resource-details-info';
export * from './resource-details-options';
export * from './resource-events-info';
export * from './resource-events-options';
export * from './target-ref';
export * from './terminal-settings';
export * from './update-resource-info';
export * from './update-resource-options';
25 changes: 25 additions & 0 deletions packages/channels/src/model/pod-logs-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**********************************************************************
* 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
***********************************************************************/

export interface PodLogsOptions {
stream?: boolean;
previous?: boolean;
tailLines?: number;
sinceSeconds?: number;
timestamps?: boolean;
}
23 changes: 23 additions & 0 deletions packages/channels/src/model/terminal-settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**********************************************************************
* 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
***********************************************************************/

export interface TerminalSettings {
fontSize: number;
lineHeight: number;
scrollback: number;
}
18 changes: 11 additions & 7 deletions packages/extension/src/dispatcher/_dispatcher-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@

import { ContainerModule } from 'inversify';
import { ActiveResourcesCountDispatcher } from './active-resources-count-dispatcher';
import { AvailableContextsDispatcher } from './available-contexts-dispatcher';
import { ContextsHealthsDispatcher } from './contexts-healths-dispatcher';
import { ContextsPermissionsDispatcher } from './contexts-permissions-dispatcher';
import { ResourcesCountDispatcher } from './resources-count-dispatcher';
import { DispatcherObject } from './util/dispatcher-object';
import { CurrentContextDispatcher } from './current-context-dispatcher';
import { UpdateResourceDispatcher } from './update-resource-dispatcher';
import { ResourceDetailsDispatcher } from './resource-details-dispatcher';
import { ResourceEventsDispatcher } from './resource-events-dispatcher';
import { PortForwardsDispatcher } from './port-forwards-dispatcher';
import { TerminalSettingsDispatcher } from './terminal-settings-dispatcher';
import { EndpointsDispatcher } from './endpoints-dispatcher';
import { AvailableContextsDispatcher } from './available-contexts-dispatcher';
import { KubernetesProvidersDispatcher } from './kubernetes-providers-dispatcher';
import { PortForwardsDispatcher } from './port-forwards-dispatcher';
import { ResourceDetailsDispatcher } from './resource-details-dispatcher';
import { ResourceEventsDispatcher } from './resource-events-dispatcher';
import { ResourcesCountDispatcher } from './resources-count-dispatcher';
import { UpdateResourceDispatcher } from './update-resource-dispatcher';
import { DispatcherObject } from './util/dispatcher-object';

const dispatchersModule = new ContainerModule(options => {
options.bind<ActiveResourcesCountDispatcher>(ActiveResourcesCountDispatcher).toSelf().inSingletonScope();
Expand All @@ -50,6 +51,9 @@ const dispatchersModule = new ContainerModule(options => {
options.bind<AvailableContextsDispatcher>(AvailableContextsDispatcher).toSelf().inSingletonScope();
options.bind(DispatcherObject).toService(AvailableContextsDispatcher);

options.bind<TerminalSettingsDispatcher>(TerminalSettingsDispatcher).toSelf().inSingletonScope();
options.bind(DispatcherObject).toService(TerminalSettingsDispatcher);

options.bind<UpdateResourceDispatcher>(UpdateResourceDispatcher).toSelf().inSingletonScope();
options.bind(DispatcherObject).toService(UpdateResourceDispatcher);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**********************************************************************
* 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 { TERMINAL_SETTINGS, type TerminalSettings } from '@kubernetes-dashboard/channels';
import { configuration } from '@podman-desktop/api';
import { injectable } from 'inversify';
import type { DispatcherObject } from '/@/dispatcher/util/dispatcher-object';
import { AbsDispatcherObjectImpl } from '/@/dispatcher/util/dispatcher-object';

@injectable()
export class TerminalSettingsDispatcher
extends AbsDispatcherObjectImpl<void, TerminalSettings>
implements DispatcherObject<void>
{
constructor() {
super(TERMINAL_SETTINGS);
}

getData(): TerminalSettings {
//TODO probably would be nice to expose these keys in the podman-desktop api spec
const terminalSettings = configuration.getConfiguration('terminal');
const fontSize = terminalSettings.get<number>('integrated.fontSize') ?? 10;
const lineHeight = terminalSettings.get<number>('integrated.lineHeight') ?? 1;
const scrollback = terminalSettings.get<number>('integrated.scrollback') ?? 1000;
return {
fontSize,
lineHeight,
scrollback,
};
}
}
15 changes: 9 additions & 6 deletions packages/extension/src/manager/contexts-states-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,32 @@
***********************************************************************/

import {
KubernetesTroubleshootingInformation,
ACTIVE_RESOURCES_COUNT,
AVAILABLE_CONTEXTS,
CONTEXTS_HEALTHS,
CONTEXTS_PERMISSIONS,
CURRENT_CONTEXT,
ENDPOINTS,
KUBERNETES_PROVIDERS,
KubernetesTroubleshootingInformation,
PORT_FORWARDS,
RESOURCE_DETAILS,
RESOURCE_EVENTS,
RESOURCES_COUNT,
TERMINAL_SETTINGS,
UPDATE_RESOURCE,
KUBERNETES_PROVIDERS,
} from '@kubernetes-dashboard/channels';

import { RpcChannel } from '@kubernetes-dashboard/rpc';
import { inject, injectable, multiInject } from 'inversify';
import type { ContextHealthState } from './context-health-checker.js';
import type { ContextPermissionResult } from './context-permissions-checker.js';
import type { DispatcherEvent } from './contexts-dispatcher.js';
import { ContextsManager } from './contexts-manager.js';
import { RpcChannel } from '@kubernetes-dashboard/rpc';
import { inject, injectable, multiInject } from 'inversify';
import { DispatcherObject } from '/@/dispatcher/util/dispatcher-object.js';
import { ChannelSubscriber } from '/@/subscriber/channel-subscriber.js';
import { PortForwardServiceProvider } from '/@/port-forward/port-forward-service.js';
import { KubernetesProvidersManager } from '/@/manager/kubernetes-providers.js';
import { PortForwardServiceProvider } from '/@/port-forward/port-forward-service.js';
import { ChannelSubscriber } from '/@/subscriber/channel-subscriber.js';
import { StateSubscriber } from '/@/subscriber/state-subscriber.js';

@injectable()
Expand Down Expand Up @@ -113,6 +114,8 @@ export class ContextsStatesDispatcher {
await this.dispatch(KUBERNETES_PROVIDERS);
});

this.dispatch(TERMINAL_SETTINGS).catch(console.error);

this.#subscribers.forEach(subscriber => {
subscriber.onSubscribe(channelName => this.dispatchByChannelName(subscriber, channelName));
});
Expand Down
10 changes: 8 additions & 2 deletions packages/extension/src/manager/pod-logs-api-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { IDisposable, PodLogsApi } from '@kubernetes-dashboard/channels';
import { PodLogsService } from '/@/pod-logs/pod-logs-service';
import { ContextsManager } from './contexts-manager';
import { RpcExtension } from '@kubernetes-dashboard/rpc';
import type { PodLogsOptions } from '@kubernetes-dashboard/channels';

type PodLogsInstance = {
counter: number;
Expand All @@ -35,7 +36,12 @@ export class PodLogsApiImpl implements PodLogsApi, IDisposable {
@inject(RpcExtension) private rpcExtension: RpcExtension,
) {}

async streamPodLogs(podName: string, namespace: string, containerName: string): Promise<void> {
async streamPodLogs(
podName: string,
namespace: string,
containerName: string,
options?: PodLogsOptions,
): Promise<void> {
if (!this.contextsManager.currentContext) {
throw new Error('No current context found');
}
Expand All @@ -45,7 +51,7 @@ export class PodLogsApiImpl implements PodLogsApi, IDisposable {
};
instance.counter++;
if (instance.counter === 1) {
await instance.service.startStream(podName, namespace, containerName);
await instance.service.startStream(podName, namespace, containerName, options);
}
this.#instances.set(this.getKey(podName, namespace, containerName), instance);
}
Expand Down
17 changes: 14 additions & 3 deletions packages/extension/src/pod-logs/pod-logs-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Log } from '@kubernetes/client-node';
import { injectable } from 'inversify';
import { PassThrough } from 'node:stream';
import { RpcExtension } from '@kubernetes-dashboard/rpc';
import { POD_LOGS } from '@kubernetes-dashboard/channels';
import { POD_LOGS, type PodLogsOptions } from '@kubernetes-dashboard/channels';
import { KubeConfigSingleContext } from '/@/types/kubeconfig-single-context';

@injectable()
Expand All @@ -33,7 +33,12 @@ export class PodLogsService {
private readonly rpcExtension: RpcExtension,
) {}

async startStream(podName: string, namespace: string, containerName: string): Promise<void> {
async startStream(
podName: string,
namespace: string,
containerName: string,
options?: PodLogsOptions,
): Promise<void> {
const log = new Log(this.context.getKubeConfig());

this.#logStream = new PassThrough();
Expand All @@ -52,7 +57,13 @@ export class PodLogsService {
})
.catch(console.error);
});
this.#abortController = await log.log(namespace, podName, containerName, this.#logStream, { follow: true });
this.#abortController = await log.log(namespace, podName, containerName, this.#logStream, {
follow: options?.stream ?? true,
previous: options?.previous,
tailLines: options?.tailLines,
sinceSeconds: options?.sinceSeconds,
timestamps: options?.timestamps,
});
}

stopStream(): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/webview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@xterm/addon-serialize": "^0.13.0",
"@xterm/xterm": "^5.5.0",
"humanize-duration": "^3.33.0",
"jsdom": "^27.1.0",
"jsdom": "^27.0.0",
"micromark": "^4.0.2",
"monaco-editor": "^0.54.0",
"prettier": "^3.6.1",
Expand Down
35 changes: 27 additions & 8 deletions packages/webview/src/component/pods/PodLogs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,34 @@
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import { render } from '@testing-library/svelte';
import { RemoteMocks } from '/@/tests/remote-mocks';
import { API_POD_LOGS, type PodLogsChunk, type PodLogsApi } from '@kubernetes-dashboard/channels';
import { StreamsMocks } from '/@/tests/stream-mocks';
import { FakeStreamObject } from '/@/stream/util/fake-stream-object.svelte';
import PodLogs from './PodLogs.svelte';
import {
API_POD_LOGS,
type PodLogsApi,
type PodLogsChunk,
type TerminalSettings,
} from '@kubernetes-dashboard/channels';
import type { V1Pod } from '@kubernetes/client-node';
import TerminalWindow from '/@/component/terminal/TerminalWindow.svelte';
import type { Terminal } from '@xterm/xterm';
import { EmptyScreen } from '@podman-desktop/ui-svelte';
import { render } from '@testing-library/svelte';
import type { Terminal } from '@xterm/xterm';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import PodLogs from './PodLogs.svelte';
import TerminalWindow from '/@/component/terminal/TerminalWindow.svelte';
import { FakeStateObject } from '/@/state/util/fake-state-object.svelte';
import { FakeStreamObject } from '/@/stream/util/fake-stream-object.svelte';
import { RemoteMocks } from '/@/tests/remote-mocks';
import { StatesMocks } from '/@/tests/state-mocks';
import { StreamsMocks } from '/@/tests/stream-mocks';

vi.mock(import('../terminal/TerminalWindow.svelte'));
vi.mock(import('@podman-desktop/ui-svelte'));

const remoteMocks = new RemoteMocks();
const streamMocks = new StreamsMocks();
const statesMocks = new StatesMocks();

const streamPodLogsMock = new FakeStreamObject<PodLogsChunk>();
const terminalSettingsMock = new FakeStateObject<TerminalSettings, void>();

beforeEach(() => {
vi.resetAllMocks();
Expand All @@ -43,6 +52,16 @@ beforeEach(() => {

remoteMocks.reset();
remoteMocks.mock(API_POD_LOGS, {} as unknown as PodLogsApi);

statesMocks.reset();
statesMocks.mock<TerminalSettings, void>('stateTerminalSettingsInfoUI', terminalSettingsMock);
terminalSettingsMock.setData({
fontSize: 12,
lineHeight: 1.2,
scrollback: 1000,
});
// Ensure subscribe returns an unsubscribe function after resetAllMocks
vi.mocked(terminalSettingsMock.subscribe).mockReturnValue(() => {});
});

describe('pod with one container', async () => {
Expand Down
Loading