1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into fix-hpa-metrics-values

This commit is contained in:
Alex Andreev 2023-01-18 10:27:31 +03:00
commit b996211c9b
58 changed files with 1069 additions and 668 deletions

View File

@ -37,7 +37,7 @@ jobs:
echo "status=create" >> $GITHUB_OUTPUT
- uses: peter-evans/create-pull-request@v4
if: ${{ steps.bump.outputs.status == "create" }}
if: ${{ steps.bump.outputs.status == 'create' }}
with:
add-paths: package.json
commit-message: Update package.json version to next preminor because of recent release

View File

@ -34,5 +34,6 @@ jobs:
uses: ./.github/workflows/publish-release-npm.yml
needs: release
if: ${{ needs.release.outputs.version != '' }}
secrets: inherit
with:
version: ${{ needs.release.outputs.version }}

View File

@ -3,7 +3,7 @@
"productName": "OpenLens",
"description": "OpenLens - Open Source IDE for Kubernetes",
"homepage": "https://github.com/lensapp/lens",
"version": "6.4.0-alpha.2",
"version": "6.4.0-alpha.3",
"repository": {
"type": "git",
"url": "git+https://github.com/lensapp/lens.git"
@ -262,12 +262,12 @@
"history": "^4.10.1",
"hpagent": "^1.2.0",
"http-proxy": "^1.18.1",
"immer": "^9.0.17",
"immer": "^9.0.18",
"joi": "^17.7.0",
"js-yaml": "^4.1.0",
"jsdom": "^16.7.0",
"lodash": "^4.17.15",
"marked": "^4.2.5",
"marked": "^4.2.12",
"md5-file": "^5.0.0",
"mobx": "^6.7.0",
"mobx-observable-history": "^2.0.3",
@ -312,7 +312,7 @@
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@sentry/types": "^6.19.7",
"@swc/cli": "^0.1.59",
"@swc/core": "^1.3.25",
"@swc/core": "^1.3.26",
"@swc/jest": "^0.2.24",
"@testing-library/dom": "^7.31.2",
"@testing-library/jest-dom": "^5.16.5",
@ -370,8 +370,8 @@
"@types/webpack-dev-server": "^4.7.2",
"@types/webpack-env": "^1.18.0",
"@types/webpack-node-externals": "^2.5.3",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"adr": "^1.4.3",
"ansi_up": "^5.1.0",
"chalk": "^4.1.2",
@ -387,13 +387,13 @@
"electron": "^19.1.9",
"electron-builder": "^23.6.0",
"electron-notarize": "^0.3.0",
"esbuild": "^0.16.16",
"esbuild-loader": "^2.20.0",
"eslint": "^8.31.0",
"eslint-import-resolver-typescript": "^3.5.2",
"esbuild": "^0.17.0",
"esbuild-loader": "^2.21.0",
"eslint": "^8.32.0",
"eslint-import-resolver-typescript": "^3.5.3",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-react": "^7.32.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-unused-imports": "^2.0.0",
"fork-ts-checker-webpack-plugin": "^6.5.2",

View File

@ -8,7 +8,7 @@ import { CatalogEntity, CatalogCategory, categoryVersion } from "../catalog/cata
import { broadcastMessage } from "../ipc";
import { app } from "electron";
import type { CatalogEntityConstructor, CatalogEntitySpec } from "../catalog/catalog-entity";
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import { IpcRendererNavigationEvents } from "../ipc/navigation-events";
import { requestClusterActivation, requestClusterDisconnection } from "../../renderer/ipc";
import KubeClusterCategoryIcon from "./icons/kubernetes.svg";
import getClusterByIdInjectable from "../cluster-store/get-by-id.injectable";

View File

@ -2,7 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import { IpcRendererNavigationEvents } from "../ipc/navigation-events";
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
export type AppNavigationChannel = MessageChannel<string>;

View File

@ -2,7 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import { IpcRendererNavigationEvents } from "../ipc/navigation-events";
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
export type ClusterFrameNavigationChannel = MessageChannel<string>;

View File

@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { IpcMain } from "electron";
const ipcMainInjectionToken = getInjectionToken<IpcMain>({
id: "ipc-main-injection-token",
});
export default ipcMainInjectionToken;

View File

@ -12,17 +12,17 @@ import { toJS } from "../utils/toJS";
import type { ClusterFrameInfo } from "../cluster-frames";
import { clusterFrameMap } from "../cluster-frames";
import type { Disposer } from "../utils";
import ipcMainInjectable from "../../main/utils/channel/ipc-main/ipc-main.injectable";
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable";
import loggerInjectable from "../logger.injectable";
import ipcMainInjectionToken from "./ipc-main-injection-token";
export const broadcastMainChannel = "ipc:broadcast-main";
export function ipcMainHandle(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) {
const di = getLegacyGlobalDiForExtensionApi();
const ipcMain = di.inject(ipcMainInjectable);
const ipcMain = di.inject(ipcMainInjectionToken);
ipcMain.handle(channel, async (event, ...args) => {
return sanitizePayload(await listener(event, ...args));
@ -90,7 +90,7 @@ export async function broadcastMessage(channel: string, ...args: any[]): Promise
export function ipcMainOn(channel: string, listener: (event: Electron.IpcMainEvent, ...args: any[]) => any): Disposer {
const di = getLegacyGlobalDiForExtensionApi();
const ipcMain = di.inject(ipcMainInjectable);
const ipcMain = di.inject(ipcMainInjectionToken);
ipcMain.on(channel, listener);

View File

@ -27,6 +27,10 @@ import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-exten
import maybeKubeApiInjectable from "../../common/k8s-api/maybe-kube-api.injectable";
import { DeploymentApi as InternalDeploymentApi, IngressApi as InternalIngressApi, NodeApi, PersistentVolumeClaimApi, PodApi } from "../../common/k8s-api/endpoints";
import { storesAndApisCanBeCreatedInjectionToken } from "../../common/k8s-api/stores-apis-can-be-created.token";
import type { JsonApiConfig } from "../../common/k8s-api/json-api";
import type { KubeJsonApi as InternalKubeJsonApi } from "../../common/k8s-api/kube-json-api";
import createKubeJsonApiInjectable from "../../common/k8s-api/create-kube-json-api.injectable";
import type { RequestInit } from "node-fetch";
export const apiManager = asLegacyGlobalForExtensionApi(apiManagerInjectable);
export const forCluster = asLegacyGlobalFunctionForExtensionApi(createKubeApiForClusterInjectable);
@ -107,11 +111,24 @@ export {
type KubeStatusData,
} from "../../common/k8s-api/kube-object";
export {
KubeJsonApi,
type KubeJsonApiData,
export type {
KubeJsonApiData,
} from "../../common/k8s-api/kube-json-api";
function KubeJsonApiCstr(config: JsonApiConfig, reqInit?: RequestInit) {
const di = getLegacyGlobalDiForExtensionApi();
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
return createKubeJsonApi(config, reqInit);
}
export const KubeJsonApi = Object.assign(
KubeJsonApiCstr as unknown as new (config: JsonApiConfig, reqInit?: RequestInit) => InternalKubeJsonApi,
{
forCluster,
},
);
export abstract class KubeObjectStore<
K extends KubeObject = KubeObject,
A extends InternalKubeApi<K, D> = InternalKubeApi<K, KubeJsonApiDataFor<K>>,

View File

@ -73,14 +73,14 @@ export * from "../../renderer/components/dialog";
export * from "../../renderer/components/line-progress";
export * from "../../renderer/components/menu";
export type {
CreateNotificationOptions,
Notification,
NotificationId,
NotificationMessage,
export {
NotificationStatus,
ShowNotification,
NotificationsStore,
type CreateNotificationOptions,
type Notification,
type NotificationId,
type NotificationMessage,
type ShowNotification,
type NotificationsStore,
} from "../../renderer/components/notifications";
export const Notifications = {

View File

@ -1141,7 +1141,7 @@ exports[`preferences - navigation to terminal preferences given in preferences,
class="Select__single-value css-1dimb5e-singleValue"
>
<span
style="font-family: RobotoMono; font-size: 12px;"
style="font-family: RobotoMono, var(--font-terminal); font-size: 12px;"
>
RobotoMono
</span>

View File

@ -5,66 +5,30 @@
import React from "react";
import { SubTitle } from "../../../../../../renderer/components/layout/sub-title";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { UserStore } from "../../../../../../common/user-store";
import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable";
import { observer } from "mobx-react";
import type { SelectOption } from "../../../../../../renderer/components/select";
import { Select } from "../../../../../../renderer/components/select";
import type { Logger } from "../../../../../../common/logger";
import { action } from "mobx";
import loggerInjectable from "../../../../../../common/logger.injectable";
import type { TerminalFontPreferencePresenter } from "./terminal-font-options.injectable";
import terminalFontPreferencePresenterInjectable from "./terminal-font-options.injectable";
interface Dependencies {
userStore: UserStore;
logger: Logger;
model: TerminalFontPreferencePresenter;
}
const NonInjectedTerminalFontFamily = observer(
({ userStore, logger }: Dependencies) => {
const NonInjectedTerminalFontFamily = observer(({ model }: Dependencies) => (
<section>
<SubTitle title="Font family" />
<Select
themeName="lens"
controlShouldRenderValue
value={model.current.get()}
options={model.options.get()}
onChange={model.onSelection}
/>
</section>
));
// fonts must be declared in `fonts.scss` and at `template.html` (if early-preloading required)
const supportedCustomFonts: SelectOption<string>[] = [
"RobotoMono", "Anonymous Pro", "IBM Plex Mono", "JetBrains Mono", "Red Hat Mono",
"Source Code Pro", "Space Mono", "Ubuntu Mono",
].map(customFont => {
const { fontFamily, fontSize } = userStore.terminalConfig;
return {
label: <span style={{ fontFamily: customFont, fontSize }}>{customFont}</span>,
value: customFont,
isSelected: fontFamily === customFont,
};
});
const onFontFamilyChange = action(({ value: fontFamily }: SelectOption<string>) => {
logger.info(`setting terminal font to ${fontFamily}`);
userStore.terminalConfig.fontFamily = fontFamily; // save to external storage
});
return (
<section>
<SubTitle title="Font family" />
<Select
themeName="lens"
controlShouldRenderValue
value={userStore.terminalConfig.fontFamily}
options={supportedCustomFonts}
onChange={onFontFamilyChange as any}
/>
</section>
);
},
);
export const TerminalFontFamily = withInjectables<Dependencies>(
NonInjectedTerminalFontFamily,
{
getProps: (di) => ({
userStore: di.inject(userStoreInjectable),
logger: di.inject(loggerInjectable),
}),
},
);
export const TerminalFontFamily = withInjectables<Dependencies>(NonInjectedTerminalFontFamily, {
getProps: (di) => ({
model: di.inject(terminalFontPreferencePresenterInjectable),
}),
});

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import { action, computed } from "mobx";
import React from "react";
import type { SingleValue } from "react-select";
import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable";
import { defaultTerminalFontFamily } from "../../../../../../common/vars";
import type { SelectOption } from "../../../../../../renderer/components/select";
import { terminalFontInjectionToken } from "../../../../../terminal/renderer/fonts/token";
export interface TerminalFontPreferencePresenter {
readonly options: IComputedValue<SelectOption<string>[]>;
readonly current: IComputedValue<string>;
onSelection: (selection: SingleValue<SelectOption<string>>) => void;
}
const terminalFontPreferencePresenterInjectable = getInjectable({
id: "terminal-font-preference-presenter",
instantiate: (di): TerminalFontPreferencePresenter => {
const userStore = di.inject(userStoreInjectable);
const terminalFonts = di.injectMany(terminalFontInjectionToken);
return {
options: computed(() => terminalFonts.map(font => ({
label: (
<span
style={{
fontFamily: `${font.name}, var(--font-terminal)`,
fontSize: userStore.terminalConfig.fontSize,
}}
>
{font.name}
</span>
),
value: font.name,
isSelected: userStore.terminalConfig.fontFamily === font.name,
}))),
current: computed(() => userStore.terminalConfig.fontFamily),
onSelection: action(selection => {
userStore.terminalConfig.fontFamily = selection?.value ?? defaultTerminalFontFamily;
}),
};
},
});
export default terminalFontPreferencePresenterInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
import AnonymousPro from "./AnonymousPro-Regular.ttf";
const anonymousProTerminalFontInjectable = getInjectable({
id: "anonymous-pro-terminal-font",
instantiate: () => ({
name:"Anonymous Pro",
url: AnonymousPro,
}),
injectionToken: terminalFontInjectionToken,
});
export default anonymousProTerminalFontInjectable;

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
const terminalFontsInjectable = getInjectable({
id: "terminal-fonts",
instantiate: (di) => di.injectMany(terminalFontInjectionToken),
});
export default terminalFontsInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
import IBMPlexMono from "./IBMPlexMono-Regular.ttf";
const ibmPlexMonoTerminalFontInjectable = getInjectable({
id: "ibm-plex-mono-terminal-font",
instantiate: () => ({
name: "IBM Plex Mono",
url: IBMPlexMono,
}),
injectionToken: terminalFontInjectionToken,
});
export default ibmPlexMonoTerminalFontInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
import JetBrainsMono from "./JetBrainsMono-Regular.ttf";
const jetbrainsMonoTerminalFontInjectable = getInjectable({
id: "jetbrains-mono-terminal-font",
instantiate: () => ({
name: "JetBrains Mono",
url: JetBrainsMono,
}),
injectionToken: terminalFontInjectionToken,
});
export default jetbrainsMonoTerminalFontInjectable;

View File

@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../../../common/test-utils/get-global-override";
import loadTerminalFontInjectable from "./load-font.injectable";
export default getGlobalOverride(loadTerminalFontInjectable, () => async () => {});

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { TerminalFont } from "./token";
export type LoadTerminalFont = (font: TerminalFont) => Promise<void>;
const loadTerminalFontInjectable = getInjectable({
id: "load-terminal-font",
instantiate: (): LoadTerminalFont => async (font) => {
const fontLoaded = document.fonts.check(`10px ${font.name}`);
if (fontLoaded) {
return;
}
const fontFace = new FontFace(font.name, `url(${font.url})`);
document.fonts.add(fontFace);
await fontFace.load();
},
causesSideEffects: true,
});
export default loadTerminalFontInjectable;

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeFrameStartsFirstInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
import terminalFontsInjectable from "./fonts.injectable";
import loadTerminalFontInjectable from "./load-font.injectable";
const preloadTerminalFontsInjectable = getInjectable({
id: "preload-terminal-fonts",
instantiate: (di) => ({
id: "preload-terminal-fonts",
run: async () => {
const terminalFonts = di.inject(terminalFontsInjectable);
const loadTerminalFont = di.inject(loadTerminalFontInjectable);
await Promise.allSettled(terminalFonts.map(loadTerminalFont));
},
}),
injectionToken: beforeFrameStartsFirstInjectionToken,
});
export default preloadTerminalFontsInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
import RedHatMono from "./RedHatMono-Regular.ttf";
const redHatMonoTerminalFontInjectable = getInjectable({
id: "red-hat-mono-terminal-font",
instantiate: () => ({
name: "Red Hat Mono",
url: RedHatMono,
}),
injectionToken: terminalFontInjectionToken,
});
export default redHatMonoTerminalFontInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
import RobotoMono from "./Roboto-Mono.ttf"; // patched font with icons
const robotoMonoTerminalFontInjectable = getInjectable({
id: "roboto-mono-terminal-font",
instantiate: () => ({
name: "RobotoMono",
url: RobotoMono,
}),
injectionToken: terminalFontInjectionToken,
});
export default robotoMonoTerminalFontInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
import SourceCodePro from "./SourceCodePro-Regular.ttf";
const sourceCodeProTerminalFontInjectable = getInjectable({
id: "source-code-pro-terminal-font",
instantiate: () => ({
name: "Source Code Pro",
url: SourceCodePro,
}),
injectionToken: terminalFontInjectionToken,
});
export default sourceCodeProTerminalFontInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
import SpaceMono from "./SpaceMono-Regular.ttf";
const spaceMonoTerminalFontInjectable = getInjectable({
id: "space-mono-terminal-font",
instantiate: () => ({
name: "Space Mono",
url: SpaceMono,
}),
injectionToken: terminalFontInjectionToken,
});
export default spaceMonoTerminalFontInjectable;

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
export interface TerminalFont {
name: string;
url: string;
}
export const terminalFontInjectionToken = getInjectionToken<TerminalFont>({
id: "terminal-font-token",
});

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { terminalFontInjectionToken } from "./token";
import UbuntuMono from "./UbuntuMono-Regular.ttf";
const ubunutuMonoTerminalFontInjectable = getInjectable({
id: "ubunutu-mono-terminal-font",
instantiate: () => ({
name: "Ubuntu Mono",
url: UbuntuMono,
}),
injectionToken: terminalFontInjectionToken,
});
export default ubunutuMonoTerminalFontInjectable;

View File

@ -4,11 +4,11 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { reaction } from "mobx";
import ipcMainInjectionToken from "../../common/ipc/ipc-main-injection-token";
import { catalogInitChannel } from "../../common/ipc/catalog";
import { disposer, toJS } from "../../common/utils";
import { getStartableStoppable } from "../../common/utils/get-startable-stoppable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
import ipcMainInjectable from "../utils/channel/ipc-main/ipc-main.injectable";
import catalogSyncBroadcasterInjectable from "./broadcaster.injectable";
const catalogSyncToRendererInjectable = getInjectable({
@ -16,7 +16,7 @@ const catalogSyncToRendererInjectable = getInjectable({
instantiate: (di) => {
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const ipcMain = di.inject(ipcMainInjectable);
const ipcMain = di.inject(ipcMainInjectionToken);
const catalogSyncBroadcaster = di.inject(catalogSyncBroadcasterInjectable);
return getStartableStoppable(

View File

@ -145,31 +145,31 @@ export class ClusterManager {
} else {
entity.status.phase = (() => {
if (!cluster) {
this.dependencies.logger.debug(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="no cluster"`);
this.dependencies.logger.silly(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="no cluster"`);
return LensKubernetesClusterStatus.DISCONNECTED;
}
if (cluster.accessible) {
this.dependencies.logger.debug(`${logPrefix} setting entity ${entity.getName()} to CONNECTED, reason="cluster is accessible"`);
this.dependencies.logger.silly(`${logPrefix} setting entity ${entity.getName()} to CONNECTED, reason="cluster is accessible"`);
return LensKubernetesClusterStatus.CONNECTED;
}
if (!cluster.disconnected) {
this.dependencies.logger.debug(`${logPrefix} setting entity ${entity.getName()} to CONNECTING, reason="cluster is not disconnected"`);
this.dependencies.logger.silly(`${logPrefix} setting entity ${entity.getName()} to CONNECTING, reason="cluster is not disconnected"`);
return LensKubernetesClusterStatus.CONNECTING;
}
// Extensions are not allowed to use the Lens specific status phases
if (!lensSpecificClusterStatuses.has(entity?.status?.phase)) {
this.dependencies.logger.debug(`${logPrefix} not clearing entity ${entity.getName()} status, reason="custom string"`);
this.dependencies.logger.silly(`${logPrefix} not clearing entity ${entity.getName()} status, reason="custom string"`);
return entity.status.phase;
}
this.dependencies.logger.debug(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="fallthrough"`);
this.dependencies.logger.silly(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="fallthrough"`);
return LensKubernetesClusterStatus.DISCONNECTED;
})();

View File

@ -3,15 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import ipcMainInjectionToken from "../../../../common/ipc/ipc-main-injection-token";
import { bundledExtensionsLoaded } from "../../../../common/ipc/extension-handling";
import { delay } from "../../../../common/utils";
import ipcMainInjectable from "../../../utils/channel/ipc-main/ipc-main.injectable";
const waitUntilBundledExtensionsAreLoadedInjectable = getInjectable({
id: "wait-until-bundled-extensions-are-loaded",
instantiate: (di) => {
const ipcMain = di.inject(ipcMainInjectable);
const ipcMain = di.inject(ipcMainInjectionToken);
return async () => {
const viewHasLoaded = new Promise<void>((resolve) => {

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import { iter } from "../../../common/utils";
import clusterFramesInjectable from "../../../common/cluster-frames.injectable";
import { IpcRendererNavigationEvents } from "../../../renderer/navigation/events";
import { IpcRendererNavigationEvents } from "../../../common/ipc/navigation-events";
import showApplicationWindowInjectable from "./show-application-window.injectable";
import getCurrentApplicationWindowInjectable from "./application-window/get-current-application-window.injectable";
import assert from "assert";

View File

@ -4,14 +4,14 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { IpcMainEvent } from "electron";
import ipcMainInjectable from "../ipc-main/ipc-main.injectable";
import { enlistMessageChannelListenerInjectionToken } from "../../../../common/utils/channel/enlist-message-channel-listener-injection-token";
import ipcMainInjectionToken from "../../../../common/ipc/ipc-main-injection-token";
const enlistMessageChannelListenerInjectable = getInjectable({
id: "enlist-message-channel-listener-for-main",
instantiate: (di) => {
const ipcMain = di.inject(ipcMainInjectable);
const ipcMain = di.inject(ipcMainInjectionToken);
return ({ channel, handler }) => {
const nativeOnCallback = (_: IpcMainEvent, message: unknown) => {

View File

@ -4,10 +4,10 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { IpcMainInvokeEvent } from "electron";
import ipcMainInjectable from "../ipc-main/ipc-main.injectable";
import type { Disposer } from "../../../../common/utils";
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
import type { RequestChannelListener } from "./listener-tokens";
import ipcMainInjectionToken from "../../../../common/ipc/ipc-main-injection-token";
export type EnlistRequestChannelListener = <TChannel extends RequestChannel<unknown, unknown>>(listener: RequestChannelListener<TChannel>) => Disposer;
@ -15,7 +15,7 @@ const enlistRequestChannelListenerInjectable = getInjectable({
id: "enlist-request-channel-listener-for-main",
instantiate: (di): EnlistRequestChannelListener => {
const ipcMain = di.inject(ipcMainInjectable);
const ipcMain = di.inject(ipcMainInjectionToken);
return ({ channel, handler }) => {
const nativeHandleCallback = (_: IpcMainInvokeEvent, request: unknown) => handler(request);

View File

@ -4,11 +4,13 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { ipcMain } from "electron";
import ipcMainInjectionToken from "../../../../common/ipc/ipc-main-injection-token";
const ipcMainInjectable = getInjectable({
id: "ipc-main",
instantiate: () => ipcMain,
causesSideEffects: true,
injectionToken: ipcMainInjectionToken,
});
export default ipcMainInjectable;

View File

@ -16,7 +16,6 @@ const setupKubernetesClusterCatalogAddMenuListenerInjectable = getInjectable({
instantiate: (di) => ({
id: "setup-kubernetes-cluster-catalog-add-menu-listener",
run: () => {
// NOTE: these have to be here so that they are initialized only after the `runAfter` is ran
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
const addSyncEntries = di.inject(addSyncEntriesInjectable);
const kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable);

View File

@ -232,22 +232,6 @@ iframe {
}
}
#fonts-preloading {
> span {
position: absolute;
visibility: hidden;
height: 0;
&:before {
width: 0;
display: block;
overflow: hidden;
content: "text-example"; // some text required to start applying/rendering font in document
font-family: inherit; // font-family must be specified via style="" (see: template.html)
}
}
}
// app's common loading indicator, displaying on the route transitions
#loading {
position: absolute;

View File

@ -88,7 +88,15 @@ export class ClusterFrameHandler {
() => {
this.dependencies.logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`);
this.views.delete(clusterId);
parentElem.removeChild(iframe);
// Must only remove iframe from DOM after it unloads old code. Else it crashes
iframe.addEventListener("load", () => parentElem.removeChild(iframe), {
once: true,
});
// This causes the old code to be unloaded.
iframe.setAttribute("src", "");
dispose();
},
);

View File

@ -11,7 +11,7 @@ import React, { useState } from "react";
import commandOverlayInjectable from "./command-overlay.injectable";
import type { CatalogEntity } from "../../../common/catalog";
import { broadcastMessage } from "../../../common/ipc";
import { IpcRendererNavigationEvents } from "../../navigation/events";
import { IpcRendererNavigationEvents } from "../../../common/ipc/navigation-events";
import type { RegisteredCommand } from "./registered-commands/commands";
import { iter } from "../../utils";
import { withInjectables } from "@ogre-tools/injectable-react";

View File

@ -9,7 +9,8 @@ import type { TabId } from "../dock/store";
import type { TerminalApi } from "../../../api/terminal-api";
import terminalSpawningPoolInjectable from "./terminal-spawning-pool.injectable";
import terminalConfigInjectable from "../../../../common/user-store/terminal-config.injectable";
import terminalCopyOnSelectInjectable from "../../../../common/user-store/terminal-copy-on-select.injectable";
import terminalCopyOnSelectInjectable
from "../../../../common/user-store/terminal-copy-on-select.injectable";
import isMacInjectable from "../../../../common/vars/is-mac.injectable";
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
import xtermColorThemeInjectable from "../../../themes/terminal-colors.injectable";

View File

@ -44,58 +44,3 @@
font-display: block;
src: url("../fonts/MaterialIcons-Regular.ttf") format("truetype");
}
// Terminal fonts (monospaced)
// Source: https://fonts.google.com/?category=Monospace
@font-face {
font-family: "Anonymous Pro";
src: local("Anonymous Pro"), url("../fonts/AnonymousPro-Regular.ttf") format("truetype");
font-display: block;
}
@font-face {
font-family: "IBM Plex Mono";
src: local("IBM Plex Mono"), url("../fonts/IBMPlexMono-Regular.ttf") format("truetype");
font-display: block;
}
@font-face {
font-family: "JetBrains Mono";
src: local("JetBrains Mono"), url("../fonts/JetBrainsMono-Regular.ttf") format("truetype");
font-display: block;
}
@font-face {
font-family: "Red Hat Mono";
src: local("Red Hat Mono"), url("../fonts/RedHatMono-Regular.ttf") format("truetype");
font-display: block;
}
@font-face {
font-family: "Source Code Pro";
src: local("Source Code Pro"), url("../fonts/SourceCodePro-Regular.ttf") format("truetype");
font-display: block;
}
@font-face {
font-family: "Space Mono";
src: local("Space Mono"), url("../fonts/SpaceMono-Regular.ttf") format("truetype");
font-display: block;
}
@font-face {
font-family: "Ubuntu Mono";
src: local("Ubuntu Mono"), url("../fonts/UbuntuMono-Regular.ttf") format("truetype");
font-display: block;
}
// Patched RobotoMono font with icons
// RobotoMono Windows Compatible for using in terminal
// https://github.com/ryanoasis/nerd-fonts/tree/master/patched-fonts/RobotoMono
@font-face {
font-family: "RobotoMono";
src: local("RobotoMono"), url("../fonts/Roboto-Mono-nerd.ttf") format("truetype");
font-display: block;
}

View File

@ -0,0 +1,121 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`kube-object-list-layout given pod store renders 1`] = `
<body>
<div>
<div>
<div
class="ItemListLayout flex column KubeObjectListLayout Pods"
>
<div
class="header flex gaps align-center"
>
<h5
class="title"
>
Pods
</h5>
<div
class="info-panel box grow"
>
0 items
</div>
<div
class="NamespaceSelectFilterParent"
data-testid="namespace-select-filter"
>
<div
class="Select theme-dark NamespaceSelect NamespaceSelectFilter css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-kube-object-list-layout-namespace-select-input-live-region"
/>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class="Select__control css-13cymwt-control"
>
<div
class="Select__value-container Select__value-container--is-multi css-1fdsijx-ValueContainer"
>
<div
class="Select__placeholder css-1jqq78o-placeholder"
id="react-select-kube-object-list-layout-namespace-select-input-placeholder"
>
All namespaces
</div>
<div
class="Select__input-container css-qbdosj-Input"
data-value=""
>
<input
aria-autocomplete="list"
aria-describedby="react-select-kube-object-list-layout-namespace-select-input-placeholder"
aria-expanded="false"
aria-haspopup="true"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="Select__input"
id="kube-object-list-layout-namespace-select-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
>
<span
class="Select__indicator-separator css-1u9des2-indicatorSeparator"
/>
<div
aria-hidden="true"
class="Select__indicator Select__dropdown-indicator css-1xc3v61-indicatorContainer"
>
<svg
aria-hidden="true"
class="css-tj5bde-Svg"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
>
<path
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="items box grow flex column"
>
<div
class="Table flex column KubeObjectListLayout Pods box grow dark selectable scrollable autoSize virtual"
>
<div
class="Spinner singleColor center"
/>
</div>
<div
class="AddRemoveButtons flex gaps"
/>
</div>
</div>
</div>
</div>
</body>
`;

View File

@ -0,0 +1,108 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainer } from "@ogre-tools/injectable";
import "@testing-library/jest-dom/extend-expect";
import type { RenderResult } from "@testing-library/react";
import React from "react";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import kubeSelectedUrlParamInjectable from "../kube-detail-params/kube-selected-url.injectable";
import toggleKubeDetailsPaneInjectable from "../kube-detail-params/toggle-details.injectable";
import type { DiRender } from "../test-utils/renderFor";
import { renderFor } from "../test-utils/renderFor";
import { KubeObjectListLayout } from "./index";
import appPathsStateInjectable from "../../../common/app-paths/app-paths-state.injectable";
import podStoreInjectable from "../+workloads-pods/store.injectable";
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
import type { PodStore } from "../+workloads-pods/store";
describe("kube-object-list-layout", () => {
let di: DiContainer;
let render: DiRender;
let podStore: PodStore;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable);
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name",
id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig",
}, {
clusterServerUrl: "https://localhost:8080",
}));
render = renderFor(di);
di.override(subscribeStoresInjectable, () => jest.fn().mockImplementation(() => jest.fn()));
di.override(kubeSelectedUrlParamInjectable, () => ({
get: () => "path",
}));
di.override(toggleKubeDetailsPaneInjectable, () => null);
di.override(appPathsStateInjectable, () => ({
get: () => ({}),
}));
podStore = di.inject(podStoreInjectable);
});
describe("given pod store", () => {
let result: RenderResult;
it("renders", () => {
result = render((
<div>
<KubeObjectListLayout
className="Pods"
store={podStore}
tableId = "workloads_pods"
isConfigurable
renderHeaderTitle="Pods"
renderTableContents={pod => [
<div key={pod.getName()}>{pod.getName()}</div>,
]}
/>
</div>
));
expect(result.baseElement).toMatchSnapshot();
});
describe("given resourcename", () => {
it("uses resourcename in search placeholder", () => {
result = render((
<div>
<KubeObjectListLayout
className="Pods"
store={podStore}
tableId = "workloads_pods"
isConfigurable
renderHeaderTitle="Pods"
renderTableContents={pod => [
<div key={pod.getName()}>{pod.getName()}</div>,
]}
resourceName="My Custom Items"
searchFilters={[() => null]}
/>
</div>
));
expect(result.getByPlaceholderText("Search My Custom Items...")).toBeInTheDocument();
});
});
});
});

