mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into fix-load-release-data-on-details-open
This commit is contained in:
64 files changed
+34126
-36975
No files matched your search
@@ -1,3 +0,0 @@
|
||||
disturl "https://electronjs.org/headers"
|
||||
target "19.0.4"
|
||||
runtime "electron"
|
||||
+10
-10
@@ -50,14 +50,14 @@
|
||||
"license": "MIT",
|
||||
"author": "OpenLens Authors <info@k8slens.dev>",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production yarn run webpack --config webpack/library-bundle.ts --progress",
|
||||
"build": "cross-env NODE_ENV=production webpack --config webpack/library-bundle.ts --progress",
|
||||
"clean": "rimraf dist static/build",
|
||||
"prepare:dev": "cross-env NODE_ENV=development yarn run webpack --config webpack/library-bundle.ts --progress",
|
||||
"dev": "cross-env NODE_ENV=development yarn run webpack --config webpack/library-bundle.ts --watch",
|
||||
"prepare:dev": "cross-env NODE_ENV=development webpack --config webpack/library-bundle.ts --progress",
|
||||
"dev": "cross-env NODE_ENV=development webpack --config webpack/library-bundle.ts --watch",
|
||||
"test:unit": "jest --testPathIgnorePatterns integration",
|
||||
"test:watch": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func",
|
||||
"lint": "PROD=true yarn run eslint --ext js,ts,tsx --max-warnings=0 .",
|
||||
"lint:fix": "yarn run lint --fix"
|
||||
"lint": "PROD=true eslint --ext js,ts,tsx --max-warnings=0 .",
|
||||
"lint:fix": "npm run lint --fix"
|
||||
},
|
||||
"config": {
|
||||
"k8sProxyVersion": "0.3.0",
|
||||
@@ -192,7 +192,7 @@
|
||||
"win-ca": "^3.5.0",
|
||||
"winston": "^3.8.2",
|
||||
"winston-transport-browserconsole": "^1.0.5",
|
||||
"ws": "^8.12.0",
|
||||
"ws": "^8.12.1",
|
||||
"xterm-link-provider": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -242,7 +242,7 @@
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/readable-stream": "^2.3.13",
|
||||
"@types/semver": "^7.3.13",
|
||||
"@types/tar": "^6.1.3",
|
||||
"@types/tar": "^6.1.4",
|
||||
"@types/tcp-port-used": "^1.0.1",
|
||||
"@types/tempy": "^0.3.0",
|
||||
"@types/triple-beam": "^1.3.2",
|
||||
@@ -310,7 +310,7 @@
|
||||
"react-table": "^7.8.0",
|
||||
"react-window": "^1.8.8",
|
||||
"rimraf": "^4.1.2",
|
||||
"sass": "^1.58.1",
|
||||
"sass": "^1.58.2",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
@@ -318,7 +318,7 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"type-fest": "^2.14.0",
|
||||
"typed-emitter": "^1.4.0",
|
||||
"typedoc": "0.23.24",
|
||||
"typedoc": "0.23.25",
|
||||
"typedoc-plugin-markdown": "^3.13.6",
|
||||
"typescript": "^4.9.5",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
@@ -340,7 +340,7 @@
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/tar": "^6.1.3",
|
||||
"@types/tar": "^6.1.4",
|
||||
"@types/tcp-port-used": "^1.0.1",
|
||||
"@types/url-parse": "^1.4.8",
|
||||
"@types/uuid": "^8.3.4",
|
||||
|
||||
+8
-7
@@ -37,18 +37,17 @@ export interface RequestMetricsParams {
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export interface RequestMetrics {
|
||||
(query: string, params?: RequestMetricsParams): Promise<MetricData>;
|
||||
(query: string[], params?: RequestMetricsParams): Promise<MetricData[]>;
|
||||
<Keys extends string>(query: Record<Keys, Partial<Record<string, string>>>, params?: RequestMetricsParams): Promise<Record<Keys, MetricData>>;
|
||||
}
|
||||
export type RequestMetrics = ReturnType<typeof requestMetricsInjectable["instantiate"]>;
|
||||
|
||||
const requestMetricsInjectable = getInjectable({
|
||||
id: "request-metrics",
|
||||
instantiate: (di) => {
|
||||
const apiBase = di.inject(apiBaseInjectable);
|
||||
|
||||
return (async (query: object, params: RequestMetricsParams = {}) => {
|
||||
function requestMetrics(query: string, params?: RequestMetricsParams): Promise<MetricData>;
|
||||
function requestMetrics(query: string[], params?: RequestMetricsParams): Promise<MetricData[]>;
|
||||
function requestMetrics<Keys extends string>(query: Record<Keys, Partial<Record<string, string>>>, params?: RequestMetricsParams): Promise<Record<Keys, MetricData>>;
|
||||
async function requestMetrics(query: string | string[] | Partial<Record<string, Partial<Record<string, string>>>>, params: RequestMetricsParams = {}): Promise<MetricData | MetricData[] | Partial<Record<string, MetricData>>> {
|
||||
const { range = 3600, step = 60, namespace } = params;
|
||||
let { start, end } = params;
|
||||
|
||||
@@ -66,7 +65,9 @@ const requestMetricsInjectable = getInjectable({
|
||||
"kubernetes_namespace": namespace,
|
||||
},
|
||||
});
|
||||
}) as RequestMetrics;
|
||||
}
|
||||
|
||||
return requestMetrics;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import randomBytesInjectable from "./random-bytes.injectable";
|
||||
|
||||
export default getGlobalOverride(randomBytesInjectable, () => async (size) => {
|
||||
export default getGlobalOverride(randomBytesInjectable, () => (size) => {
|
||||
const res = Buffer.alloc(size);
|
||||
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { randomBytes } from "crypto";
|
||||
import { promisify } from "util";
|
||||
|
||||
export type RandomBytes = (size: number) => Promise<Buffer>;
|
||||
export type RandomBytes = (size: number) => Buffer;
|
||||
|
||||
const randomBytesInjectable = getInjectable({
|
||||
id: "random-bytes",
|
||||
instantiate: (): RandomBytes => promisify(randomBytes),
|
||||
instantiate: (): RandomBytes => randomBytes,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
|
||||
+22
-8
@@ -3,17 +3,18 @@
|
||||
* 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 { getOrInsert } 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";
|
||||
import getPathToLegacyPackageJsonInjectable from "./get-path-to-legacy-package-json.injectable";
|
||||
import { registeredExtensionsInjectable } from "./registered-extensions.injectable";
|
||||
|
||||
export type EnsureHashedDirectoryForExtension = (extensionName: string, registeredExtensions: ObservableMap<string, string>) => Promise<string>;
|
||||
export type EnsureHashedDirectoryForExtension = (extensionName: string) => Promise<string>;
|
||||
|
||||
const ensureHashedDirectoryForExtensionInjectable = getInjectable({
|
||||
id: "ensure-hashed-directory-for-extension",
|
||||
@@ -24,14 +25,27 @@ const ensureHashedDirectoryForExtensionInjectable = getInjectable({
|
||||
const directoryForExtensionData = di.inject(directoryForExtensionDataInjectable);
|
||||
const ensureDirectory = di.inject(ensureDirInjectable);
|
||||
const getHash = di.inject(getHashInjectable);
|
||||
const getPathToLegacyPackageJson = di.inject(getPathToLegacyPackageJsonInjectable);
|
||||
const registeredExtensions = di.inject(registeredExtensionsInjectable);
|
||||
|
||||
return async (extensionName, registeredExtensions) => {
|
||||
const dirPath = await getOrInsertWithAsync(registeredExtensions, extensionName, async () => {
|
||||
const salt = (await randomBytes(32)).toString("hex");
|
||||
return async (extensionName) => {
|
||||
let dirPath: string;
|
||||
|
||||
const legacyDirPath = getPathToLegacyPackageJson(extensionName);
|
||||
const hashedDirectoryForLegacyDirPath = registeredExtensions.get(legacyDirPath);
|
||||
|
||||
if (hashedDirectoryForLegacyDirPath) {
|
||||
registeredExtensions.set(extensionName, hashedDirectoryForLegacyDirPath);
|
||||
registeredExtensions.delete(legacyDirPath);
|
||||
dirPath = hashedDirectoryForLegacyDirPath;
|
||||
} else {
|
||||
const salt = randomBytes(32).toString("hex");
|
||||
const hashedName = getHash(`${extensionName}/${salt}`);
|
||||
|
||||
return joinPaths(directoryForExtensionData, hashedName);
|
||||
});
|
||||
const hashedExtensionDirectory = joinPaths(directoryForExtensionData, hashedName);
|
||||
|
||||
dirPath = getOrInsert(registeredExtensions, extensionName, hashedExtensionDirectory);
|
||||
}
|
||||
|
||||
await ensureDirectory(dirPath);
|
||||
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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 { runInAction } from "mobx";
|
||||
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||
import type { EnsureHashedDirectoryForExtension } from "./ensure-hashed-directory-for-extension.injectable";
|
||||
import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable";
|
||||
import ensureDirInjectable from "../../../common/fs/ensure-dir.injectable";
|
||||
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { registeredExtensionsInjectable } from "./registered-extensions.injectable";
|
||||
|
||||
describe("ensure-hashed-directory-for-extension", () => {
|
||||
let ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension;
|
||||
let ensureDirMock: jest.Mock;
|
||||
let registeredExtensions: ObservableMap<string, string>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
ensureDirMock = jest.fn();
|
||||
|
||||
di.override(ensureDirInjectable, () => ensureDirMock);
|
||||
di.override(directoryForExtensionDataInjectable, () => "some-directory-for-extension-data");
|
||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||
|
||||
ensureHashedDirectoryForExtension = di.inject(
|
||||
ensureHashedDirectoryForExtensionInjectable,
|
||||
);
|
||||
|
||||
registeredExtensions = di.inject(registeredExtensionsInjectable);
|
||||
});
|
||||
|
||||
it("given registered extension exists, returns existing directory", async () => {
|
||||
runInAction(() => {
|
||||
registeredExtensions.set("some-extension-name", "some-directory");
|
||||
});
|
||||
|
||||
const actual = await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(actual).toBe("some-directory");
|
||||
});
|
||||
|
||||
it("given registered extension does not exist, returns random directory", async () => {
|
||||
const actual = await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(actual).toBe("some-directory-for-extension-data/a37a1cfefc0391af3733f23cb6b29443f596a2b8ffe6d116c35df7bc3cd99ef6");
|
||||
});
|
||||
|
||||
describe("given extension directory was saved based on extension's package.json path", () => {
|
||||
beforeEach(() => {
|
||||
runInAction(() => {
|
||||
registeredExtensions.set("/some-directory-for-user-data/node_modules/some-extension-name/package.json", "some-directory");
|
||||
});
|
||||
ensureDirMock.mockClear();
|
||||
});
|
||||
|
||||
it("returns existing directory", async () => {
|
||||
const actual = await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(actual).toBe("some-directory");
|
||||
});
|
||||
|
||||
it("ensure dir is called with some directory", async () => {
|
||||
await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(ensureDirMock).toHaveBeenCalledWith("some-directory");
|
||||
});
|
||||
|
||||
it("is migrated to use the extension name as key", async () => {
|
||||
await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(registeredExtensions.get("some-extension-name")).toEqual("some-directory");
|
||||
});
|
||||
|
||||
it("old key is removed", async () => {
|
||||
await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(registeredExtensions.has("/some-directory-for-user-data/node_modules/some-extension-name/package.json")).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { MigrationDeclaration } from "../../../common/base-store/migrations.injectable";
|
||||
|
||||
export const fileSystemProvisionerStoreInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||
id: "file-system-provisioner-store-injection-token",
|
||||
});
|
||||
+2
@@ -14,6 +14,7 @@ import { persistStateToConfigInjectionToken } from "../../../common/base-store/s
|
||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||
import { enlistMessageChannelListenerInjectionToken } from "../../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||
import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable";
|
||||
import { registeredExtensionsInjectable } from "./registered-extensions.injectable";
|
||||
|
||||
const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||
id: "file-system-provisioner-store",
|
||||
@@ -30,6 +31,7 @@ const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||
ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable),
|
||||
registeredExtensions: di.inject(registeredExtensionsInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
+6
-6
@@ -3,7 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { action, makeObservable } 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";
|
||||
@@ -16,11 +17,10 @@ interface FSProvisionModel {
|
||||
|
||||
interface Dependencies extends BaseStoreDependencies {
|
||||
ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension;
|
||||
registeredExtensions: ObservableMap<LensExtensionId, string>;
|
||||
}
|
||||
|
||||
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||
readonly registeredExtensions = observable.map<LensExtensionId, string>();
|
||||
|
||||
constructor(protected readonly dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
configName: "lens-filesystem-provisioner-store",
|
||||
@@ -37,17 +37,17 @@ 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> {
|
||||
return this.dependencies.ensureHashedDirectoryForExtension(extensionName, this.registeredExtensions);
|
||||
return this.dependencies.ensureHashedDirectoryForExtension(extensionName);
|
||||
}
|
||||
|
||||
@action
|
||||
protected fromStore({ extensions }: FSProvisionModel = { extensions: {}}): void {
|
||||
this.registeredExtensions.merge(extensions);
|
||||
this.dependencies.registeredExtensions.merge(extensions);
|
||||
}
|
||||
|
||||
toJSON(): FSProvisionModel {
|
||||
return toJS({
|
||||
extensions: Object.fromEntries(this.registeredExtensions),
|
||||
extensions: Object.fromEntries(this.dependencies.registeredExtensions),
|
||||
});
|
||||
}
|
||||
}
|
||||
+22
@@ -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 directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
|
||||
export type GetPathToLegacyPackageJson = (extensionName: string) => string;
|
||||
|
||||
const getPathToLegacyPackageJson = getInjectable({
|
||||
id: "get-path-to-legacy-package-json",
|
||||
|
||||
instantiate: (di): GetPathToLegacyPackageJson => {
|
||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return (extensionName: string) => joinPaths(directoryForUserData, "node_modules", extensionName, "package.json");
|
||||
},
|
||||
});
|
||||
|
||||
export default getPathToLegacyPackageJson;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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 { observable } from "mobx";
|
||||
import type { LensExtensionId } from "../../lens-extension";
|
||||
|
||||
export const registeredExtensionsInjectable = getInjectable({
|
||||
id: "registered-extensions",
|
||||
instantiate: () => observable.map<LensExtensionId, string>(),
|
||||
});
|
||||
@@ -38,6 +38,8 @@ import kubeEventApiInjectable from "../../common/k8s-api/endpoints/events.api.in
|
||||
import roleBindingApiInjectable from "../../common/k8s-api/endpoints/role-binding.api.injectable";
|
||||
import customResourceDefinitionApiInjectable from "../../common/k8s-api/endpoints/custom-resource-definition.api.injectable";
|
||||
import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token";
|
||||
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||
import requestMetricsInjectable from "../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable";
|
||||
|
||||
export function isAllowedResource(resources: KubeResource | KubeResource[]) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
@@ -93,6 +95,17 @@ export const crdApi = asLegacyGlobalForExtensionApi(customResourceDefinitionApiI
|
||||
|
||||
export * from "../common-api/k8s-api";
|
||||
|
||||
export const requestMetrics = asLegacyGlobalFunctionForExtensionApi(requestMetricsInjectable);
|
||||
|
||||
export type {
|
||||
RequestMetrics,
|
||||
RequestMetricsParams,
|
||||
} from "../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable";
|
||||
export type {
|
||||
MetricData,
|
||||
MetricResult,
|
||||
} from "../../common/k8s-api/endpoints/metrics.api";
|
||||
|
||||
export {
|
||||
KubeObjectStatusLevel,
|
||||
type KubeObjectStatus,
|
||||
|
||||
+494
@@ -4507,3 +4507,497 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`cluster - sidebar and tab navigation for extensions given extension with cluster pages and cluster page menus with explicit 'orderNumber' given no state for expanded sidebar items exists, and navigated to child sidebar item, when rendered renders 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Animate slide-right Drawer KubeObjectDetails flex column right enter leave"
|
||||
style="--size: 725px; --enter-duration: 100ms; --leave-duration: 100ms;"
|
||||
>
|
||||
<div
|
||||
class="drawer-wrapper flex column"
|
||||
>
|
||||
<div
|
||||
class="drawer-title flex align-center"
|
||||
>
|
||||
<div
|
||||
class="drawer-title-text flex gaps align-center"
|
||||
>
|
||||
|
||||
</div>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="close"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Close
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="drawer-content flex column box grow"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ResizingAnchor horizontal leading"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="Notifications flex column align-flex-end"
|
||||
/>
|
||||
<div
|
||||
class="mainLayout"
|
||||
style="--sidebar-width: 200px;"
|
||||
>
|
||||
<div
|
||||
class="sidebar"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col"
|
||||
data-testid="cluster-sidebar"
|
||||
>
|
||||
<div
|
||||
class="SidebarCluster"
|
||||
>
|
||||
<div
|
||||
class="Avatar rounded loadingAvatar"
|
||||
style="width: 40px; height: 40px;"
|
||||
>
|
||||
??
|
||||
</div>
|
||||
<div
|
||||
class="loadingClusterName"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="sidebarNav sidebar-active-status"
|
||||
>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="true"
|
||||
data-testid="sidebar-item-some-extension-name-some-parent-id"
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
class="navItem active"
|
||||
data-testid="sidebar-item-link-for-some-extension-name-some-parent-id"
|
||||
href="/"
|
||||
>
|
||||
<div>
|
||||
Some icon
|
||||
</div>
|
||||
<span>
|
||||
Parent
|
||||
</span>
|
||||
<i
|
||||
class="Icon expandIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-workloads"
|
||||
>
|
||||
<a
|
||||
class="navItem"
|
||||
data-testid="sidebar-item-link-for-workloads"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon svg focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
/>
|
||||
</i>
|
||||
<span>
|
||||
Workloads
|
||||
</span>
|
||||
<i
|
||||
class="Icon expandIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-config"
|
||||
>
|
||||
<a
|
||||
class="navItem"
|
||||
data-testid="sidebar-item-link-for-config"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="list"
|
||||
>
|
||||
list
|
||||
</span>
|
||||
</i>
|
||||
<span>
|
||||
Config
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-network"
|
||||
>
|
||||
<a
|
||||
class="navItem"
|
||||
data-testid="sidebar-item-link-for-network"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="device_hub"
|
||||
>
|
||||
device_hub
|
||||
</span>
|
||||
</i>
|
||||
<span>
|
||||
Network
|
||||
</span>
|
||||
<i
|
||||
class="Icon expandIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-storage"
|
||||
>
|
||||
<a
|
||||
class="navItem"
|
||||
data-testid="sidebar-item-link-for-storage"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="storage"
|
||||
>
|
||||
storage
|
||||
</span>
|
||||
</i>
|
||||
<span>
|
||||
Storage
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-helm"
|
||||
>
|
||||
<a
|
||||
class="navItem"
|
||||
data-testid="sidebar-item-link-for-helm"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon svg focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
/>
|
||||
</i>
|
||||
<span>
|
||||
Helm
|
||||
</span>
|
||||
<i
|
||||
class="Icon expandIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-user-management"
|
||||
>
|
||||
<a
|
||||
class="navItem"
|
||||
data-testid="sidebar-item-link-for-user-management"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="security"
|
||||
>
|
||||
security
|
||||
</span>
|
||||
</i>
|
||||
<span>
|
||||
Access Control
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarItem"
|
||||
data-is-active-test="false"
|
||||
data-testid="sidebar-item-custom-resources"
|
||||
>
|
||||
<a
|
||||
class="navItem"
|
||||
data-testid="sidebar-item-link-for-custom-resources"
|
||||
href="/"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="extension"
|
||||
>
|
||||
extension
|
||||
</span>
|
||||
</i>
|
||||
<span>
|
||||
Custom Resources
|
||||
</span>
|
||||
<i
|
||||
class="Icon expandIcon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="keyboard_arrow_down"
|
||||
>
|
||||
keyboard_arrow_down
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ResizingAnchor horizontal trailing"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="contents"
|
||||
>
|
||||
<div
|
||||
class="TabLayout"
|
||||
data-testid="tab-layout"
|
||||
>
|
||||
<div
|
||||
class="Tabs center scrollable"
|
||||
>
|
||||
<div
|
||||
class="Tab flex gaps align-center active"
|
||||
data-is-active-test="true"
|
||||
data-testid="tab-link-for-some-extension-name-some-child-id"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="label"
|
||||
>
|
||||
Child 1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="Tab flex gaps align-center"
|
||||
data-is-active-test="false"
|
||||
data-testid="tab-link-for-some-extension-name-some-other-child-id"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="label"
|
||||
>
|
||||
Child 2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main
|
||||
class=""
|
||||
>
|
||||
<div
|
||||
data-testid="some-child-page"
|
||||
>
|
||||
Some child page
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="footer"
|
||||
>
|
||||
<div
|
||||
class="Dock"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="ResizingAnchor vertical leading"
|
||||
/>
|
||||
<div
|
||||
class="tabs-container flex align-center"
|
||||
>
|
||||
<div
|
||||
class="dockTabs"
|
||||
role="tablist"
|
||||
>
|
||||
<div
|
||||
class="Tabs tabs"
|
||||
>
|
||||
<div
|
||||
class="Tab flex gaps align-center DockTab TerminalTab active"
|
||||
data-testid="dock-tab-for-terminal"
|
||||
id="tab-terminal"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
class="Icon material focusable"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="terminal"
|
||||
>
|
||||
terminal
|
||||
</span>
|
||||
</i>
|
||||
<div
|
||||
class="label"
|
||||
>
|
||||
<div
|
||||
class="flex align-center"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Terminal
|
||||
</span>
|
||||
<div
|
||||
class="close"
|
||||
>
|
||||
<i
|
||||
class="Icon material interactive focusable small"
|
||||
data-testid="dock-tab-close-for-terminal"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="close"
|
||||
>
|
||||
close
|
||||
</span>
|
||||
</i>
|
||||
<div
|
||||
data-testid="tooltip-content-for-dock-tab-close-for-terminal"
|
||||
>
|
||||
Close ⌘+W
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="toolbar flex gaps align-center box grow"
|
||||
>
|
||||
<div
|
||||
class="dock-menu box grow"
|
||||
>
|
||||
<i
|
||||
class="Icon new-dock-tab material interactive focusable"
|
||||
id="menu-actions-for-dock"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="add"
|
||||
>
|
||||
add
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
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_up"
|
||||
>
|
||||
keyboard_arrow_up
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Open
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
+15
-3
@@ -79,7 +79,11 @@ exports[`disable kube object detail items when cluster is not relevant given ext
|
||||
<span
|
||||
class="value"
|
||||
>
|
||||
some-namespace
|
||||
<a
|
||||
href="/workloads?kube-details=%2Fapi%2Fv1%2Fnamespaces%2Fsome-namespace&kube-selected=%2Fapis%2Fsome-api-version%2Fnamespaces%2Fsome-namespace%2Fsome-kind%2Fsome-name"
|
||||
>
|
||||
some-namespace
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@@ -695,7 +699,11 @@ exports[`disable kube object detail items when cluster is not relevant given ext
|
||||
<span
|
||||
class="value"
|
||||
>
|
||||
some-namespace
|
||||
<a
|
||||
href="/workloads?kube-details=%2Fapi%2Fv1%2Fnamespaces%2Fsome-namespace&kube-selected=%2Fapis%2Fsome-api-version%2Fnamespaces%2Fsome-namespace%2Fsome-kind%2Fsome-name"
|
||||
>
|
||||
some-namespace
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -1306,7 +1314,11 @@ exports[`disable kube object detail items when cluster is not relevant given not
|
||||
<span
|
||||
class="value"
|
||||
>
|
||||
some-namespace
|
||||
<a
|
||||
href="/workloads?kube-details=%2Fapi%2Fv1%2Fnamespaces%2Fsome-namespace&kube-selected=%2Fapis%2Fsome-api-version%2Fnamespaces%2Fsome-namespace%2Fsome-kind%2Fsome-name"
|
||||
>
|
||||
some-namespace
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
+10
-2
@@ -79,7 +79,11 @@ exports[`reactively hide kube object detail item renders 1`] = `
|
||||
<span
|
||||
class="value"
|
||||
>
|
||||
some-namespace
|
||||
<a
|
||||
href="/workloads?kube-details=%2Fapi%2Fv1%2Fnamespaces%2Fsome-namespace&kube-selected=%2Fapis%2Fsome-api-version%2Fnamespaces%2Fsome-namespace%2Fsome-kind%2Fsome-name"
|
||||
>
|
||||
some-namespace
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -690,7 +694,11 @@ exports[`reactively hide kube object detail item when the item is shown renders
|
||||
<span
|
||||
class="value"
|
||||
>
|
||||
some-namespace
|
||||
<a
|
||||
href="/workloads?kube-details=%2Fapi%2Fv1%2Fnamespaces%2Fsome-namespace&kube-selected=%2Fapis%2Fsome-api-version%2Fnamespaces%2Fsome-namespace%2Fsome-kind%2Fsome-name"
|
||||
>
|
||||
some-namespace
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
||||
+131
-6
@@ -21,6 +21,7 @@ import { runInAction, computed, observable } from "mobx";
|
||||
import storageSaveDelayInjectable from "../../renderer/utils/create-storage/storage-save-delay.injectable";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { flushPromises } from "../../common/test-utils/flush-promises";
|
||||
import type { ClusterPageMenuRegistration } from "../../extensions/common-api/types";
|
||||
|
||||
describe("cluster - sidebar and tab navigation for extensions", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
@@ -139,15 +140,12 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
|
||||
const route = windowDi
|
||||
.inject(routesInjectable)
|
||||
.get()
|
||||
.find(
|
||||
matches({
|
||||
path: "/extension/some-extension-name/some-child-page-id",
|
||||
}),
|
||||
);
|
||||
.find(matches({
|
||||
path: "/extension/some-extension-name/some-child-page-id",
|
||||
}));
|
||||
|
||||
assert(route);
|
||||
navigateToRoute(route);
|
||||
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
@@ -453,4 +451,131 @@ describe("cluster - sidebar and tab navigation for extensions", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("given extension with cluster pages and cluster page menus with explicit 'orderNumber'", () => {
|
||||
let someObservable: IObservableValue<boolean>;
|
||||
|
||||
beforeEach(() => {
|
||||
someObservable = observable.box(false);
|
||||
|
||||
const testExtension = {
|
||||
id: "some-extension-id",
|
||||
name: "some-extension-name",
|
||||
|
||||
rendererOptions: {
|
||||
clusterPages: [
|
||||
{
|
||||
components: {
|
||||
Page: () => {
|
||||
throw new Error("should never come here");
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "some-child-page-id",
|
||||
|
||||
components: {
|
||||
Page: () => <div data-testid="some-child-page">Some child page</div>,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "some-other-child-page-id",
|
||||
|
||||
components: {
|
||||
Page: () => (
|
||||
<div data-testid="some-other-child-page">Some other child page</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
clusterPageMenus: [
|
||||
{
|
||||
id: "some-parent-id",
|
||||
title: "Parent",
|
||||
components: {
|
||||
Icon: () => <div>Some icon</div>,
|
||||
},
|
||||
orderNumber: -Infinity,
|
||||
},
|
||||
{
|
||||
id: "some-child-id",
|
||||
target: { pageId: "some-child-page-id" },
|
||||
parentId: "some-parent-id",
|
||||
title: "Child 1",
|
||||
components: {
|
||||
Icon: null as never,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "some-other-child-id",
|
||||
target: { pageId: "some-other-child-page-id" },
|
||||
parentId: "some-parent-id",
|
||||
title: "Child 2",
|
||||
components: {
|
||||
Icon: null as never,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "some-menu-with-controlled-visibility",
|
||||
title: "Some menu with controlled visibility",
|
||||
visible: computed(() => someObservable.get()),
|
||||
components: {
|
||||
Icon: () => <div>Some icon</div>,
|
||||
},
|
||||
},
|
||||
] as ClusterPageMenuRegistration[],
|
||||
},
|
||||
};
|
||||
|
||||
applicationBuilder.extensions.enable(testExtension);
|
||||
});
|
||||
|
||||
describe("given no state for expanded sidebar items exists, and navigated to child sidebar item, when rendered", () => {
|
||||
beforeEach(async () => {
|
||||
rendered = await applicationBuilder.render();
|
||||
|
||||
const windowDi = applicationBuilder.applicationWindow.only.di;
|
||||
|
||||
const navigateToRoute = windowDi.inject(navigateToRouteInjectionToken);
|
||||
|
||||
const route = windowDi
|
||||
.inject(routesInjectable)
|
||||
.get()
|
||||
.find(matches({
|
||||
path: "/extension/some-extension-name/some-child-page-id",
|
||||
}));
|
||||
|
||||
assert(route);
|
||||
navigateToRoute(route);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renderes parent as first item in sidebar", () => {
|
||||
const parent = rendered.getByTestId("sidebar-item-some-extension-name-some-parent-id");
|
||||
|
||||
assert(parent);
|
||||
expect(parent.previousSibling).toBeNull();
|
||||
});
|
||||
|
||||
it("parent is highlighted", () => {
|
||||
const parent = rendered.getByTestId("sidebar-item-some-extension-name-some-parent-id");
|
||||
|
||||
expect(parent?.dataset.isActiveTest).toBe("true");
|
||||
});
|
||||
|
||||
it("parent sidebar item is not expanded", () => {
|
||||
const child = rendered.queryByTestId("sidebar-item-some-extension-name-some-child-id");
|
||||
|
||||
expect(child).toBeNull();
|
||||
});
|
||||
|
||||
it("child page is shown", () => {
|
||||
expect(rendered.getByTestId("some-child-page")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -29,17 +29,9 @@ export async function bootstrap(di: DiContainer) {
|
||||
|
||||
assert(rootElem, "#app MUST exist");
|
||||
|
||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
||||
|
||||
extensionLoader.init();
|
||||
|
||||
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||
|
||||
extensionDiscovery.init();
|
||||
|
||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||
|
||||
extensionInstallationStateStore.bindIpcListeners();
|
||||
await di.inject(extensionLoaderInjectable).init();
|
||||
await di.inject(extensionDiscoveryInjectable).init();
|
||||
di.inject(extensionInstallationStateStoreInjectable).bindIpcListeners();
|
||||
|
||||
let App;
|
||||
let initializeApp;
|
||||
|
||||
@@ -9,7 +9,7 @@ import React from "react";
|
||||
import upperFirst from "lodash/upperFirst";
|
||||
import kebabCase from "lodash/kebabCase";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { DrawerItem, DrawerItemLabels } from "../drawer";
|
||||
import { DrawerItem } from "../drawer";
|
||||
import { Badge } from "../badge";
|
||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||
import { formatNodeTaint, Node } from "../../../common/k8s-api/endpoints";
|
||||
@@ -23,7 +23,8 @@ import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.inj
|
||||
import type { PodStore } from "../+workloads-pods/store";
|
||||
import podStoreInjectable from "../+workloads-pods/store.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import loadPodsFromAllNamespacesInjectable from "../+workloads-pods/load-pods-from-all-namespaces.injectable";
|
||||
import loadPodsFromAllNamespacesInjectable
|
||||
from "../+workloads-pods/load-pods-from-all-namespaces.injectable";
|
||||
|
||||
export interface NodeDetailsProps extends KubeObjectDetailsProps<Node> {
|
||||
}
|
||||
@@ -97,14 +98,6 @@ class NonInjectedNodeDetails extends React.Component<NodeDetailsProps & Dependen
|
||||
</DrawerItem>
|
||||
</>
|
||||
)}
|
||||
<DrawerItemLabels
|
||||
name="Labels"
|
||||
labels={node.getLabels()}
|
||||
/>
|
||||
<DrawerItemLabels
|
||||
name="Annotations"
|
||||
labels={node.getAnnotations()}
|
||||
/>
|
||||
{taints.length > 0 && (
|
||||
<DrawerItem name="Taints" labelsOnly>
|
||||
{taints.map(taint => <Badge key={taint.key} label={formatNodeTaint(taint)} />)}
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
--overlay-bg: #8cc474b8;
|
||||
--overlay-active-bg: orange;
|
||||
|
||||
// fix for `this.logsElement.scrollTop = this.logsElement.scrollHeight`
|
||||
// `overflow: overlay` don't allow scroll to the last line
|
||||
overflow: auto;
|
||||
|
||||
position: relative;
|
||||
color: var(--logsForeground);
|
||||
background: var(--logsBackground);
|
||||
@@ -21,6 +17,7 @@
|
||||
|
||||
.list {
|
||||
overflow-x: scroll!important;
|
||||
overflow-y: auto!important;
|
||||
|
||||
.LogRow {
|
||||
padding: 2px 16px;
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface InstantiateArgs {
|
||||
const logsViewModelInjectable = getInjectable({
|
||||
id: "logs-view-model",
|
||||
|
||||
instantiate: (di, { tabId }: InstantiateArgs) => new LogTabViewModel(tabId, {
|
||||
instantiate: (di, tabId) => new LogTabViewModel(tabId, {
|
||||
getLogs: di.inject(getLogsInjectable),
|
||||
getLogsWithoutTimestamps: di.inject(getLogsWithoutTimestampsInjectable),
|
||||
getTimestampSplitLogs: di.inject(getTimestampSplitLogsInjectable),
|
||||
@@ -45,7 +45,10 @@ const logsViewModelInjectable = getInjectable({
|
||||
downloadAllLogs: di.inject(downloadAllLogsInjectable),
|
||||
searchStore: di.inject(searchStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
|
||||
lifecycle: lifecycleEnum.keyedSingleton({
|
||||
getInstanceKey: (di, tabId: TabId) => `log-tab-view-model-${tabId}}`,
|
||||
}),
|
||||
});
|
||||
|
||||
export default logsViewModelInjectable;
|
||||
@@ -101,9 +101,7 @@ const NonInjectedLogsDockTab = observer(({
|
||||
export const LogsDockTab = withInjectables<Dependencies, LogsDockTabProps>(NonInjectedLogsDockTab, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
model: di.inject(logsViewModelInjectable, {
|
||||
tabId: props.tab.id,
|
||||
}),
|
||||
model: di.inject(logsViewModelInjectable, props.tab.id),
|
||||
subscribeStores: di.inject(subscribeStoresInjectable),
|
||||
podStore: di.inject(podStoreInjectable),
|
||||
}),
|
||||
|
||||
@@ -19,6 +19,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable";
|
||||
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import type { NamespaceApi } from "../../../common/k8s-api/endpoints";
|
||||
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
|
||||
|
||||
export interface KubeObjectMetaProps {
|
||||
object: KubeObject;
|
||||
@@ -28,20 +30,24 @@ export interface KubeObjectMetaProps {
|
||||
interface Dependencies {
|
||||
getDetailsUrl: GetDetailsUrl;
|
||||
apiManager: ApiManager;
|
||||
namespaceApi: NamespaceApi;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
const NonInjectedKubeObjectMeta = observer(({
|
||||
apiManager,
|
||||
getDetailsUrl,
|
||||
object,
|
||||
hideFields = [
|
||||
"uid",
|
||||
"resourceVersion",
|
||||
"selfLink",
|
||||
],
|
||||
logger,
|
||||
}: Dependencies & KubeObjectMetaProps) => {
|
||||
const NonInjectedKubeObjectMeta = observer((
|
||||
{
|
||||
apiManager,
|
||||
getDetailsUrl,
|
||||
object,
|
||||
hideFields = [
|
||||
"uid",
|
||||
"resourceVersion",
|
||||
"selfLink",
|
||||
],
|
||||
logger,
|
||||
namespaceApi,
|
||||
}
|
||||
: Dependencies & KubeObjectMetaProps) => {
|
||||
if (!object) {
|
||||
return null;
|
||||
}
|
||||
@@ -59,6 +65,10 @@ const NonInjectedKubeObjectMeta = observer(({
|
||||
getFinalizers, getId, getName, metadata: { creationTimestamp },
|
||||
} = object;
|
||||
const ownerRefs = object.getOwnerRefs();
|
||||
const namespace = getNs();
|
||||
const namespaceDetailsUrl = namespace ? getDetailsUrl(
|
||||
namespaceApi.formatUrlForNotListing({ name: namespace }),
|
||||
) : "";
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -71,8 +81,8 @@ const NonInjectedKubeObjectMeta = observer(({
|
||||
{getName()}
|
||||
<KubeObjectStatusIcon key="icon" object={object} />
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Namespace" hidden={isHidden("namespace") || !getNs()}>
|
||||
{getNs()}
|
||||
<DrawerItem name="Namespace" hidden={isHidden("namespace") || !namespace}>
|
||||
<Link to={namespaceDetailsUrl}>{namespace}</Link>
|
||||
</DrawerItem>
|
||||
<DrawerItem name="UID" hidden={isHidden("uid")}>
|
||||
{getId()}
|
||||
@@ -122,5 +132,6 @@ export const KubeObjectMeta = withInjectables<Dependencies, KubeObjectMetaProps>
|
||||
getDetailsUrl: di.inject(getDetailsUrlInjectable),
|
||||
apiManager: di.inject(apiManagerInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
namespaceApi: di.inject(namespaceApiInjectable),
|
||||
}),
|
||||
});
|
||||
@@ -15,6 +15,7 @@ export interface ClusterPageMenuRegistration {
|
||||
title: React.ReactNode;
|
||||
components: ClusterPageMenuComponents;
|
||||
visible?: IComputedValue<boolean>;
|
||||
orderNumber?: number;
|
||||
}
|
||||
|
||||
export interface ClusterPageMenuComponents {
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ const extensionSidebarItemRegistratorInjectable = getInjectable({
|
||||
|
||||
const res: SidebarItemRegistration = {
|
||||
id: `${extension.sanitizedExtensionId}-${registration.id}`,
|
||||
orderNumber: 9999,
|
||||
orderNumber: registration.orderNumber ?? 9999,
|
||||
|
||||
parentId: registration.parentId
|
||||
? `${extension.sanitizedExtensionId}-${registration.parentId}`
|
||||
|
||||
@@ -36,7 +36,7 @@ const webpackLensMain = (): webpack.Configuration => {
|
||||
extensions: [".json", ".js", ".ts"],
|
||||
},
|
||||
externals: [
|
||||
nodeExternals(),
|
||||
nodeExternals({ modulesFromFile: true }),
|
||||
],
|
||||
module: {
|
||||
parser: {
|
||||
|
||||
@@ -46,7 +46,7 @@ export function webpackLensRenderer(): webpack.Configuration {
|
||||
],
|
||||
},
|
||||
externals: [
|
||||
nodeExternals(),
|
||||
nodeExternals({ modulesFromFile: true }),
|
||||
],
|
||||
optimization: {
|
||||
minimize: false,
|
||||
|
||||
-12948
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user