mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into issue-3498
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
commit
8cce15a002
85 files changed
+9640
-5855
No files matched your search
+10
-13
@@ -3,7 +3,7 @@
|
||||
"productName": "",
|
||||
"description": "Lens Desktop Core",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"version": "6.4.0-beta.10",
|
||||
"version": "6.4.0-beta.13",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lensapp/lens.git"
|
||||
@@ -125,9 +125,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astronautlabs/jsonpath": "^1.1.0",
|
||||
"@hapi/call": "^9.0.0",
|
||||
"@hapi/call": "^9.0.1",
|
||||
"@hapi/subtext": "^7.0.4",
|
||||
"@k8slens/node-fetch": "^6.4.0-beta.10",
|
||||
"@k8slens/node-fetch": "^6.4.0-beta.13",
|
||||
"@kubernetes/client-node": "^0.18.1",
|
||||
"@material-ui/styles": "^4.11.5",
|
||||
"@ogre-tools/fp": "^12.0.1",
|
||||
@@ -200,10 +200,9 @@
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@sentry/types": "^6.19.7",
|
||||
"@swc/cli": "^0.1.59",
|
||||
"@swc/core": "^1.3.31",
|
||||
"@swc/cli": "^0.1.61",
|
||||
"@swc/core": "^1.3.35",
|
||||
"@swc/jest": "^0.2.24",
|
||||
"@testing-library/dom": "^7.31.2",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
@@ -221,7 +220,6 @@
|
||||
"@types/glob-to-regexp": "^0.4.1",
|
||||
"@types/hapi__call": "^9.0.0",
|
||||
"@types/hapi__subtext": "^7.0.0",
|
||||
"@types/html-webpack-plugin": "^3.2.6",
|
||||
"@types/http-proxy": "^1.17.9",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
@@ -254,8 +252,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.49.0",
|
||||
"@typescript-eslint/parser": "^5.49.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
||||
"@typescript-eslint/parser": "^5.52.0",
|
||||
"adr": "^1.4.3",
|
||||
"ansi_up": "^5.1.0",
|
||||
"chalk": "^4.1.2",
|
||||
@@ -267,11 +265,11 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.3",
|
||||
"deepdash": "^5.3.9",
|
||||
"dompurify": "^2.4.3",
|
||||
"dompurify": "^2.4.4",
|
||||
"electron": "^19.1.9",
|
||||
"electron-builder": "^23.6.0",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"esbuild": "^0.17.5",
|
||||
"esbuild": "^0.17.7",
|
||||
"esbuild-loader": "^2.21.0",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.3",
|
||||
@@ -281,7 +279,6 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.3.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"include-media": "^1.4.9",
|
||||
@@ -313,7 +310,7 @@
|
||||
"react-table": "^7.8.0",
|
||||
"react-window": "^1.8.8",
|
||||
"rimraf": "^4.1.2",
|
||||
"sass": "^1.57.1",
|
||||
"sass": "^1.58.0",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
|
||||
@@ -33,6 +33,20 @@ export class Namespace extends KubeObject<
|
||||
getStatus() {
|
||||
return this.status?.phase ?? "-";
|
||||
}
|
||||
|
||||
isSubnamespace() {
|
||||
return this.getAnnotations().find(annotation => annotation.includes("hnc.x-k8s.io/subnamespace-of"));
|
||||
}
|
||||
|
||||
isChildOf(parentName: string) {
|
||||
return this.getLabels().find(label => label === `${parentName}.tree.hnc.x-k8s.io/depth=1`);
|
||||
}
|
||||
|
||||
isControlledByHNC() {
|
||||
const hierarchicalNamesaceControllerLabel = "hnc.x-k8s.io/included-namespace=true";
|
||||
|
||||
return this.getLabels().find(label => label === hierarchicalNamesaceControllerLabel);
|
||||
}
|
||||
}
|
||||
|
||||
export class NamespaceApi extends KubeApi<Namespace> {
|
||||
|
||||
@@ -7,5 +7,3 @@
|
||||
export { applicationInformationToken } from "./vars/application-information-token";
|
||||
export type { ApplicationInformation } from "./vars/application-information-token";
|
||||
export { bundledExtensionInjectionToken } from "../extensions/extension-discovery/bundled-extension-token";
|
||||
|
||||
export * as extensionApi from "../extensions/common-api";
|
||||
@@ -3,20 +3,13 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { createLogger, format } from "winston";
|
||||
import type { Logger } from "./logger";
|
||||
import { loggerTransportInjectionToken } from "./logger/transports";
|
||||
import winstonLoggerInjectable from "./winston-logger.injectable";
|
||||
|
||||
const loggerInjectable = getInjectable({
|
||||
id: "logger",
|
||||
instantiate: (di): Logger => {
|
||||
const baseLogger = createLogger({
|
||||
format: format.combine(
|
||||
format.splat(),
|
||||
format.simple(),
|
||||
),
|
||||
transports: di.injectMany(loggerTransportInjectionToken),
|
||||
});
|
||||
const baseLogger = di.inject(winstonLoggerInjectable);
|
||||
|
||||
return {
|
||||
debug: (message, ...data) => baseLogger.debug(message, ...data),
|
||||
|
||||
@@ -23,7 +23,7 @@ export const defaultTerminalConfig: TerminalConfig = {
|
||||
fontFamily: defaultTerminalFontFamily,
|
||||
};
|
||||
|
||||
interface BaseEditorConfiguration extends Required<Pick<editor.IStandaloneEditorConstructionOptions, "minimap" | "tabSize" | "fontSize" | "fontFamily">> {
|
||||
export interface BaseEditorConfiguration extends Required<Pick<editor.IStandaloneEditorConstructionOptions, "minimap" | "tabSize" | "fontSize" | "fontFamily">> {
|
||||
lineNumbers: NonNullable<Exclude<editor.IStandaloneEditorConstructionOptions["lineNumbers"], Function>>;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { createLogger, format } from "winston";
|
||||
import { loggerTransportInjectionToken } from "./logger/transports";
|
||||
|
||||
const winstonLoggerInjectable = getInjectable({
|
||||
id: "winston-logger",
|
||||
instantiate: (di) => createLogger({
|
||||
format: format.combine(
|
||||
format.splat(),
|
||||
format.simple(),
|
||||
),
|
||||
transports: di.injectMany(loggerTransportInjectionToken),
|
||||
}),
|
||||
});
|
||||
|
||||
export default winstonLoggerInjectable;
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { runInAction } from "mobx";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake";
|
||||
import getHashInjectable from "../../extensions/extension-loader/file-system-provisioner-store/get-hash.injectable";
|
||||
import fsInjectable from "../../common/fs/fs.injectable";
|
||||
|
||||
describe("configurable directories for extension files", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
|
||||
beforeEach(async () => {
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
builder.beforeApplicationStart(
|
||||
(mainDi) => {
|
||||
runInAction(() => {
|
||||
mainDi.override(getHashInjectable, () => x => x);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await builder.startHidden();
|
||||
|
||||
const window = builder.applicationWindow.create("some-window-id");
|
||||
|
||||
await window.start();
|
||||
|
||||
});
|
||||
|
||||
describe("when extension with a specific store name is enabled", () => {
|
||||
let testExtensionOptions: FakeExtensionOptions;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
testExtensionOptions = {
|
||||
id: "some-extension",
|
||||
name: "some-extension-name",
|
||||
|
||||
mainOptions: {
|
||||
manifest: {
|
||||
name: "irrelevant",
|
||||
storeName: "some-specific-store-name",
|
||||
version: "0",
|
||||
engines: {
|
||||
lens: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
builder.extensions.enable(testExtensionOptions);
|
||||
});
|
||||
|
||||
it("creates extension directory for specific store name", async () => {
|
||||
const fs = builder.mainDi.inject(fsInjectable);
|
||||
|
||||
await builder.extensions.get("some-extension").main.getExtensionFileFolder();
|
||||
|
||||
const nonHashedExtensionDirectories = await fs.readdir("/some-directory-for-app-data/some-product-name/extension_data");
|
||||
|
||||
expect(nonHashedExtensionDirectories).toContain("some-specific-store-name");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when extension with no specific store name is enabled", () => {
|
||||
let testExtensionOptions: FakeExtensionOptions;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
testExtensionOptions = {
|
||||
id: "some-extension",
|
||||
name: "some-extension-name",
|
||||
|
||||
mainOptions: {
|
||||
manifest: {
|
||||
name: "some-package-name",
|
||||
storeName: undefined,
|
||||
version: "0",
|
||||
engines: {
|
||||
lens: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
builder.extensions.enable(testExtensionOptions);
|
||||
});
|
||||
|
||||
it("creates extension directory for package name", async () => {
|
||||
const fs = builder.mainDi.inject(fsInjectable);
|
||||
|
||||
await builder.extensions.get("some-extension").main.getExtensionFileFolder();
|
||||
|
||||
const nonHashedExtensionDirectories = await fs.readdir("/some-directory-for-app-data/some-product-name/extension_data");
|
||||
|
||||
expect(nonHashedExtensionDirectories).toContain("some-package-name");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,31 +3,21 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// APIs
|
||||
import { App } from "./app";
|
||||
import * as EventBus from "./event-bus";
|
||||
import * as Store from "./stores";
|
||||
import { Util } from "./utils";
|
||||
import * as Catalog from "./catalog";
|
||||
import * as Types from "./types";
|
||||
import * as Proxy from "./proxy";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { LensExtension, LensExtensionManifest } from "../lens-extension";
|
||||
|
||||
const logger = asLegacyGlobalForExtensionApi(loggerInjectable);
|
||||
// APIs
|
||||
export { App } from "./app";
|
||||
export * as EventBus from "./event-bus";
|
||||
export * as Store from "./stores";
|
||||
export * as Util from "./utils";
|
||||
export * as Catalog from "./catalog";
|
||||
export * as Types from "./types";
|
||||
export * as Proxy from "./proxy";
|
||||
|
||||
export {
|
||||
App,
|
||||
EventBus,
|
||||
Catalog,
|
||||
Store,
|
||||
Types,
|
||||
Util,
|
||||
logger,
|
||||
Proxy,
|
||||
Logger,
|
||||
LensExtension,
|
||||
LensExtensionManifest,
|
||||
};
|
||||
export type { Logger } from "../../common/logger";
|
||||
export type { LensExtension, LensExtensionManifest } from "../lens-extension";
|
||||
export type { InstalledExtension } from "../extension-discovery/extension-discovery";
|
||||
export type { PackageJson } from "type-fest";
|
||||
|
||||
export const logger = asLegacyGlobalForExtensionApi(loggerInjectable);
|
||||
@@ -108,13 +108,16 @@ export type { CreateKubeApiForLocalClusterConfig as ILocalKubeApiConfig } from "
|
||||
export {
|
||||
KubeObject,
|
||||
KubeStatus,
|
||||
type OwnerReference,
|
||||
type KubeObjectMetadata,
|
||||
type NamespaceScopedMetadata,
|
||||
type ClusterScopedMetadata,
|
||||
type BaseKubeJsonApiObjectMetadata,
|
||||
type KubeJsonApiObjectMetadata,
|
||||
type KubeStatusData,
|
||||
} from "../../common/k8s-api/kube-object";
|
||||
export type {
|
||||
OwnerReference,
|
||||
KubeObjectMetadata,
|
||||
NamespaceScopedMetadata,
|
||||
ClusterScopedMetadata,
|
||||
BaseKubeJsonApiObjectMetadata,
|
||||
KubeJsonApiObjectMetadata,
|
||||
KubeStatusData,
|
||||
KubeJsonApiDataFor,
|
||||
} from "../../common/k8s-api/kube-object";
|
||||
|
||||
export type {
|
||||
@@ -177,11 +180,11 @@ export abstract class KubeObjectStore<
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
type JsonPatch,
|
||||
type KubeObjectStoreLoadAllParams,
|
||||
type KubeObjectStoreLoadingParams,
|
||||
type KubeObjectStoreSubscribeParams,
|
||||
export type {
|
||||
JsonPatch,
|
||||
KubeObjectStoreLoadAllParams,
|
||||
KubeObjectStoreLoadingParams,
|
||||
KubeObjectStoreSubscribeParams,
|
||||
} from "../../common/k8s-api/kube-object.store";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration";
|
||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/kube-object-menu-registration";
|
||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-registration";
|
||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../../renderer/components/kube-object-details/kube-object-detail-registration";
|
||||
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
||||
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../../renderer/routes/page-registration";
|
||||
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../../renderer/components/layout/cluster-page-menu";
|
||||
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../../common/protocol-handler/registration";
|
||||
export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views";
|
||||
export type { ShellEnvModifier, ShellEnvContext } from "../../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
|
||||
export type { KubeObjectContextMenuItem, KubeObjectOnContextMenuOpenContext, KubeObjectOnContextMenuOpen, KubeObjectHandlers, KubeObjectHandlerRegistration } from "../../renderer/kube-object/handler";
|
||||
export type { TrayMenuRegistration } from "../../main/tray/tray-menu-registration";
|
||||
export type { MenuRegistration } from "../../features/application-menu/main/menu-registration";
|
||||
@@ -3,8 +3,21 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
|
||||
export type IpcMainInvokeEvent = Electron.IpcMainInvokeEvent;
|
||||
export type IpcRendererEvent = Electron.IpcRendererEvent;
|
||||
export type IpcMainEvent = Electron.IpcMainEvent;
|
||||
|
||||
export * from "./registrations";
|
||||
export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration";
|
||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/kube-object-menu-registration";
|
||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../features/preferences/renderer/compliance-for-legacy-extension-api/app-preference-registration";
|
||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../../renderer/components/kube-object-details/kube-object-detail-registration";
|
||||
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
||||
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../../renderer/routes/page-registration";
|
||||
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../../renderer/components/layout/cluster-page-menu";
|
||||
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../../common/protocol-handler/registration";
|
||||
export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views";
|
||||
export type { ShellEnvModifier, ShellEnvContext } from "../../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
|
||||
export type { KubeObjectContextMenuItem, KubeObjectOnContextMenuOpenContext, KubeObjectOnContextMenuOpen, KubeObjectHandlers, KubeObjectHandlerRegistration } from "../../renderer/kube-object/handler";
|
||||
export type { TrayMenuRegistration } from "../../main/tray/tray-menu-registration";
|
||||
export type { MenuRegistration } from "../../features/application-menu/main/menu-registration";
|
||||
@@ -7,20 +7,23 @@ import openLinkInBrowserInjectable from "../../common/utils/open-link-in-browser
|
||||
import buildVersionInjectable from "../../main/vars/build-version/build-version.injectable";
|
||||
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import { Singleton } from "../../common/utils";
|
||||
import { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault";
|
||||
import { cssNames } from "../../renderer/utils/cssNames";
|
||||
|
||||
export const Util = {
|
||||
Singleton,
|
||||
prevDefault,
|
||||
stopPropagation,
|
||||
cssNames,
|
||||
openExternal: asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable),
|
||||
openBrowser: asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable),
|
||||
getAppVersion: () => {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
export { Singleton } from "../../common/utils";
|
||||
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault";
|
||||
|
||||
return di.inject(buildVersionInjectable).get();
|
||||
},
|
||||
} as const;
|
||||
export type { IClassName, IgnoredClassNames } from "../../renderer/utils";
|
||||
export { cssNames } from "../../renderer/utils/cssNames";
|
||||
|
||||
export type { OpenLinkInBrowser } from "../../common/utils/open-link-in-browser.injectable";
|
||||
|
||||
export const openExternal = asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable);
|
||||
export const openBrowser = asLegacyGlobalFunctionForExtensionApi(openLinkInBrowserInjectable);
|
||||
|
||||
export const getAppVersion = () => {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
return di.inject(buildVersionInjectable).get();
|
||||
};
|
||||
|
||||
export { disposer } from "../../common/utils";
|
||||
export type { Disposer, Disposable, ExtendableDisposer } from "../../common/utils";
|
||||
@@ -216,9 +216,6 @@ export class ExtensionDiscovery {
|
||||
const extension = await this.loadExtensionFromFolder(absPath);
|
||||
|
||||
if (extension) {
|
||||
// Remove a broken symlink left by a previous installation if it exists.
|
||||
await this.dependencies.removePath(extension.manifestPath);
|
||||
|
||||
// Install dependencies for the new extension
|
||||
await this.dependencies.installExtension(extension.absolutePath);
|
||||
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
import { getOrInsertWithAsync } from "../../../common/utils";
|
||||
import randomBytesInjectable from "../../../common/utils/random-bytes.injectable";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
||||
import ensureDirInjectable from "../../../common/fs/ensure-dir.injectable";
|
||||
import getHashInjectable from "./get-hash.injectable";
|
||||
|
||||
export type EnsureHashedDirectoryForExtension = (extensionName: string, registeredExtensions: ObservableMap<string, string>) => Promise<string>;
|
||||
|
||||
const ensureHashedDirectoryForExtensionInjectable = getInjectable({
|
||||
id: "ensure-hashed-directory-for-extension",
|
||||
|
||||
instantiate: (di): EnsureHashedDirectoryForExtension => {
|
||||
const randomBytes = di.inject(randomBytesInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForExtensionData = di.inject(directoryForExtensionDataInjectable);
|
||||
const ensureDirectory = di.inject(ensureDirInjectable);
|
||||
const getHash = di.inject(getHashInjectable);
|
||||
|
||||
return async (extensionName, registeredExtensions) => {
|
||||
const dirPath = await getOrInsertWithAsync(registeredExtensions, extensionName, async () => {
|
||||
const salt = (await randomBytes(32)).toString("hex");
|
||||
const hashedName = getHash(`${extensionName}/${salt}`);
|
||||
|
||||
return joinPaths(directoryForExtensionData, hashedName);
|
||||
});
|
||||
|
||||
await ensureDirectory(dirPath);
|
||||
|
||||
return dirPath;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default ensureHashedDirectoryForExtensionInjectable;
|
||||
+2
-8
@@ -4,10 +4,6 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { FileSystemProvisionerStore } from "./file-system-provisioner-store";
|
||||
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
||||
import ensureDirectoryInjectable from "../../../common/fs/ensure-dir.injectable";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
import randomBytesInjectable from "../../../common/utils/random-bytes.injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
@@ -18,15 +14,12 @@ import getBasenameOfPathInjectable from "../../../common/path/get-basename.injec
|
||||
import { enlistMessageChannelListenerInjectionToken } from "../../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||
import fileSystemProvisionerStoreMigrationVersionInjectable from "./migration-version.injectable";
|
||||
import { sendMessageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token";
|
||||
import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable";
|
||||
|
||||
const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||
id: "file-system-provisioner-store",
|
||||
|
||||
instantiate: (di) => new FileSystemProvisionerStore({
|
||||
directoryForExtensionData: di.inject(directoryForExtensionDataInjectable),
|
||||
ensureDirectory: di.inject(ensureDirectoryInjectable),
|
||||
joinPaths: di.inject(joinPathsInjectable),
|
||||
randomBytes: di.inject(randomBytesInjectable),
|
||||
directoryForUserData: di.inject(directoryForUserDataInjectable),
|
||||
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
@@ -38,6 +31,7 @@ const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||
sendMessageToChannel: di.inject(sendMessageToChannelInjectionToken),
|
||||
ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
+4
-19
@@ -3,25 +3,19 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { SHA256 } from "crypto-js";
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import type { BaseStoreDependencies } from "../../../common/base-store/base-store";
|
||||
import { BaseStore } from "../../../common/base-store/base-store";
|
||||
import type { LensExtensionId } from "../../lens-extension";
|
||||
import { getOrInsertWithAsync, toJS } from "../../../common/utils";
|
||||
import type { EnsureDirectory } from "../../../common/fs/ensure-dir.injectable";
|
||||
import type { JoinPaths } from "../../../common/path/join-paths.injectable";
|
||||
import type { RandomBytes } from "../../../common/utils/random-bytes.injectable";
|
||||
import { toJS } from "../../../common/utils";
|
||||
import type { EnsureHashedDirectoryForExtension } from "./ensure-hashed-directory-for-extension.injectable";
|
||||
|
||||
interface FSProvisionModel {
|
||||
extensions: Record<string, string>; // extension names to paths
|
||||
}
|
||||
|
||||
interface Dependencies extends BaseStoreDependencies {
|
||||
readonly directoryForExtensionData: string;
|
||||
ensureDirectory: EnsureDirectory;
|
||||
joinPaths: JoinPaths;
|
||||
randomBytes: RandomBytes;
|
||||
ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension;
|
||||
}
|
||||
|
||||
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||
@@ -43,16 +37,7 @@ export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||
* @returns path to the folder that the extension can safely write files to.
|
||||
*/
|
||||
async requestDirectory(extensionName: string): Promise<string> {
|
||||
const dirPath = await getOrInsertWithAsync(this.registeredExtensions, extensionName, async () => {
|
||||
const salt = (await this.dependencies.randomBytes(32)).toString("hex");
|
||||
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
|
||||
|
||||
return this.dependencies.joinPaths(this.dependencies.directoryForExtensionData, hashedName);
|
||||
});
|
||||
|
||||
await this.dependencies.ensureDirectory(dirPath);
|
||||
|
||||
return dirPath;
|
||||
return this.dependencies.ensureHashedDirectoryForExtension(extensionName, this.registeredExtensions);
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 { SHA256 } from "crypto-js";
|
||||
|
||||
const getHashInjectable = getInjectable({
|
||||
id: "get-hash",
|
||||
|
||||
instantiate: () => (text: string) => SHA256(text).toString(),
|
||||
});
|
||||
|
||||
export default getHashInjectable;
|
||||
@@ -101,6 +101,6 @@ export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
||||
protected cwd() {
|
||||
assert(this.extension, "must call this.load() first");
|
||||
|
||||
return path.join(super.cwd(), "extension-store", this.extension.name);
|
||||
return path.join(super.cwd(), "extension-store", this.extension.storeName);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { action, computed, makeObservable, observable } from "mobx";
|
||||
import type { PackageJson } from "type-fest";
|
||||
import { disposer } from "../common/utils";
|
||||
import type { LensExtensionDependencies } from "./lens-extension-set-dependencies";
|
||||
import type { ProtocolHandlerRegistration } from "./common-api/registrations";
|
||||
import type { ProtocolHandlerRegistration } from "../common/protocol-handler/registration";
|
||||
|
||||
export type LensExtensionId = string; // path to manifest (package.json)
|
||||
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
||||
@@ -27,6 +27,10 @@ export interface LensExtensionManifest extends PackageJson {
|
||||
npm?: string;
|
||||
node?: string;
|
||||
};
|
||||
|
||||
// Specify extension name used for persisting data.
|
||||
// Useful if extension is renamed but the data should not be lost.
|
||||
storeName?: string;
|
||||
}
|
||||
|
||||
export const lensExtensionDependencies = Symbol("lens-extension-dependencies");
|
||||
@@ -62,7 +66,10 @@ export class LensExtension<
|
||||
|
||||
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
|
||||
makeObservable(this);
|
||||
|
||||
// id is the name of the manifest
|
||||
this.id = id;
|
||||
|
||||
this.manifest = manifest;
|
||||
this.manifestPath = manifestPath;
|
||||
this.isBundled = !!isBundled;
|
||||
@@ -80,6 +87,11 @@ export class LensExtension<
|
||||
return this.manifest.description;
|
||||
}
|
||||
|
||||
// Name of extension for persisting data
|
||||
get storeName() {
|
||||
return this.manifest.storeName || this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@@ -93,7 +105,8 @@ export class LensExtension<
|
||||
* folder name.
|
||||
*/
|
||||
async getExtensionFileFolder(): Promise<string> {
|
||||
return this[lensExtensionDependencies].fileSystemProvisionerStore.requestDirectory(this.id);
|
||||
// storeName is read from the manifest and has a fallback to the manifest name, which equals id
|
||||
return this[lensExtensionDependencies].fileSystemProvisionerStore.requestDirectory(this.storeName);
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@@ -29,7 +29,8 @@ import type { KubeObjectDetailRegistration } from "../renderer/components/kube-o
|
||||
import type { ClusterFrameChildComponent } from "../renderer/frames/cluster-frame/cluster-frame-child-component-injection-token";
|
||||
import type { EntitySettingRegistration } from "../renderer/components/+entity-settings/extension-registrator.injectable";
|
||||
import type { CatalogEntityDetailRegistration } from "../renderer/components/+catalog/entity-details/token";
|
||||
import type { ClusterPageMenuRegistration, PageRegistration } from "./common-api/registrations";
|
||||
import type { PageRegistration } from "../renderer/routes/page-registration";
|
||||
import type { ClusterPageMenuRegistration } from "../renderer/components/layout/cluster-page-menu";
|
||||
|
||||
export class LensRendererExtension extends LensExtension<LensRendererExtensionDependencies> {
|
||||
globalPages: PageRegistration[] = [];
|
||||
|
||||
+169
-4
@@ -9797,11 +9797,11 @@ exports[`cluster/namespaces - edit namespace from new tab when navigating to nam
|
||||
class="footer"
|
||||
>
|
||||
<div
|
||||
class="Dock"
|
||||
class="Dock isOpen"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="ResizingAnchor vertical leading disabled"
|
||||
class="ResizingAnchor vertical leading"
|
||||
/>
|
||||
<div
|
||||
class="tabs-container flex align-center"
|
||||
@@ -9812,10 +9812,63 @@ exports[`cluster/namespaces - edit namespace from new tab when navigating to nam
|
||||
>
|
||||
<div
|
||||
class="Tabs tabs"
|
||||
/>
|
||||
>
|
||||
<div
|
||||
class="Tab flex gaps align-center DockTab active"
|
||||
data-testid="dock-tab-for-some-first-tab-id"
|
||||
id="tab-some-first-tab-id"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable small"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="edit"
|
||||
>
|
||||
edit
|
||||
</span>
|
||||
</i>
|
||||
<div
|
||||
class="label"
|
||||
>
|
||||
<div
|
||||
class="flex align-center"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Namespace: some-name
|
||||
</span>
|
||||
<div
|
||||
class="close"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable small"
|
||||
data-testid="dock-tab-close-for-some-first-tab-id"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="close"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</i>
|
||||
<div
|
||||
data-testid="tooltip-content-for-dock-tab-close-for-some-first-tab-id"
|
||||
>
|
||||
Close ⌘+W
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="toolbar flex gaps align-center box grow pl-0"
|
||||
class="toolbar flex gaps align-center box grow"
|
||||
>
|
||||
<div
|
||||
class="dock-menu box grow"
|
||||
@@ -9836,6 +9889,118 @@ exports[`cluster/namespaces - edit namespace from new tab when navigating to nam
|
||||
New tab
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="fullscreen"
|
||||
>
|
||||
fullscreen
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Fit to window
|
||||
</div>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Minimize
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-content edit-resource"
|
||||
data-testid="dock-tab-content-for-some-first-tab-id"
|
||||
style="flex-basis: 300px;"
|
||||
>
|
||||
<div
|
||||
class="EditResource flex column"
|
||||
>
|
||||
<div
|
||||
class="InfoPanel flex gaps align-center"
|
||||
>
|
||||
<div
|
||||
class="controls"
|
||||
>
|
||||
<div
|
||||
class="resource-info flex gaps align-center"
|
||||
>
|
||||
<span>
|
||||
Kind:
|
||||
</span>
|
||||
<div
|
||||
class="badge"
|
||||
>
|
||||
Namespace
|
||||
</div>
|
||||
<span>
|
||||
Name:
|
||||
</span>
|
||||
<div
|
||||
class="badge"
|
||||
>
|
||||
some-name
|
||||
</div>
|
||||
<span>
|
||||
Namespace:
|
||||
</span>
|
||||
<div
|
||||
class="badge"
|
||||
>
|
||||
default
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex gaps align-center"
|
||||
/>
|
||||
<button
|
||||
class="Button plain"
|
||||
data-testid="cancel-edit-resource-from-tab-for-some-first-tab-id"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="Button active outlined"
|
||||
data-testid="save-edit-resource-from-tab-for-some-first-tab-id"
|
||||
type="button"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
class="Button primary active"
|
||||
data-testid="save-and-close-edit-resource-from-tab-for-some-first-tab-id"
|
||||
type="button"
|
||||
>
|
||||
Save & Close
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
data-testid="monaco-editor-for-some-first-tab-id"
|
||||
>
|
||||
apiVersion: some-api-version
|
||||
kind: Namespace
|
||||
metadata:
|
||||
uid: some-uid
|
||||
name: some-name
|
||||
resourceVersion: some-resource-version
|
||||
selfLink: /apis/some-api-version/namespaces/some-uid
|
||||
somePropertyToBeRemoved: some-value
|
||||
somePropertyToBeChanged: some-old-value
|
||||
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -401,8 +401,7 @@ metadata:
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// TODO: Not doable at the moment because info panel controls closing of the tab
|
||||
xit("does not close the dock tab", () => {
|
||||
it("does not close the dock tab", () => {
|
||||
expect(
|
||||
rendered.getByTestId("dock-tab-for-some-first-tab-id"),
|
||||
).toBeInTheDocument();
|
||||
|
||||
+3
@@ -443,6 +443,9 @@ exports[`extensions - navigation using application menu when navigating to exten
|
||||
: you can drag and drop a tarball file to this area
|
||||
</small>
|
||||
</section>
|
||||
<div
|
||||
class="size-md"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
class="Spinner singleColor center"
|
||||
|
||||
@@ -11,7 +11,7 @@ const apiBaseHostHeaderInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const lensProxyPort = di.inject(lensProxyPortInjectable);
|
||||
|
||||
return `localhost:${lensProxyPort.get()}`;
|
||||
return `lens.app:${lensProxyPort.get()}`;
|
||||
},
|
||||
injectionToken: apiBaseHostHeaderInjectionToken,
|
||||
});
|
||||
|
||||
+1
-2
@@ -2,7 +2,6 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { SelfSignedCert } from "selfsigned";
|
||||
import { lensProxyCertificateChannel } from "../../common/certificate/lens-proxy-certificate-channel";
|
||||
import { getRequestChannelListenerInjectable } from "../utils/channel/channel-listeners/listener-tokens";
|
||||
import lensProxyCertificateInjectable from "../../common/certificate/lens-proxy-certificate.injectable";
|
||||
@@ -10,7 +9,7 @@ import lensProxyCertificateInjectable from "../../common/certificate/lens-proxy-
|
||||
const lensProxyCertificateRequestHandlerInjectable = getRequestChannelListenerInjectable({
|
||||
channel: lensProxyCertificateChannel,
|
||||
handler: (di) => {
|
||||
const lensProxyCertificate = di.inject(lensProxyCertificateInjectable).get() as SelfSignedCert;
|
||||
const lensProxyCertificate = di.inject(lensProxyCertificateInjectable).get();
|
||||
|
||||
return () => ({
|
||||
cert: lensProxyCertificate.cert,
|
||||
|
||||
@@ -10,5 +10,6 @@ export { beforeElectronIsReadyInjectionToken } from "./start-main-application/ru
|
||||
export { onLoadOfApplicationInjectionToken } from "./start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
||||
export { createApp } from "./create-app";
|
||||
export * as Mobx from "mobx";
|
||||
export * as extensionApi from "../extensions/main-api";
|
||||
export * as mainExtensionApi from "../extensions/main-api";
|
||||
export * as commonExtensionApi from "../extensions/common-api";
|
||||
export * as Pty from "node-pty";
|
||||
+15
-7
@@ -27,6 +27,12 @@ export interface UrlSource {
|
||||
}
|
||||
export type ContentSource = RequireExactlyOne<FileSource & UrlSource>;
|
||||
|
||||
enum ChromiumNetError {
|
||||
SUCCESS = 0,
|
||||
FAILURE = 1,
|
||||
RESULT_FROM_CHROMIUM,
|
||||
}
|
||||
|
||||
export interface ElectronWindowConfiguration {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -112,6 +118,15 @@ const createElectronWindowInjectable = getInjectable({
|
||||
|
||||
applicationWindowState.manage(browserWindow);
|
||||
|
||||
browserWindow.webContents.session.setCertificateVerifyProc((request, shouldBeTrusted) => {
|
||||
const { certificate } = request;
|
||||
const cert = new X509Certificate(certificate.data);
|
||||
const shouldTrustCert = cert.raw.length === lensProxyX509Cert.raw.length
|
||||
&& timingSafeEqual(cert.raw, lensProxyX509Cert.raw);
|
||||
|
||||
shouldBeTrusted(shouldTrustCert ? ChromiumNetError.SUCCESS : ChromiumNetError.RESULT_FROM_CHROMIUM);
|
||||
});
|
||||
|
||||
browserWindow
|
||||
.on("focus", () => {
|
||||
configuration.onFocus?.();
|
||||
@@ -126,13 +141,6 @@ const createElectronWindowInjectable = getInjectable({
|
||||
.webContents.on("dom-ready", () => {
|
||||
configuration.onDomReady?.();
|
||||
})
|
||||
.on("certificate-error", (event, url, error, certificate, shouldBeTrusted) => {
|
||||
const cert = new X509Certificate(certificate.data);
|
||||
const shouldTrustCert = cert.raw.length === lensProxyX509Cert.raw.length
|
||||
&& timingSafeEqual(cert.raw, lensProxyX509Cert.raw);
|
||||
|
||||
shouldBeTrusted(shouldTrustCert);
|
||||
})
|
||||
.on("did-fail-load", (_event, code, desc) => {
|
||||
logger.error(
|
||||
`[CREATE-ELECTRON-WINDOW]: Failed to load window "${configuration.id}"`,
|
||||
|
||||
@@ -54,7 +54,9 @@ export async function bootstrap(di: DiContainer) {
|
||||
}
|
||||
|
||||
try {
|
||||
await initializeApp(() => unmountComponentAtNode(rootElem));
|
||||
await initializeApp(() => {
|
||||
unmountComponentAtNode(rootElem);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[BOOTSTRAP]: view initialization error: ${error}`, {
|
||||
origin: location.href,
|
||||
|
||||
@@ -37,6 +37,7 @@ import installOnDropInjectable from "./install-on-drop.injectable";
|
||||
import { supportedExtensionFormats } from "./supported-extension-formats";
|
||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import Gutter from "../gutter/gutter";
|
||||
|
||||
interface Dependencies {
|
||||
userExtensions: IComputedValue<InstalledExtension[]>;
|
||||
@@ -113,6 +114,8 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
|
||||
installPath={this.installPath}
|
||||
/>
|
||||
|
||||
<Gutter size="md" />
|
||||
|
||||
<InstalledExtensions
|
||||
extensions={userExtensions}
|
||||
enable={this.props.enableExtension}
|
||||
|
||||
+937
@@ -0,0 +1,937 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<NamespaceTreeView /> collapses item by clicking minus button 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="TreeView"
|
||||
data-testid="namespace-tree-view"
|
||||
>
|
||||
<div
|
||||
class="DrawerTitle title"
|
||||
>
|
||||
Tree View
|
||||
</div>
|
||||
<ul
|
||||
aria-multiselectable="false"
|
||||
class="MuiTreeView-root"
|
||||
role="tree"
|
||||
>
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-levels-deep"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
levels-deep
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
|
||||
role="group"
|
||||
style="min-height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-child-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
aria-expanded="false"
|
||||
class="MuiTreeItem-root"
|
||||
data-testid="namespace-level-deep-child-b"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="plus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 12.977h-4.923v4.896q0 .401-.281.682t-.682.281v0q-.375 0-.669-.281t-.294-.682v-4.896h-4.923q-.401 0-.682-.294t-.281-.669v0q0-.401.281-.682t.682-.281h4.923v-4.896q0-.401.294-.682t.669-.281v0q.401 0 .682.281t.281.682v4.896h4.923q.401 0 .682.281t.281.682v0q0 .375-.281.669t-.682.294z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-child-b
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group group"
|
||||
role="group"
|
||||
style="min-height: 0px; height: 0px; transition-duration: 300ms;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-subchild-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-subchild-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`<NamespaceTreeView /> expands item by clicking plus button 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="TreeView"
|
||||
data-testid="namespace-tree-view"
|
||||
>
|
||||
<div
|
||||
class="DrawerTitle title"
|
||||
>
|
||||
Tree View
|
||||
</div>
|
||||
<ul
|
||||
aria-multiselectable="false"
|
||||
class="MuiTreeView-root"
|
||||
role="tree"
|
||||
>
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-levels-deep"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
levels-deep
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
|
||||
role="group"
|
||||
style="min-height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-child-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-b"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-child-b
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group group"
|
||||
role="group"
|
||||
style="min-height: 0px; height: 0px; transition-duration: 300ms;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-subchild-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-subchild-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`<NamespaceTreeView /> renders 2 levels deep 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="TreeView"
|
||||
data-testid="namespace-tree-view"
|
||||
>
|
||||
<div
|
||||
class="DrawerTitle title"
|
||||
>
|
||||
Tree View
|
||||
</div>
|
||||
<ul
|
||||
aria-multiselectable="false"
|
||||
class="MuiTreeView-root"
|
||||
role="tree"
|
||||
>
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-levels-deep"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
levels-deep
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
|
||||
role="group"
|
||||
style="min-height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-child-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-child-b"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-child-b
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
|
||||
role="group"
|
||||
style="min-height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-level-deep-subchild-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
level-deep-subchild-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`<NamespaceTreeView /> renders namespace with 2 children namespaces 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="TreeView"
|
||||
data-testid="namespace-tree-view"
|
||||
>
|
||||
<div
|
||||
class="DrawerTitle title"
|
||||
>
|
||||
Tree View
|
||||
</div>
|
||||
<ul
|
||||
aria-multiselectable="false"
|
||||
class="MuiTreeView-root"
|
||||
role="tree"
|
||||
>
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-acme-org"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
acme-org
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
|
||||
role="group"
|
||||
style="min-height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-team-a"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
team-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-team-b"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
team-b
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`<NamespaceTreeView /> renders namespace with children namespaces and a subnamespace 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="TreeView"
|
||||
data-testid="namespace-tree-view"
|
||||
>
|
||||
<div
|
||||
class="DrawerTitle title"
|
||||
>
|
||||
Tree View
|
||||
</div>
|
||||
<ul
|
||||
aria-multiselectable="false"
|
||||
class="MuiTreeView-root"
|
||||
role="tree"
|
||||
>
|
||||
<li
|
||||
aria-expanded="true"
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-org-a"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
org-a
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="MuiCollapse-root MuiTreeItem-group group MuiCollapse-entered"
|
||||
role="group"
|
||||
style="min-height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapper"
|
||||
>
|
||||
<div
|
||||
class="MuiCollapse-wrapperInner"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-team-c"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
team-c
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="MuiTreeItem-root Mui-expanded"
|
||||
data-testid="namespace-service-1"
|
||||
role="treeitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
service-1
|
||||
|
||||
<span
|
||||
class="subnamespaceBadge"
|
||||
data-testid="namespace-details-badge-for-service-1"
|
||||
id="namespace-details-badge-for-service-1"
|
||||
>
|
||||
S
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`<NamespaceTreeView /> renders one namespace without children 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="TreeView"
|
||||
data-testid="namespace-tree-view"
|
||||
>
|
||||
<div
|
||||
class="DrawerTitle title"
|
||||
>
|
||||
Tree View
|
||||
</div>
|
||||
<ul
|
||||
aria-multiselectable="false"
|
||||
class="MuiTreeView-root"
|
||||
role="tree"
|
||||
>
|
||||
<li
|
||||
class="MuiTreeItem-root"
|
||||
data-testid="namespace-single-root"
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-content"
|
||||
>
|
||||
<div
|
||||
class="MuiTreeItem-iconContainer"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0.3;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
data-testid="minus-square"
|
||||
focusable="false"
|
||||
style="width: 14px; height: 14px;"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiTypography-root MuiTreeItem-label label MuiTypography-body1"
|
||||
>
|
||||
single-root
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
+18
@@ -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 namespaceStoreInjectable from "./store.injectable";
|
||||
|
||||
const hierarchicalNamespacesInjectable = getInjectable({
|
||||
id: "hierarchical-namespaces",
|
||||
|
||||
instantiate: (di) => {
|
||||
const namespaceStore = di.inject(namespaceStoreInjectable);
|
||||
|
||||
return namespaceStore.items.filter(item => item.isControlledByHNC());
|
||||
},
|
||||
});
|
||||
|
||||
export default hierarchicalNamespacesInjectable;
|
||||
@@ -26,6 +26,9 @@ import limitRangeStoreInjectable from "../+config-limit-ranges/store.injectable"
|
||||
import resourceQuotaStoreInjectable from "../+config-resource-quotas/store.injectable";
|
||||
import type { Logger } from "../../../common/logger";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import { NamespaceTreeView } from "./namespace-tree-view";
|
||||
import namespaceStoreInjectable from "./store.injectable";
|
||||
import type { NamespaceStore } from "./store";
|
||||
|
||||
export interface NamespaceDetailsProps extends KubeObjectDetailsProps<Namespace> {
|
||||
}
|
||||
@@ -35,6 +38,7 @@ interface Dependencies {
|
||||
getDetailsUrl: GetDetailsUrl;
|
||||
resourceQuotaStore: ResourceQuotaStore;
|
||||
limitRangeStore: LimitRangeStore;
|
||||
namespaceStore: NamespaceStore;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
@@ -103,6 +107,10 @@ class NonInjectedNamespaceDetails extends React.Component<NamespaceDetailsProps
|
||||
</Link>
|
||||
))}
|
||||
</DrawerItem>
|
||||
|
||||
{namespace.isControlledByHNC() && (
|
||||
<NamespaceTreeView tree={this.props.namespaceStore.getNamespaceTree(namespace)}/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -115,6 +123,7 @@ export const NamespaceDetails = withInjectables<Dependencies, NamespaceDetailsPr
|
||||
getDetailsUrl: di.inject(getDetailsUrlInjectable),
|
||||
limitRangeStore: di.inject(limitRangeStoreInjectable),
|
||||
resourceQuotaStore: di.inject(resourceQuotaStoreInjectable),
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* 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 { observable } from "mobx";
|
||||
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { Namespace } from "../../../common/k8s-api/endpoints";
|
||||
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
|
||||
import createClusterInjectable from "../../cluster/create-cluster.injectable";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
|
||||
import type { NamespaceStore } from "./store";
|
||||
import namespaceStoreInjectable from "./store.injectable";
|
||||
|
||||
function createNamespace(name: string, labels?: Record<string, string>, annotations?: Record<string, string>): Namespace {
|
||||
return new Namespace({
|
||||
apiVersion: "v1",
|
||||
kind: "Namespace",
|
||||
metadata: {
|
||||
name,
|
||||
resourceVersion: "1",
|
||||
selfLink: `/api/v1/namespaces/${name}`,
|
||||
uid: `${name}`,
|
||||
labels: {
|
||||
...labels,
|
||||
},
|
||||
annotations: {
|
||||
...annotations,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const singleRoot = createNamespace("single-root", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
});
|
||||
|
||||
const acmeGroup = createNamespace("acme-org", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
});
|
||||
|
||||
const orgA = createNamespace("org-a", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
});
|
||||
|
||||
const teamA = createNamespace("team-a", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"acme-org.tree.hnc.x-k8s.io/depth": "1",
|
||||
"kubernetes.io/metadata.name": "team-a",
|
||||
"team-a.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const teamB = createNamespace("team-b", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"acme-org.tree.hnc.x-k8s.io/depth": "1",
|
||||
"kubernetes.io/metadata.name": "team-b",
|
||||
"team-b.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const teamC = createNamespace("team-c", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"org-a.tree.hnc.x-k8s.io/depth": "1",
|
||||
"kubernetes.io/metadata.name": "team-c",
|
||||
"team-c.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const service1 = createNamespace("service-1", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"org-a.tree.hnc.x-k8s.io/depth": "1",
|
||||
"kubernetes.io/metadata.name": "team-c",
|
||||
"service-1.tree.hnc.x-k8s.io/depth": "0",
|
||||
}, {
|
||||
"hnc.x-k8s.io/subnamespace-of": "org-a",
|
||||
});
|
||||
|
||||
const levelsDeep = createNamespace("levels-deep", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
});
|
||||
|
||||
const levelDeepChildA = createNamespace("level-deep-child-a", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"levels-deep.tree.hnc.x-k8s.io/depth": "1",
|
||||
"level-deep-child-a.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const levelDeepChildB = createNamespace("level-deep-child-b", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"levels-deep.tree.hnc.x-k8s.io/depth": "1",
|
||||
"level-deep-child-b.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const levelDeepSubChildA = createNamespace("level-deep-subchild-a", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"levels-deep.tree.hnc.x-k8s.io/depth": "2",
|
||||
"level-deep-child-b.tree.hnc.x-k8s.io/depth": "1",
|
||||
"level-deep-subchild-a.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
|
||||
describe("NamespaceStore", () => {
|
||||
let di: DiContainer;
|
||||
let namespaceStore: NamespaceStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
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",
|
||||
}));
|
||||
|
||||
namespaceStore = di.inject(namespaceStoreInjectable);
|
||||
|
||||
namespaceStore.items = observable.array([
|
||||
acmeGroup,
|
||||
orgA,
|
||||
teamA,
|
||||
teamB,
|
||||
teamC,
|
||||
service1,
|
||||
levelsDeep,
|
||||
levelDeepChildA,
|
||||
levelDeepChildB,
|
||||
levelDeepSubChildA,
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns tree for single node", () => {
|
||||
const tree = namespaceStore.getNamespaceTree(service1);
|
||||
|
||||
expect(tree).toEqual({
|
||||
id: "service-1",
|
||||
namespace: service1,
|
||||
children: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("returns tree for namespace not listed in store", () => {
|
||||
const tree = namespaceStore.getNamespaceTree(singleRoot);
|
||||
|
||||
expect(tree).toEqual({
|
||||
id: "single-root",
|
||||
namespace: singleRoot,
|
||||
children: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("return tree for namespace with children", () => {
|
||||
const tree = namespaceStore.getNamespaceTree(acmeGroup);
|
||||
|
||||
expect(tree).toEqual({
|
||||
id: "acme-org",
|
||||
namespace: acmeGroup,
|
||||
children: [
|
||||
{
|
||||
id: "team-a",
|
||||
namespace: teamA,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: "team-b",
|
||||
namespace: teamB,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("return tree for namespace with deep nested children", () => {
|
||||
const tree = namespaceStore.getNamespaceTree(levelsDeep);
|
||||
|
||||
expect(tree).toEqual({
|
||||
id: "levels-deep",
|
||||
namespace: levelsDeep,
|
||||
children: [
|
||||
{
|
||||
id: "level-deep-child-a",
|
||||
namespace: levelDeepChildA,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: "level-deep-child-b",
|
||||
namespace: levelDeepChildB,
|
||||
children: [{
|
||||
id: "level-deep-subchild-a",
|
||||
namespace: levelDeepSubChildA,
|
||||
children: [],
|
||||
}],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
.TreeView {
|
||||
.group {
|
||||
margin-inline-start: var(--margin);
|
||||
padding-inline-start: calc(var(--padding) * 2);
|
||||
border-inline-start: 1px dashed var(--borderColor);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: inherit;
|
||||
line-height: 1.8;
|
||||
cursor: default;
|
||||
background-color: transparent!important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* 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 { fireEvent } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { Namespace } from "../../../common/k8s-api/endpoints";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../test-utils/renderFor";
|
||||
import { renderFor } from "../test-utils/renderFor";
|
||||
import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable";
|
||||
import { NamespaceTreeView } from "./namespace-tree-view";
|
||||
import type { NamespaceTree } from "./store";
|
||||
|
||||
jest.mock("react-router-dom", () => ({
|
||||
Link: ({ children }: { children: React.ReactNode }) => children,
|
||||
}));
|
||||
|
||||
function createNamespace(name: string, labels?: Record<string, string>, annotations?: Record<string, string>): Namespace {
|
||||
return new Namespace({
|
||||
apiVersion: "v1",
|
||||
kind: "Namespace",
|
||||
metadata: {
|
||||
name,
|
||||
resourceVersion: "1",
|
||||
selfLink: `/api/v1/namespaces/${name}`,
|
||||
uid: `${name}`,
|
||||
labels: {
|
||||
...labels,
|
||||
},
|
||||
annotations: {
|
||||
...annotations,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const singleRoot = createNamespace("single-root", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
});
|
||||
|
||||
const acmeGroup = createNamespace("acme-org", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
});
|
||||
|
||||
const orgA = createNamespace("org-a", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
});
|
||||
|
||||
const teamA = createNamespace("team-a", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"acme-org.tree.hnc.x-k8s.io/depth": "1",
|
||||
"kubernetes.io/metadata.name": "team-a",
|
||||
"team-a.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const teamB = createNamespace("team-b", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"acme-org.tree.hnc.x-k8s.io/depth": "1",
|
||||
"kubernetes.io/metadata.name": "team-b",
|
||||
"team-b.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const teamC = createNamespace("team-c", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"org-a.tree.hnc.x-k8s.io/depth": "1",
|
||||
"kubernetes.io/metadata.name": "team-c",
|
||||
"team-c.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const service1 = createNamespace("service-1", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"org-a.tree.hnc.x-k8s.io/depth": "1",
|
||||
"kubernetes.io/metadata.name": "team-c",
|
||||
"service-1.tree.hnc.x-k8s.io/depth": "0",
|
||||
}, {
|
||||
"hnc.x-k8s.io/subnamespace-of": "org-a",
|
||||
});
|
||||
|
||||
const levelsDeep = createNamespace("levels-deep", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
});
|
||||
|
||||
const levelDeepChildA = createNamespace("level-deep-child-a", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"levels-deep.tree.hnc.x-k8s.io/depth": "1",
|
||||
"level-deep-child-a.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const levelDeepChildB = createNamespace("level-deep-child-b", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"levels-deep.tree.hnc.x-k8s.io/depth": "1",
|
||||
"level-deep-child-b.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
const levelDeepSubChildA = createNamespace("level-deep-subchild-a", {
|
||||
"hnc.x-k8s.io/included-namespace": "true",
|
||||
"levels-deep.tree.hnc.x-k8s.io/depth": "2",
|
||||
"level-deep-child-b.tree.hnc.x-k8s.io/depth": "1",
|
||||
"level-deep-subchild-a.tree.hnc.x-k8s.io/depth": "0",
|
||||
});
|
||||
|
||||
describe("<NamespaceTreeView />", () => {
|
||||
let di: DiContainer;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(async () => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(hierarchicalNamespacesInjectable, () => [
|
||||
acmeGroup,
|
||||
orgA,
|
||||
teamA,
|
||||
teamB,
|
||||
teamC,
|
||||
service1,
|
||||
levelsDeep,
|
||||
levelDeepChildA,
|
||||
levelDeepChildB,
|
||||
levelDeepSubChildA,
|
||||
]);
|
||||
|
||||
render = renderFor(di);
|
||||
});
|
||||
|
||||
it("renders one namespace without children", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "single-root",
|
||||
namespace: singleRoot,
|
||||
};
|
||||
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders namespace with 2 children namespaces", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "acme-org",
|
||||
namespace: acmeGroup,
|
||||
children: [
|
||||
{
|
||||
id: "team-a",
|
||||
namespace: teamA,
|
||||
},
|
||||
{
|
||||
id: "team-b",
|
||||
namespace: teamB,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders namespace with children namespaces and a subnamespace", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "org-a",
|
||||
namespace: orgA,
|
||||
children: [
|
||||
{
|
||||
id: "team-c",
|
||||
namespace: teamC,
|
||||
},
|
||||
{
|
||||
id: "service-1",
|
||||
namespace: service1,
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders an indicator badge for the subnamespace", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "org-a",
|
||||
namespace: orgA,
|
||||
children: [
|
||||
{
|
||||
id: "team-c",
|
||||
namespace: teamC,
|
||||
},
|
||||
{
|
||||
id: "service-1",
|
||||
namespace: service1,
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
|
||||
expect(result.getByTestId("namespace-details-badge-for-service-1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render an indicator badge for the true namespace", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "org-a",
|
||||
namespace: orgA,
|
||||
children: [
|
||||
{
|
||||
id: "team-c",
|
||||
namespace: teamC,
|
||||
},
|
||||
{
|
||||
id: "service-1",
|
||||
namespace: service1,
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
const trueNamespace = result.getByTestId("namespace-team-c");
|
||||
|
||||
expect(trueNamespace.querySelector("[data-testid='namespace-details-badge-for-team-c']")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders 2 levels deep", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "levels-deep",
|
||||
namespace: levelsDeep,
|
||||
children: [
|
||||
{
|
||||
id: "level-deep-child-a",
|
||||
namespace: levelDeepChildA,
|
||||
},
|
||||
{
|
||||
id: "level-deep-child-b",
|
||||
namespace: levelDeepChildB,
|
||||
children: [{
|
||||
id: "level-deep-subchild-a",
|
||||
namespace: levelDeepSubChildA,
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("expands children items by default", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "levels-deep",
|
||||
namespace: levelsDeep,
|
||||
children: [
|
||||
{
|
||||
id: "level-deep-child-a",
|
||||
namespace: levelDeepChildA,
|
||||
},
|
||||
{
|
||||
id: "level-deep-child-b",
|
||||
namespace: levelDeepChildB,
|
||||
children: [{
|
||||
id: "level-deep-subchild-a",
|
||||
namespace: levelDeepSubChildA,
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
const deepest = result.getByTestId("namespace-level-deep-child-b");
|
||||
|
||||
expect(deepest).toHaveAttribute("aria-expanded", "true");
|
||||
});
|
||||
|
||||
it("collapses item by clicking minus button", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "levels-deep",
|
||||
namespace: levelsDeep,
|
||||
children: [
|
||||
{
|
||||
id: "level-deep-child-a",
|
||||
namespace: levelDeepChildA,
|
||||
},
|
||||
{
|
||||
id: "level-deep-child-b",
|
||||
namespace: levelDeepChildB,
|
||||
children: [{
|
||||
id: "level-deep-subchild-a",
|
||||
namespace: levelDeepSubChildA,
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
const levelB = result.getByTestId("namespace-level-deep-child-b");
|
||||
const minusButton = levelB.querySelector("[data-testid='minus-square']");
|
||||
|
||||
if (minusButton) {
|
||||
fireEvent.click(minusButton);
|
||||
}
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("expands item by clicking plus button", () => {
|
||||
const tree: NamespaceTree = {
|
||||
id: "levels-deep",
|
||||
namespace: levelsDeep,
|
||||
children: [
|
||||
{
|
||||
id: "level-deep-child-a",
|
||||
namespace: levelDeepChildA,
|
||||
},
|
||||
{
|
||||
id: "level-deep-child-b",
|
||||
namespace: levelDeepChildB,
|
||||
children: [{
|
||||
id: "level-deep-subchild-a",
|
||||
namespace: levelDeepSubChildA,
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = render(<NamespaceTreeView tree={tree} />);
|
||||
const levelB = result.getByTestId("namespace-level-deep-child-b");
|
||||
const minusButton = levelB.querySelector("[data-testid='minus-square']");
|
||||
|
||||
if (minusButton) {
|
||||
fireEvent.click(minusButton);
|
||||
}
|
||||
|
||||
const plusButton = levelB.querySelector("[data-testid='plus-square']");
|
||||
|
||||
if (plusButton) {
|
||||
fireEvent.click(plusButton);
|
||||
}
|
||||
|
||||
expect(result.baseElement).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import styles from "./namespace-tree-view.module.scss";
|
||||
|
||||
import { SvgIcon } from "@material-ui/core";
|
||||
import { TreeItem, TreeView } from "@material-ui/lab";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { Namespace } from "../../../common/k8s-api/endpoints";
|
||||
import { DrawerTitle } from "../drawer";
|
||||
import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable";
|
||||
import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable";
|
||||
import { SubnamespaceBadge } from "./subnamespace-badge";
|
||||
import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable";
|
||||
import { prevDefault } from "../../utils";
|
||||
import type { NamespaceTree } from "./store";
|
||||
|
||||
interface NamespaceTreeViewProps {
|
||||
tree: NamespaceTree;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
namespaces: Namespace[];
|
||||
getDetailsUrl: GetDetailsUrl;
|
||||
}
|
||||
|
||||
function NonInjectableNamespaceTreeView({ tree, namespaces, getDetailsUrl }: Dependencies & NamespaceTreeViewProps) {
|
||||
const [expandedItems, setExpandedItems] = React.useState<string[]>(namespaces.map(ns => ns.getId()));
|
||||
const classes = { group: styles.group, label: styles.label };
|
||||
|
||||
function renderTree(nodes: NamespaceTree) {
|
||||
return (
|
||||
<TreeItem
|
||||
key={nodes.id}
|
||||
nodeId={nodes.id}
|
||||
data-testid={`namespace-${nodes.id}`}
|
||||
classes={classes}
|
||||
onIconClick={prevDefault(() => toggleNode(nodes.id))}
|
||||
label={(
|
||||
<>
|
||||
<Link key={nodes.namespace.getId()} to={getDetailsUrl(nodes.namespace.selfLink)}>
|
||||
{nodes.namespace.getName()}
|
||||
</Link>
|
||||
{" "}
|
||||
{nodes.namespace.isSubnamespace() && (
|
||||
<SubnamespaceBadge id={`namespace-details-badge-for-${nodes.namespace.getId()}`} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
>
|
||||
{Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null}
|
||||
</TreeItem>
|
||||
);
|
||||
}
|
||||
|
||||
function toggleNode(id: string) {
|
||||
if (expandedItems.includes(id)) {
|
||||
setExpandedItems(expandedItems.filter(item => item !== id));
|
||||
} else {
|
||||
setExpandedItems([...expandedItems, id]);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="namespace-tree-view" className={styles.TreeView}>
|
||||
<DrawerTitle>Tree View</DrawerTitle>
|
||||
<TreeView
|
||||
defaultExpanded={[tree.id]}
|
||||
defaultCollapseIcon={<MinusSquareIcon />}
|
||||
defaultExpandIcon={<PlusSquareIcon />}
|
||||
defaultEndIcon={(<div style={{ opacity: 0.3 }}><MinusSquareIcon /></div>)}
|
||||
expanded={expandedItems}
|
||||
>
|
||||
{renderTree(tree)}
|
||||
</TreeView>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MinusSquareIcon() {
|
||||
return (
|
||||
<SvgIcon style={{ width: 14, height: 14 }} data-testid="minus-square">
|
||||
<path d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 11.023h-11.826q-.375 0-.669.281t-.294.682v0q0 .401.294 .682t.669.281h11.826q.375 0 .669-.281t.294-.682v0q0-.401-.294-.682t-.669-.281z" />
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
|
||||
function PlusSquareIcon() {
|
||||
return (
|
||||
<SvgIcon style={{ width: 14, height: 14 }} data-testid="plus-square">
|
||||
<path d="M22.047 22.074v0 0-20.147 0h-20.12v0 20.147 0h20.12zM22.047 24h-20.12q-.803 0-1.365-.562t-.562-1.365v-20.147q0-.776.562-1.351t1.365-.575h20.147q.776 0 1.351.575t.575 1.351v20.147q0 .803-.575 1.365t-1.378.562v0zM17.873 12.977h-4.923v4.896q0 .401-.281.682t-.682.281v0q-.375 0-.669-.281t-.294-.682v-4.896h-4.923q-.401 0-.682-.294t-.281-.669v0q0-.401.281-.682t.682-.281h4.923v-4.896q0-.401.294-.682t.669-.281v0q.401 0 .682.281t.281.682v4.896h4.923q.401 0 .682.281t.281.682v0q0 .375-.281.669t-.682.294z" />
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
|
||||
export const NamespaceTreeView = withInjectables<Dependencies, NamespaceTreeViewProps>(NonInjectableNamespaceTreeView, {
|
||||
getProps: (di, props) => ({
|
||||
namespaces: di.inject(hierarchicalNamespacesInjectable),
|
||||
getDetailsUrl: di.inject(getDetailsUrlInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@@ -22,4 +22,8 @@
|
||||
@include namespaceStatus;
|
||||
}
|
||||
}
|
||||
|
||||
.subnamespaceBadge {
|
||||
margin-inline-start: var(--margin);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./store.injectable";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
import openAddNamepaceDialogInjectable from "./add-dialog/open.injectable";
|
||||
import { SubnamespaceBadge } from "./subnamespace-badge";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@@ -55,7 +56,12 @@ const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }:
|
||||
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
|
||||
]}
|
||||
renderTableContents={namespace => [
|
||||
namespace.getName(),
|
||||
<>
|
||||
{namespace.getName()}
|
||||
{namespace.isSubnamespace() && (
|
||||
<SubnamespaceBadge className="subnamespaceBadge" id={`namespace-list-badge-for-${namespace.getId()}`} />
|
||||
)}
|
||||
</>,
|
||||
<KubeObjectStatusIcon key="icon" object={namespace} />,
|
||||
namespace.getLabels().map(label => (
|
||||
<Badge
|
||||
|
||||
@@ -12,6 +12,12 @@ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||
import type { NamespaceApi } from "../../../common/k8s-api/endpoints/namespace.api";
|
||||
import { Namespace } from "../../../common/k8s-api/endpoints/namespace.api";
|
||||
|
||||
export interface NamespaceTree {
|
||||
id: string;
|
||||
namespace: Namespace;
|
||||
children?: NamespaceTree[];
|
||||
}
|
||||
|
||||
interface Dependencies extends KubeObjectStoreDependencies {
|
||||
readonly storage: StorageLayer<string[] | undefined>;
|
||||
readonly clusterConfiguredAccessibleNamespaces: IComputedValue<string[]>;
|
||||
@@ -202,6 +208,16 @@ export class NamespaceStore extends KubeObjectStore<Namespace, NamespaceApi> {
|
||||
this.selectAll();
|
||||
}
|
||||
|
||||
getNamespaceTree(root: Namespace): NamespaceTree {
|
||||
const children = this.items.filter(namespace => namespace.isChildOf(root.getName()));
|
||||
|
||||
return {
|
||||
id: root.getId(),
|
||||
namespace: root,
|
||||
children: children.map(this.getNamespaceTree),
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async remove(item: Namespace) {
|
||||
await super.remove(item);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.subnamespaceBadge {
|
||||
border-radius: 3px;
|
||||
padding: 0px 4px;
|
||||
border: 1px solid cadetblue;
|
||||
color: cadetblue;
|
||||
display: inline-flex;
|
||||
font-size: x-small;
|
||||
font-weight: bold;
|
||||
height: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import styles from "./subnamespace-badge.module.scss";
|
||||
|
||||
import React from "react";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { cssNames } from "../../utils";
|
||||
|
||||
interface SubnamespaceBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export function SubnamespaceBadge({ id, className, ...other }: SubnamespaceBadgeProps) {
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={cssNames(styles.subnamespaceBadge, className)}
|
||||
data-testid={id}
|
||||
id={id}
|
||||
{...other}
|
||||
>
|
||||
S
|
||||
</span>
|
||||
<Tooltip targetId={id}>
|
||||
Subnamespace
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class NonInjectedCreateResource extends React.Component<CreateResourceProps & De
|
||||
this.error = error.toString();
|
||||
};
|
||||
|
||||
create = async (): Promise<void> => {
|
||||
create = async (): Promise<string | void> => {
|
||||
const { apiManager, getDetailsUrl, navigate, requestKubeObjectCreation } = this.props;
|
||||
|
||||
if (this.error || !this.data?.trim()) {
|
||||
@@ -98,7 +98,7 @@ class NonInjectedCreateResource extends React.Component<CreateResourceProps & De
|
||||
this.props.logger.warn("Failed to create resource", { resource }, result.error);
|
||||
this.props.showCheckedErrorNotification(result.error, "Unknown error occured while creating resources");
|
||||
|
||||
return;
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
const { kind, apiVersion, metadata: { name, namespace }} = result.response;
|
||||
@@ -122,7 +122,13 @@ class NonInjectedCreateResource extends React.Component<CreateResourceProps & De
|
||||
));
|
||||
});
|
||||
|
||||
await Promise.allSettled(creatingResources);
|
||||
const results = await Promise.allSettled(creatingResources);
|
||||
|
||||
if (results.some(result => result.status === "rejected")) {
|
||||
return;
|
||||
}
|
||||
|
||||
return "All resources have been successfully created";
|
||||
};
|
||||
|
||||
renderControls() {
|
||||
|
||||
+2
@@ -174,5 +174,7 @@ export class EditResourceModel {
|
||||
runInAction(() => {
|
||||
this.editingResource.firstDraft = currentValue;
|
||||
});
|
||||
|
||||
return result.response.toString();
|
||||
};
|
||||
}
|
||||
@@ -93,18 +93,25 @@ class NonInjectedInfoPanel extends Component<InfoPanelProps & Dependencies> {
|
||||
if (showNotifications && result) {
|
||||
this.props.showSuccessNotification(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (showNotifications) {
|
||||
this.props.showCheckedErrorNotification(error, "Unknown error while submitting");
|
||||
}
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
this.waiting = false;
|
||||
}
|
||||
};
|
||||
|
||||
submitAndClose = async () => {
|
||||
await this.submit();
|
||||
this.close();
|
||||
const result = await this.submit();
|
||||
|
||||
if (result) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
close = () => {
|
||||
|
||||
+2
@@ -12,6 +12,7 @@ import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.
|
||||
import loadExtensionsInjectable from "../../load-extensions.injectable";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import showErrorNotificationInjectable from "../../../components/notifications/show-error-notification.injectable";
|
||||
import closeRendererLogFileInjectable from "../../../logger/close-renderer-log-file.injectable";
|
||||
|
||||
const initClusterFrameInjectable = getInjectable({
|
||||
id: "init-cluster-frame",
|
||||
@@ -29,6 +30,7 @@ const initClusterFrameInjectable = getInjectable({
|
||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||
closeFileLogging: di.inject(closeRendererLogFileInjectable),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
+9
-3
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { once } from "lodash";
|
||||
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||
import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
|
||||
import type { ShowNotification } from "../../../components/notifications";
|
||||
@@ -18,6 +19,7 @@ interface Dependencies {
|
||||
emitAppEvent: EmitAppEvent;
|
||||
logger: Logger;
|
||||
showErrorNotification: ShowNotification;
|
||||
closeFileLogging: () => void;
|
||||
}
|
||||
|
||||
const logPrefix = "[CLUSTER-FRAME]:";
|
||||
@@ -30,6 +32,7 @@ export const initClusterFrame = ({
|
||||
emitAppEvent,
|
||||
logger,
|
||||
showErrorNotification,
|
||||
closeFileLogging,
|
||||
}: Dependencies) =>
|
||||
async (unmountRoot: () => void) => {
|
||||
await requestSetClusterFrameId(hostedCluster.id);
|
||||
@@ -69,11 +72,14 @@ export const initClusterFrame = ({
|
||||
});
|
||||
});
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
const onCloseFrame = once(() => {
|
||||
logger.info(
|
||||
`${logPrefix} Unload dashboard, clusterId=${(hostedCluster.id)}, frameId=${frameRoutingId}`,
|
||||
);
|
||||
|
||||
closeFileLogging();
|
||||
unmountRoot();
|
||||
};
|
||||
});
|
||||
|
||||
window.addEventListener("beforeunload", onCloseFrame);
|
||||
window.addEventListener("pagehide", onCloseFrame);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import loggerInjectable from "../../../common/logger.injectable";
|
||||
import { delay } from "../../../common/utils";
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
import { bundledExtensionsLoaded } from "../../../common/ipc/extension-handling";
|
||||
import closeRendererLogFileInjectable from "../../logger/close-renderer-log-file.injectable";
|
||||
|
||||
const initRootFrameInjectable = getInjectable({
|
||||
id: "init-root-frame",
|
||||
@@ -22,6 +23,7 @@ const initRootFrameInjectable = getInjectable({
|
||||
const bindProtocolAddRouteHandlers = di.inject(bindProtocolAddRouteHandlersInjectable);
|
||||
const lensProtocolRouterRenderer = di.inject(lensProtocolRouterRendererInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const closeRendererLogFile = di.inject(closeRendererLogFileInjectable);
|
||||
|
||||
return async (unmountRoot: () => void) => {
|
||||
try {
|
||||
@@ -55,7 +57,7 @@ const initRootFrameInjectable = getInjectable({
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
logger.info("[ROOT-FRAME]: Unload app");
|
||||
|
||||
closeRendererLogFile();
|
||||
unmountRoot();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,5 +12,6 @@ export * as Mobx from "mobx";
|
||||
export * as MobxReact from "mobx-react";
|
||||
export * as ReactRouter from "react-router";
|
||||
export * as ReactRouterDom from "react-router-dom";
|
||||
export * as extensionApi from "../extensions/renderer-api";
|
||||
export * as rendererExtensionApi from "../extensions/renderer-api";
|
||||
export * as commonExtensionApi from "../extensions/common-api";
|
||||
export { createApp } from "./create-app";
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 winstonLoggerInjectable from "../../common/winston-logger.injectable";
|
||||
import rendererFileLoggerTransportInjectable from "./file-transport.injectable";
|
||||
|
||||
const closeRendererLogFileInjectable = getInjectable({
|
||||
id: "close-renderer-log-file",
|
||||
instantiate: (di) => {
|
||||
const winstonLogger = di.inject(winstonLoggerInjectable);
|
||||
const fileLoggingTransport = di.inject(rendererFileLoggerTransportInjectable);
|
||||
|
||||
return () => {
|
||||
fileLoggingTransport.close?.();
|
||||
winstonLogger.remove(fileLoggingTransport);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default closeRendererLogFileInjectable;
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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 { transports } from "winston";
|
||||
import directoryForLogsInjectable from "../../common/app-paths/directory-for-logs.injectable";
|
||||
import { loggerTransportInjectionToken } from "../../common/logger/transports";
|
||||
import windowLocationInjectable from "../../common/k8s-api/window-location.injectable";
|
||||
import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable";
|
||||
import { getClusterIdFromHost } from "../utils";
|
||||
|
||||
const rendererFileLoggerTransportInjectable = getInjectable({
|
||||
id: "renderer-file-logger-transport",
|
||||
instantiate: (di) => {
|
||||
let frameId: string;
|
||||
|
||||
const currentlyInClusterFrame = di.inject(
|
||||
currentlyInClusterFrameInjectable,
|
||||
);
|
||||
|
||||
if (currentlyInClusterFrame) {
|
||||
const { host } = di.inject(windowLocationInjectable);
|
||||
const clusterId = getClusterIdFromHost(host);
|
||||
|
||||
frameId = clusterId ? `cluster-${clusterId}` : "cluster";
|
||||
} else {
|
||||
frameId = "main";
|
||||
}
|
||||
|
||||
return new transports.File({
|
||||
handleExceptions: false,
|
||||
level: "info",
|
||||
filename: `lens-renderer-${frameId}.log`,
|
||||
dirname: di.inject(directoryForLogsInjectable),
|
||||
maxsize: 1024 * 1024,
|
||||
maxFiles: 2,
|
||||
tailable: true,
|
||||
});
|
||||
},
|
||||
injectionToken: loggerTransportInjectionToken,
|
||||
});
|
||||
|
||||
export default rendererFileLoggerTransportInjectable;
|
||||
@@ -6,9 +6,7 @@
|
||||
import { iter } from "../../common/utils";
|
||||
|
||||
export type IgnoredClassNames = number | symbol | Function;
|
||||
|
||||
export type IClassName = string | string[] | IClassNameMap | undefined | null | false | IgnoredClassNames;
|
||||
export type IClassNameMap = object;
|
||||
export type IClassName = string | string[] | Record<string, any> | undefined | null | false | IgnoredClassNames;
|
||||
|
||||
export function cssNames(...classNames: IClassName[]): string {
|
||||
const classNamesEnabled = new Map<string, boolean>();
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type webpack from "webpack";
|
||||
import path from "path";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import webpackLensMain from "./main";
|
||||
import { buildDir } from "./vars";
|
||||
|
||||
const webpackLensCommon = (): webpack.Configuration => {
|
||||
const mainConfig = webpackLensMain();
|
||||
|
||||
return {
|
||||
...mainConfig,
|
||||
name: "lens-app-common",
|
||||
entry: {
|
||||
common: path.resolve(__dirname, "..", "src", "common", "library.ts"),
|
||||
},
|
||||
output: {
|
||||
publicPath: "",
|
||||
library: {
|
||||
type: "commonjs2",
|
||||
},
|
||||
path: path.resolve(buildDir, "library"),
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
plugins: [
|
||||
new ForkTsCheckerPlugin({}),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export default webpackLensCommon;
|
||||
@@ -2,118 +2,15 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import { platform } from "os";
|
||||
import path from "path";
|
||||
import { DefinePlugin, optimize } from "webpack";
|
||||
import main from "./main";
|
||||
import renderer, { iconsAndImagesWebpackRules } from "./renderer";
|
||||
import { buildDir, isDevelopment } from "./vars";
|
||||
|
||||
const rendererConfig = renderer({ showVars: false });
|
||||
const mainConfig = main();
|
||||
import webpackLensCommon from "./common";
|
||||
import webpackLensMain from "./main";
|
||||
import { webpackLensRenderer } from "./renderer";
|
||||
|
||||
const config = [
|
||||
{
|
||||
...mainConfig,
|
||||
entry: {
|
||||
main: path.resolve(__dirname, "..", "src", "main", "library.ts"),
|
||||
},
|
||||
output: {
|
||||
library: {
|
||||
type: "commonjs2",
|
||||
},
|
||||
path: path.resolve(buildDir, "library"),
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
module: {
|
||||
parser: {
|
||||
javascript: {
|
||||
commonjsMagicComments: true,
|
||||
},
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: "node-loader",
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
compilerOptions: {
|
||||
declaration: true,
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...iconsAndImagesWebpackRules(),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new DefinePlugin({
|
||||
CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
CONTEXT_MATCHER_FOR_FEATURES: `/\\/(main|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
...mainConfig,
|
||||
name: "lens-app-common",
|
||||
entry: {
|
||||
common: path.resolve(__dirname, "..", "src", "common", "library.ts"),
|
||||
},
|
||||
output: {
|
||||
publicPath: "",
|
||||
library: {
|
||||
type: "commonjs2",
|
||||
},
|
||||
path: path.resolve(buildDir, "library"),
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
plugins: [],
|
||||
},
|
||||
{
|
||||
...rendererConfig,
|
||||
entry: {
|
||||
renderer: path.resolve(__dirname, "..", "src", "renderer", "library.ts"),
|
||||
},
|
||||
output: {
|
||||
library: {
|
||||
type: "commonjs2",
|
||||
},
|
||||
path: path.resolve(buildDir, "library"),
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
externals: [
|
||||
...(rendererConfig.externals as any).filter(Boolean),
|
||||
{
|
||||
"monaco-editor": "commonjs monaco-editor",
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
new DefinePlugin({
|
||||
CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
CONTEXT_MATCHER_FOR_FEATURES: `/\\/(renderer|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].css",
|
||||
runtime: isDevelopment,
|
||||
}),
|
||||
new optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
webpackLensMain(),
|
||||
webpackLensRenderer(),
|
||||
webpackLensCommon(),
|
||||
];
|
||||
|
||||
export default config;
|
||||
@@ -5,25 +5,14 @@
|
||||
|
||||
import path from "path";
|
||||
import type webpack from "webpack";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import nodeExternals from "webpack-node-externals";
|
||||
import { getTypescriptLoader } from "./get-typescript-loader";
|
||||
import CircularDependencyPlugin from "circular-dependency-plugin";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import { iconsAndImagesWebpackRules } from "./renderer";
|
||||
import type { WebpackPluginInstance } from "webpack";
|
||||
import { DefinePlugin } from "webpack";
|
||||
import { additionalExternals, buildDir, isDevelopment, mainDir } from "./vars";
|
||||
import { buildDir, isDevelopment } from "./vars";
|
||||
import { platform } from "process";
|
||||
|
||||
const main = ({ showVars = true } = {}): webpack.Configuration => {
|
||||
if (showVars) {
|
||||
console.info("WEBPACK:main", {
|
||||
isDevelopment,
|
||||
mainDir,
|
||||
buildDir,
|
||||
});
|
||||
}
|
||||
|
||||
const webpackLensMain = (): webpack.Configuration => {
|
||||
return {
|
||||
name: "lens-app-main",
|
||||
context: __dirname,
|
||||
@@ -32,18 +21,22 @@ const main = ({ showVars = true } = {}): webpack.Configuration => {
|
||||
devtool: isDevelopment ? "cheap-module-source-map" : "source-map",
|
||||
cache: isDevelopment ? { type: "filesystem" } : false,
|
||||
entry: {
|
||||
main: path.resolve(mainDir, "index.ts"),
|
||||
main: path.resolve(__dirname, "..", "src", "main", "library.ts"),
|
||||
},
|
||||
output: {
|
||||
libraryTarget: "global",
|
||||
path: buildDir,
|
||||
library: {
|
||||
type: "commonjs2",
|
||||
},
|
||||
path: path.resolve(buildDir, "library"),
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".json", ".js", ".ts"],
|
||||
},
|
||||
externals: [
|
||||
nodeExternals(),
|
||||
...additionalExternals,
|
||||
],
|
||||
module: {
|
||||
parser: {
|
||||
@@ -56,7 +49,19 @@ const main = ({ showVars = true } = {}): webpack.Configuration => {
|
||||
test: /\.node$/,
|
||||
use: "node-loader",
|
||||
},
|
||||
getTypescriptLoader({}, /\.ts$/),
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
compilerOptions: {
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...iconsAndImagesWebpackRules(),
|
||||
],
|
||||
},
|
||||
@@ -65,14 +70,18 @@ const main = ({ showVars = true } = {}): webpack.Configuration => {
|
||||
CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
CONTEXT_MATCHER_FOR_FEATURES: `/\\/(main|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
}),
|
||||
new ForkTsCheckerPlugin(),
|
||||
new CircularDependencyPlugin({
|
||||
cwd: __dirname,
|
||||
exclude: /node_modules/,
|
||||
failOnError: true,
|
||||
}) as unknown as WebpackPluginInstance,
|
||||
new ForkTsCheckerPlugin({
|
||||
typescript: {
|
||||
mode: "write-dts",
|
||||
configOverwrite: {
|
||||
compilerOptions: {
|
||||
declaration: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export default main;
|
||||
export default webpackLensMain;
|
||||
@@ -5,31 +5,16 @@
|
||||
|
||||
import path from "path";
|
||||
import type webpack from "webpack";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import MonacoWebpackPlugin from "monaco-editor-webpack-plugin";
|
||||
import CircularDependencyPlugin from "circular-dependency-plugin";
|
||||
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import type { WebpackPluginInstance } from "webpack";
|
||||
import { DefinePlugin } from "webpack";
|
||||
import { getTypescriptLoader } from "./get-typescript-loader";
|
||||
import { assetsFolderName, isDevelopment, rendererDir, buildDir, appName, htmlTemplate, publicPath, sassCommonVars, additionalExternals } from "./vars";
|
||||
import { optimize, DefinePlugin } from "webpack";
|
||||
import nodeExternals from "webpack-node-externals";
|
||||
import { isDevelopment, buildDir, sassCommonVars } from "./vars";
|
||||
import { platform } from "process";
|
||||
|
||||
export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration {
|
||||
if (showVars) {
|
||||
console.info("WEBPACK:renderer", {
|
||||
assetsFolderName,
|
||||
isDevelopment,
|
||||
rendererDir,
|
||||
buildDir,
|
||||
appName,
|
||||
htmlTemplate,
|
||||
publicPath,
|
||||
});
|
||||
}
|
||||
|
||||
export function webpackLensRenderer(): webpack.Configuration {
|
||||
return {
|
||||
target: "electron-renderer",
|
||||
name: "lens-app-renderer",
|
||||
@@ -38,16 +23,13 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
devtool: isDevelopment ? "cheap-module-source-map" : "source-map",
|
||||
cache: isDevelopment ? { type: "filesystem" } : false,
|
||||
entry: {
|
||||
[appName]: path.resolve(rendererDir, "index.ts"),
|
||||
renderer: path.resolve(__dirname, "..", "src", "renderer", "library.ts"),
|
||||
},
|
||||
output: {
|
||||
libraryTarget: "global",
|
||||
globalObject: "this",
|
||||
publicPath,
|
||||
path: buildDir,
|
||||
filename: "[name].js",
|
||||
chunkFilename: "chunks/[name].js",
|
||||
assetModuleFilename: `${assetsFolderName}/[name][ext][query]`,
|
||||
library: {
|
||||
type: "commonjs2",
|
||||
},
|
||||
path: path.resolve(buildDir, "library"),
|
||||
},
|
||||
watchOptions: {
|
||||
ignored: /node_modules/, // https://webpack.js.org/configuration/watch/
|
||||
@@ -64,10 +46,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
],
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
"win-ca": "commonjs win-ca",
|
||||
},
|
||||
...additionalExternals,
|
||||
nodeExternals(),
|
||||
],
|
||||
optimization: {
|
||||
minimize: false,
|
||||
@@ -83,11 +62,20 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
test: /\.node$/,
|
||||
use: "node-loader",
|
||||
},
|
||||
getTypescriptLoader({
|
||||
getCustomTransformers: () => ({
|
||||
before: isDevelopment ? [require("react-refresh-typescript")()] : [],
|
||||
}),
|
||||
}),
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
compilerOptions: {
|
||||
declaration: true,
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cssModulesWebpackRule(),
|
||||
...iconsAndImagesWebpackRules(),
|
||||
...fontsLoaderWebpackRules(),
|
||||
@@ -99,25 +87,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
CONTEXT_MATCHER_FOR_FEATURES: `/\\/(renderer|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
}),
|
||||
new ForkTsCheckerPlugin(),
|
||||
|
||||
// see also: https://github.com/Microsoft/monaco-editor-webpack-plugin#options
|
||||
new MonacoWebpackPlugin({
|
||||
// publicPath: "/",
|
||||
// filename: "[name].worker.js",
|
||||
languages: ["json", "yaml"],
|
||||
globalAPI: isDevelopment,
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "index.html",
|
||||
template: htmlTemplate,
|
||||
inject: true,
|
||||
hash: true,
|
||||
templateParameters: {
|
||||
assetPath: `${publicPath}${assetsFolderName}`,
|
||||
},
|
||||
}),
|
||||
new ForkTsCheckerPlugin({}),
|
||||
|
||||
new CircularDependencyPlugin({
|
||||
cwd: __dirname,
|
||||
@@ -129,11 +99,9 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
filename: "[name].css",
|
||||
}),
|
||||
|
||||
...(
|
||||
isDevelopment
|
||||
? [new ReactRefreshWebpackPlugin({ overlay: false })]
|
||||
: []
|
||||
),
|
||||
new optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,8 +21,5 @@ export const htmlTemplate = path.resolve(rendererDir, "template.html");
|
||||
export const publicPath = "/build/";
|
||||
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
|
||||
export const webpackDevServerPort = Number(process.env.WEBPACK_DEV_SERVER_PORT) || 9191;
|
||||
export const additionalExternals = (process.env.LENS_BUILD_ADDITIONAL_WEBPACK_EXTERNALS || "")
|
||||
.split(",")
|
||||
.map(s => s.trim());
|
||||
|
||||
assert(Number.isInteger(webpackDevServerPort), "WEBPACK_DEV_SERVER_PORT environment variable must only be an integer");
|
||||
+432
-1035
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user