View File

@ -40,6 +40,12 @@ export interface KubeObjectListLayoutProps<
store: KubeObjectStore<K, A, D>;
dependentStores?: SubscribableStore[];
subscribeStores?: boolean;
/**
* Customize resource name for e.g. search input ("Search <ResourceName>..."")
* If not provided, ResourceNames is used instead with a fallback to resource kind.
*/
resourceName?: string;
}
interface Dependencies {
@ -132,7 +138,7 @@ class NonInjectedKubeObjectListLayout<
onDetails,
...layoutProps
} = this.props;
const placeholderString = ResourceNames[ResourceKindMap[store.api.kind]] || store.api.kind;
const resourceName = this.props.resourceName || ResourceNames[ResourceKindMap[store.api.kind]] || store.api.kind;
return (
<ItemListLayout<K, false>
@ -151,7 +157,7 @@ class NonInjectedKubeObjectListLayout<
),
searchProps: {
...searchProps,
placeholder: `Search ${placeholderString}...`,
placeholder: `Search ${resourceName}...`,
},
info: (
<>

View File

@ -8,7 +8,7 @@ import { observable } from "mobx";
import React, { useState } from "react";
import { broadcastMessage } from "../../../common/ipc";
import type { CatalogEntity, CatalogEntityContextMenu } from "../../api/catalog-entity";
import { IpcRendererNavigationEvents } from "../../navigation/events";
import { IpcRendererNavigationEvents } from "../../../common/ipc/navigation-events";
import { Avatar } from "../avatar";
import { Icon } from "../icon";
import { Menu, MenuItem } from "../menu";

View File

@ -6,8 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import React from "react";
import navigateToKubernetesPreferencesInjectable from "../../features/preferences/common/navigate-to-kubernetes-preferences.injectable";
import discoverAllKubeconfigSyncKindsInjectable from "../../features/preferences/renderer/preference-items/kubernetes/kubeconfig-sync/discover-all-sync-kinds.injectable";
import { action } from "mobx";
import { runInAction } from "mobx";
import showSuccessNotificationInjectable from "../components/notifications/show-success-notification.injectable";
const addSyncEntriesInjectable = getInjectable({
@ -16,14 +15,11 @@ const addSyncEntriesInjectable = getInjectable({
instantiate: (di) => {
const userStore = di.inject(userStoreInjectable);
const navigateToKubernetesPreferences = di.inject(navigateToKubernetesPreferencesInjectable);
const discoverAllKubeconfigSyncKinds = di.inject(discoverAllKubeconfigSyncKindsInjectable);
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
return async (filePaths: string[]) => {
const kinds = await discoverAllKubeconfigSyncKinds(filePaths);
action(() => {
for (const [path] of kinds) {
return async (paths: string[]) => {
runInAction(() => {
for (const path of paths) {
userStore.syncKubeconfigEntries.set(path, {});
}
});

View File

@ -7,7 +7,7 @@ import observableHistoryInjectable from "../navigation/observable-history.inject
import { runInAction } from "mobx";
import type { NavigateToUrl } from "../../common/front-end-routing/navigate-to-url-injection-token";
import { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token";
import { IpcRendererNavigationEvents } from "../navigation/events";
import { IpcRendererNavigationEvents } from "../../common/ipc/navigation-events";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
const navigateToUrlInjectable = getInjectable({

View File

@ -5,17 +5,6 @@
</head>
<body>
<div id="app"></div>
<div id="fonts-preloading">
<span style="font-family: 'Material Icons'" />
<span style="font-family: 'RobotoMono'" />
<span style="font-family: 'Anonymous Pro'" />
<span style="font-family: 'IBM Plex Mono'" />
<span style="font-family: 'JetBrains Mono'" />
<span style="font-family: 'Red Hat Mono'" />
<span style="font-family: 'Source Code Pro'" />
<span style="font-family: 'Space Mono'" />
<span style="font-family: 'Ubuntu Mono'" />
</div>
<div id="terminal-init"></div>
</body>
</html>

6
types/mocks.d.ts vendored
View File

@ -32,4 +32,8 @@ declare module "*.png";
declare module "*.eot";
declare module "*.woff";
declare module "*.woff2";
declare module "*.ttf";
declare module "*.ttf" {
const content: string;
export = content;
}

View File

@ -25,19 +25,19 @@ const server = new WebpackDevServer({
host: "localhost",
port: webpackDevServerPort,
static: buildDir, // aka `devServer.contentBase` in webpack@4
hot: "only", // use HMR only without errors
hot: true,
liveReload: false,
historyApiFallback: true,
compress: true, // enable gzip for everything served
devMiddleware: {
writeToDisk: false,
index: "OpenLensDev.html",
publicPath: "/build",
},
proxy: {
"^/$": "/build/",
},
client: {
reconnect: true,
overlay: false, // don't show warnings and errors on top of rendered app view
logging: "error",
logging: "info",
},
}, compiler);

890
yarn.lock

File diff suppressed because it is too large Load Diff