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

Fix runtime errors on renderer

- remove all static uses of `createPageParam` (and then removed the
  legacy global)
- Made LensRendererExtension and LensMainExtension just used
  dependencies and not the getLegacyDi
- Fixed circular dep in extension-loader

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-04-22 15:26:18 -04:00
parent fd86ea0d97
commit a12bda2fc4
102 changed files with 1323 additions and 978 deletions

View File

@ -259,6 +259,10 @@ module.exports = {
"name": ".", "name": ".",
"message": "No importing from local index.ts(x?) file. A common way to make circular dependencies.", "message": "No importing from local index.ts(x?) file. A common way to make circular dependencies.",
}, },
{
"name": "..",
"message": "No importing from parent index.ts(x?) file. A common way to make circular dependencies.",
},
], ],
"patterns": [ "patterns": [
{ {

View File

@ -3,9 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import packageInfo from "../package.json"; import packageInfo from "../package.json";
import { type WriteStream } from "fs";
import type { FileHandle } from "fs/promises"; import type { FileHandle } from "fs/promises";
import { open } from "fs/promises"; import { open } from "fs/promises";
import type { WriteStream } from "fs-extra";
import { constants, ensureDir, unlink } from "fs-extra"; import { constants, ensureDir, unlink } from "fs-extra";
import path from "path"; import path from "path";
import fetch from "node-fetch"; import fetch from "node-fetch";
@ -17,6 +17,7 @@ import AbortController from "abort-controller";
import { extract } from "tar-stream"; import { extract } from "tar-stream";
import gunzip from "gunzip-maybe"; import gunzip from "gunzip-maybe";
import { getBinaryName, normalizedPlatform } from "../src/common/vars"; import { getBinaryName, normalizedPlatform } from "../src/common/vars";
import { isErrnoException } from "../src/common/utils";
const pipeline = promisify(_pipeline); const pipeline = promisify(_pipeline);
@ -44,6 +45,10 @@ abstract class BinaryDownloader {
} }
async ensureBinary(): Promise<void> { async ensureBinary(): Promise<void> {
if (process.env.LENS_SKIP_DOWNLOAD_BINARIES === "true") {
return;
}
const controller = new AbortController(); const controller = new AbortController();
const stream = await fetch(this.url, { const stream = await fetch(this.url, {
timeout: 15 * 60 * 1000, // 15min timeout: 15 * 60 * 1000, // 15min
@ -51,7 +56,7 @@ abstract class BinaryDownloader {
}); });
const total = Number(stream.headers.get("content-length")); const total = Number(stream.headers.get("content-length"));
const bar = this.bar; const bar = this.bar;
let fileHandle: FileHandle; let fileHandle: FileHandle | undefined = undefined;
if (isNaN(total)) { if (isNaN(total)) {
throw new Error("no content-length header was present"); throw new Error("no content-length header was present");
@ -66,7 +71,7 @@ abstract class BinaryDownloader {
* This is necessary because for some reason `createWriteStream({ flags: "wx" })` * This is necessary because for some reason `createWriteStream({ flags: "wx" })`
* was throwing someplace else and not here * was throwing someplace else and not here
*/ */
fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL); const handle = fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
await pipeline( await pipeline(
stream.body, stream.body,
@ -79,7 +84,7 @@ abstract class BinaryDownloader {
}), }),
...this.getTransformStreams(new Writable({ ...this.getTransformStreams(new Writable({
write(chunk, encoding, cb) { write(chunk, encoding, cb) {
fileHandle.write(chunk) handle.write(chunk)
.then(() => cb()) .then(() => cb())
.catch(cb); .catch(cb);
}, },
@ -90,7 +95,7 @@ abstract class BinaryDownloader {
} catch (error) { } catch (error) {
await fileHandle?.close(); await fileHandle?.close();
if (error.code === "EEXIST") { if (isErrnoException(error) && error.code === "EEXIST") {
bar.increment(total); // mark as finished bar.increment(total); // mark as finished
controller.abort(); // stop trying to download controller.abort(); // stop trying to download
} else { } else {

View File

@ -9,8 +9,8 @@ import { getApplicationBuilder } from "../../renderer/components/test-utils/get-
import isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable"; import isAutoUpdateEnabledInjectable from "../../main/is-auto-update-enabled.injectable";
import extensionsStoreInjectable from "../../extensions/extensions-store/extensions-store.injectable"; import extensionsStoreInjectable from "../../extensions/extensions-store/extensions-store.injectable";
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store"; import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store";
import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable"; import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { FileSystemProvisionerStore } from "../../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store"; import type { FileSystemProvisionerStore } from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
import focusWindowInjectable from "../../renderer/ipc-channel-listeners/focus-window.injectable"; import focusWindowInjectable from "../../renderer/ipc-channel-listeners/focus-window.injectable";
// TODO: Make components free of side effects by making them deterministic // TODO: Make components free of side effects by making them deterministic

View File

@ -2,20 +2,30 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { kubernetesClusterCategory } from "../kubernetes-cluster";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
import kubernetesClusterCategoryInjectable from "../../catalog/categories/kubernetes-cluster.injectable";
import type { KubernetesClusterCategory } from "../kubernetes-cluster";
describe("kubernetesClusterCategory", () => { describe("kubernetesClusterCategory", () => {
let kubernetesClusterCategory: KubernetesClusterCategory;
beforeEach(() => {
const di = getDiForUnitTesting();
kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable);
});
describe("filteredItems", () => { describe("filteredItems", () => {
const item1 = { const item1 = {
icon: "Icon", icon: "Icon",
title: "Title", title: "Title",
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClick: () => {}, onClick: () => {},
}; };
const item2 = { const item2 = {
icon: "Icon 2", icon: "Icon 2",
title: "Title 2", title: "Title 2",
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClick: () => {}, onClick: () => {},
}; };

View File

@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import { WebLinkCategory } from "../catalog-entities";
import type { CatalogCategory } from "./catalog-entity"; import type { CatalogCategory } from "./catalog-entity";
import { CatalogCategoryRegistry } from "./category-registry"; import { CatalogCategoryRegistry } from "./category-registry";
@ -21,8 +20,6 @@ const catalogCategoryRegistryInjectable = getInjectable({
registry.add(category); registry.add(category);
} }
registry.add(new WebLinkCategory());
return registry; return registry;
}, },
}); });

View File

@ -4,6 +4,7 @@
*/ */
import { runInAction } from "mobx"; import { runInAction } from "mobx";
import { inspect } from "util";
import { isDefined } from "./type-narrowing"; import { isDefined } from "./type-narrowing";
/** /**
@ -79,7 +80,7 @@ export async function getOrInsertWithAsync<K, V>(map: Map<K, V>, key: K, asyncBu
*/ */
export function strictSet<K, V>(map: Map<K, V>, key: K, val: V): typeof map { export function strictSet<K, V>(map: Map<K, V>, key: K, val: V): typeof map {
if (map.has(key)) { if (map.has(key)) {
throw new TypeError("Duplicate key in map"); throw new TypeError(`Map already contains key: ${inspect(key)}`);
} }
return map.set(key, val); return map.set(key, val);
@ -92,7 +93,7 @@ export function strictSet<K, V>(map: Map<K, V>, key: K, val: V): typeof map {
*/ */
export function strictGet<K, V>(map: Map<K, V>, key: K): V { export function strictGet<K, V>(map: Map<K, V>, key: K): V {
if (!map.has(key)) { if (!map.has(key)) {
throw new TypeError("key not in map"); throw new TypeError(`Map does not contains key: ${inspect(key)}`);
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion

View File

@ -0,0 +1,14 @@
/**
* 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 { InstalledExtension } from "../extension-discovery/extension-discovery";
import type { LensExtension, LensExtensionConstructor } from "../lens-extension";
export type CreateExtensionInstance = (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension;
export const createExtensionInstanceInjectionToken = getInjectionToken<CreateExtensionInstance>({
id: "create-extension-instance-token",
});

View File

@ -1,17 +0,0 @@
/**
* 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 { createExtensionInstance } from "./create-extension-instance";
import fileSystemProvisionerStoreInjectable from "./file-system-provisioner-store/file-system-provisioner-store.injectable";
const createExtensionInstanceInjectable = getInjectable({
id: "create-extension-instance",
instantiate: (di) => createExtensionInstance({
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
}),
});
export default createExtensionInstanceInjectable;

View File

@ -1,21 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionConstructor } from "../../lens-extension";
import type { InstalledExtension } from "../../extension-discovery/extension-discovery";
import type {
LensExtensionDependencies } from "../../lens-extension-set-dependencies";
import {
setLensExtensionDependencies,
} from "../../lens-extension-set-dependencies";
export const createExtensionInstance =
(dependencies: LensExtensionDependencies) =>
(ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => {
const instance = new ExtensionClass(extension);
instance[setLensExtensionDependencies](dependencies);
return instance;
};

View File

@ -0,0 +1,14 @@
/**
* 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, LensExtension } from "../lens-extension";
const extensionInstancesInjectable = getInjectable({
id: "extension-instances",
instantiate: () => observable.map<LensExtensionId, LensExtension>(),
});
export default extensionInstancesInjectable;

View File

@ -5,19 +5,20 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { ExtensionLoader } from "./extension-loader"; import { ExtensionLoader } from "./extension-loader";
import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable"; import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable";
import createExtensionInstanceInjectable from "./create-extension-instance/create-extension-instance.injectable";
import { extensionRegistratorInjectionToken } from "./extension-registrator-injection-token"; import { extensionRegistratorInjectionToken } from "./extension-registrator-injection-token";
import extensionInstallationCounterInjectable from "./extension-installation-counter.injectable"; import extensionInstallationCounterInjectable from "./extension-installation-counter.injectable";
import { createExtensionInstanceInjectionToken } from "./create-extension-instance.token";
import extensionInstancesInjectable from "./extension-instances.injectable";
const extensionLoaderInjectable = getInjectable({ const extensionLoaderInjectable = getInjectable({
id: "extension-loader", id: "extension-loader",
instantiate: (di) => instantiate: (di) => new ExtensionLoader({
new ExtensionLoader({
updateExtensionsState: di.inject(updateExtensionsStateInjectable), updateExtensionsState: di.inject(updateExtensionsStateInjectable),
createExtensionInstance: di.inject(createExtensionInstanceInjectable), createExtensionInstance: di.inject(createExtensionInstanceInjectionToken),
extensionRegistrators: di.injectMany(extensionRegistratorInjectionToken), extensionRegistrators: di.injectMany(extensionRegistratorInjectionToken),
extensionInstallationCounter: di.inject(extensionInstallationCounterInjectable), extensionInstallationCounter: di.inject(extensionInstallationCounterInjectable),
extensionInstances: di.inject(extensionInstancesInjectable),
}), }),
}); });

View File

@ -4,8 +4,8 @@
*/ */
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { EventEmitter } from "events";
import { isEqual } from "lodash"; import { isEqual } from "lodash";
import type { ObservableMap } from "mobx";
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx"; import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
import path from "path"; import path from "path";
import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc"; import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc";
@ -21,14 +21,17 @@ import type { LensExtensionState } from "../extensions-store/extensions-store";
import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling"; import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling";
import { requestExtensionLoaderInitialState } from "../../renderer/ipc"; import { requestExtensionLoaderInitialState } from "../../renderer/ipc";
import assert from "assert"; import assert from "assert";
import { EventEmitter } from "../../common/event-emitter";
import type { CreateExtensionInstance } from "./create-extension-instance.token";
const logModule = "[EXTENSIONS-LOADER]"; const logModule = "[EXTENSIONS-LOADER]";
interface Dependencies { interface Dependencies {
updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void; updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void;
createExtensionInstance: (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension; createExtensionInstance: CreateExtensionInstance;
extensionRegistrators: ((extension: LensExtension, extensionInstallationCount: number) => void)[]; readonly extensionRegistrators: ((extension: LensExtension, extensionInstallationCount: number) => void)[];
extensionInstallationCounter: Map<string, number>; readonly extensionInstallationCounter: Map<string, number>;
readonly extensionInstances: ObservableMap<LensExtensionId, LensExtension>;
} }
export interface ExtensionLoading { export interface ExtensionLoading {
@ -40,23 +43,21 @@ export interface ExtensionLoading {
* Loads installed extensions to the Lens application * Loads installed extensions to the Lens application
*/ */
export class ExtensionLoader { export class ExtensionLoader {
protected extensions = observable.map<LensExtensionId, InstalledExtension>(); protected readonly extensions = observable.map<LensExtensionId, InstalledExtension>();
protected instances = observable.map<LensExtensionId, LensExtension>();
/** /**
* This is the set of extensions that don't come with either * This is the set of extensions that don't come with either
* - Main.LensExtension when running in the main process * - Main.LensExtension when running in the main process
* - Renderer.LensExtension when running in the renderer process * - Renderer.LensExtension when running in the renderer process
*/ */
protected nonInstancesByName = observable.set<string>(); protected readonly nonInstancesByName = observable.set<string>();
/** /**
* This is updated by the `observe` in the constructor. DO NOT write directly to it * This is updated by the `observe` in the constructor. DO NOT write directly to it
*/ */
protected instancesByName = observable.map<string, LensExtension>(); protected readonly instancesByName = observable.map<string, LensExtension>();
// emits event "remove" of type LensExtension when the extension is removed private readonly onRemoveExtensionId = new EventEmitter<[string]>();
private events = new EventEmitter();
@observable isLoaded = false; @observable isLoaded = false;
@ -64,10 +65,10 @@ export class ExtensionLoader {
return when(() => this.isLoaded); return when(() => this.isLoaded);
} }
constructor(protected dependencies : Dependencies) { constructor(protected readonly dependencies: Dependencies) {
makeObservable(this); makeObservable(this);
observe(this.instances, change => { observe(this.dependencies.extensionInstances, change => {
switch (change.type) { switch (change.type) {
case "add": case "add":
if (this.instancesByName.has(change.newValue.name)) { if (this.instancesByName.has(change.newValue.name)) {
@ -85,10 +86,6 @@ export class ExtensionLoader {
}); });
} }
@computed get enabledExtensionInstances() : LensExtension[] {
return [...this.instances.values()].filter(extension => extension.isEnabled);
}
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> { @computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
const extensions = this.toJSON(); const extensions = this.toJSON();
@ -164,7 +161,7 @@ export class ExtensionLoader {
@action @action
removeInstance(lensExtensionId: LensExtensionId) { removeInstance(lensExtensionId: LensExtensionId) {
logger.info(`${logModule} deleting extension instance ${lensExtensionId}`); logger.info(`${logModule} deleting extension instance ${lensExtensionId}`);
const instance = this.instances.get(lensExtensionId); const instance = this.dependencies.extensionInstances.get(lensExtensionId);
if (!instance) { if (!instance) {
return; return;
@ -172,8 +169,8 @@ export class ExtensionLoader {
try { try {
instance.disable(); instance.disable();
this.events.emit("remove", instance); this.onRemoveExtensionId.emit(instance.id);
this.instances.delete(lensExtensionId); this.dependencies.extensionInstances.delete(lensExtensionId);
this.nonInstancesByName.delete(instance.name); this.nonInstancesByName.delete(instance.name);
} catch (error) { } catch (error) {
logger.error(`${logModule}: deactivation extension error`, { lensExtensionId, error }); logger.error(`${logModule}: deactivation extension error`, { lensExtensionId, error });
@ -260,8 +257,8 @@ export class ExtensionLoader {
registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems), registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems),
]; ];
this.events.on("remove", (removedExtension: LensRendererExtension) => { this.onRemoveExtensionId.addListener((removedExtensionId) => {
if (removedExtension.id === extension.id) { if (removedExtensionId === extension.id) {
removeItems.forEach(remove => { removeItems.forEach(remove => {
remove(); remove();
}); });
@ -288,8 +285,8 @@ export class ExtensionLoader {
registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems), registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems),
]; ];
this.events.on("remove", (removedExtension: LensRendererExtension) => { this.onRemoveExtensionId.addListener((removedExtensionId) => {
if (removedExtension.id === extension.id) { if (removedExtensionId === extension.id) {
removeItems.forEach(remove => { removeItems.forEach(remove => {
remove(); remove();
}); });
@ -309,7 +306,7 @@ export class ExtensionLoader {
const extensions = [...installedExtensions.entries()] const extensions = [...installedExtensions.entries()]
.map(([extId, extension]) => { .map(([extId, extension]) => {
const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name); const alreadyInit = this.dependencies.extensionInstances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
if (extension.isCompatible && extension.isEnabled && !alreadyInit) { if (extension.isCompatible && extension.isEnabled && !alreadyInit) {
try { try {
@ -326,7 +323,7 @@ export class ExtensionLoader {
extension, extension,
); );
this.instances.set(extId, instance); this.dependencies.extensionInstances.set(extId, instance);
return { return {
instance, instance,
@ -415,7 +412,7 @@ export class ExtensionLoader {
} }
getInstanceById(extId: LensExtensionId) { getInstanceById(extId: LensExtensionId) {
return this.instances.get(extId); return this.dependencies.extensionInstances.get(extId);
} }
toJSON(): Map<LensExtensionId, InstalledExtension> { toJSON(): Map<LensExtensionId, InstalledExtension> {

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import getAbsolutePathInjectable from "../../../../../common/path/get-absolute-path.injectable"; import getAbsolutePathInjectable from "../../../common/path/get-absolute-path.injectable";
const directoryForExtensionDataInjectable = getInjectable({ const directoryForExtensionDataInjectable = getInjectable({
id: "directory-for-extension-data", id: "directory-for-extension-data",

View File

@ -4,17 +4,18 @@
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { FileSystemProvisionerStore } from "./file-system-provisioner-store"; import { FileSystemProvisionerStore } from "./file-system-provisioner-store";
import directoryForExtensionDataInjectable from "./directory-for-extension-data/directory-for-extension-data.injectable"; import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
const fileSystemProvisionerStoreInjectable = getInjectable({ const fileSystemProvisionerStoreInjectable = getInjectable({
id: "file-system-provisioner-store", id: "file-system-provisioner-store",
instantiate: (di) => instantiate: (di) => {
FileSystemProvisionerStore.createInstance({ FileSystemProvisionerStore.resetInstance();
directoryForExtensionData: di.inject(
directoryForExtensionDataInjectable, return FileSystemProvisionerStore.createInstance({
), directoryForExtensionData: di.inject(directoryForExtensionDataInjectable),
}), });
},
causesSideEffects: true, causesSideEffects: true,
}); });

View File

@ -8,9 +8,9 @@ import { SHA256 } from "crypto-js";
import fse from "fs-extra"; import fse from "fs-extra";
import { action, makeObservable, observable } from "mobx"; import { action, makeObservable, observable } from "mobx";
import path from "path"; import path from "path";
import { BaseStore } from "../../../../common/base-store"; import { BaseStore } from "../../../common/base-store";
import type { LensExtensionId } from "../../../lens-extension"; import type { LensExtensionId } from "../../lens-extension";
import { getOrInsertWith, toJS } from "../../../../common/utils"; import { getOrInsertWith, toJS } from "../../../common/utils";
interface FSProvisionModel { interface FSProvisionModel {
extensions: Record<string, string>; // extension names to paths extensions: Record<string, string>; // extension names to paths

View File

@ -4,15 +4,14 @@
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx"; import { computed } from "mobx";
import extensionLoaderInjectable from "./extension-loader/extension-loader.injectable"; import extensionInstancesInjectable from "./extension-loader/extension-instances.injectable";
const extensionsInjectable = getInjectable({ const extensionsInjectable = getInjectable({
id: "extensions", id: "extensions",
instantiate: (di) => { instantiate: (di) => {
const extensionLoader = di.inject(extensionLoaderInjectable); const extensionInstances = di.inject(extensionInstancesInjectable);
return computed(() => extensionLoader.enabledExtensionInstances); return computed(() => [...extensionInstances.values()].filter(extension => extension.isEnabled));
}, },
}); });

View File

@ -3,12 +3,27 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { FileSystemProvisionerStore } from "./extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store"; import type { IComputedValue } from "mobx";
import type { CatalogCategoryRegistry } from "../common/catalog";
// This symbol encapsulates setting of dependencies to only happen locally in Lens Core import type { NavigateToRoute } from "../common/front-end-routing/navigate-to-route-injection-token";
// and not by e.g. authors of extensions import type { Route } from "../common/front-end-routing/route-injection-token";
export const setLensExtensionDependencies = Symbol("set-lens-extension-dependencies"); import type { CatalogEntityRegistry as MainCatalogEntityRegistry } from "../main/catalog";
import type { CatalogEntityRegistry as RendererCatalogEntityRegistry } from "../renderer/api/catalog/entity/registry";
import type { GetExtensionPageParameters } from "../renderer/routes/get-extension-page-parameters.injectable";
import type { FileSystemProvisionerStore } from "./extension-loader/file-system-provisioner-store/file-system-provisioner-store";
export interface LensExtensionDependencies { export interface LensExtensionDependencies {
fileSystemProvisionerStore: FileSystemProvisionerStore; readonly fileSystemProvisionerStore: FileSystemProvisionerStore;
}
export interface LensMainExtensionDependencies extends LensExtensionDependencies {
readonly entityRegistry: MainCatalogEntityRegistry;
}
export interface LensRendererExtensionDependencies extends LensExtensionDependencies {
navigateToRoute: NavigateToRoute;
getExtensionPageParameters: GetExtensionPageParameters;
readonly routes: IComputedValue<Route<unknown>[]>;
readonly entityRegistry: RendererCatalogEntityRegistry;
readonly categoryRegistry: CatalogCategoryRegistry;
} }

View File

@ -10,11 +10,7 @@ import type { ProtocolHandlerRegistration } from "./registries";
import type { PackageJson } from "type-fest"; import type { PackageJson } from "type-fest";
import type { Disposer } from "../common/utils"; import type { Disposer } from "../common/utils";
import { disposer } from "../common/utils"; import { disposer } from "../common/utils";
import type { import type { LensExtensionDependencies } from "./lens-extension-set-dependencies";
LensExtensionDependencies } from "./lens-extension-set-dependencies";
import {
setLensExtensionDependencies,
} from "./lens-extension-set-dependencies";
export type LensExtensionId = string; // path to manifest (package.json) export type LensExtensionId = string; // path to manifest (package.json)
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension; export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
@ -28,7 +24,7 @@ export interface LensExtensionManifest extends PackageJson {
export const Disposers = Symbol(); export const Disposers = Symbol();
export class LensExtension { export class LensExtension<Dependencies extends LensExtensionDependencies = LensExtensionDependencies> {
readonly id: LensExtensionId; readonly id: LensExtensionId;
readonly manifest: LensExtensionManifest; readonly manifest: LensExtensionManifest;
readonly manifestPath: string; readonly manifestPath: string;
@ -68,11 +64,7 @@ export class LensExtension {
return this.manifest.description; return this.manifest.description;
} }
private readonly dependencies!: LensExtensionDependencies; protected readonly dependencies!: Dependencies;
[setLensExtensionDependencies] = (dependencies: LensExtensionDependencies) => {
(this as unknown as { dependencies: LensExtensionDependencies }).dependencies = dependencies;
};
/** /**
* getExtensionFileFolder returns the path to an already created folder. This * getExtensionFileFolder returns the path to an already created folder. This

View File

@ -5,14 +5,14 @@
import { LensExtension } from "./lens-extension"; import { LensExtension } from "./lens-extension";
import { WindowManager } from "../main/window-manager"; import { WindowManager } from "../main/window-manager";
import { catalogEntityRegistry } from "../main/catalog";
import type { CatalogEntity } from "../common/catalog"; import type { CatalogEntity } from "../common/catalog";
import type { IObservableArray } from "mobx"; import type { IObservableArray } from "mobx";
import type { MenuRegistration } from "../main/menu/menu-registration"; import type { MenuRegistration } from "../main/menu/menu-registration";
import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration"; import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration";
import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration"; import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
import type { LensMainExtensionDependencies } from "./lens-extension-set-dependencies";
export class LensMainExtension extends LensExtension { export class LensMainExtension extends LensExtension<LensMainExtensionDependencies> {
appMenus: MenuRegistration[] = []; appMenus: MenuRegistration[] = [];
trayMenus: TrayMenuRegistration[] = []; trayMenus: TrayMenuRegistration[] = [];
@ -36,10 +36,10 @@ export class LensMainExtension extends LensExtension {
} }
addCatalogSource(id: string, source: IObservableArray<CatalogEntity>) { addCatalogSource(id: string, source: IObservableArray<CatalogEntity>) {
catalogEntityRegistry.addObservableSource(`${this.name}:${id}`, source); this.dependencies.entityRegistry.addObservableSource(`${this.name}:${id}`, source);
} }
removeCatalogSource(id: string) { removeCatalogSource(id: string) {
catalogEntityRegistry.removeSource(`${this.name}:${id}`); this.dependencies.entityRegistry.removeSource(`${this.name}:${id}`);
} }
} }

View File

@ -7,10 +7,8 @@ import type * as registries from "./registries";
import { Disposers, LensExtension } from "./lens-extension"; import { Disposers, LensExtension } from "./lens-extension";
import type { CatalogEntity } from "../common/catalog"; import type { CatalogEntity } from "../common/catalog";
import type { Disposer } from "../common/utils"; import type { Disposer } from "../common/utils";
import type { EntityFilter } from "../renderer/api/catalog-entity-registry"; import type { EntityFilter } from "../renderer/api/catalog/entity/registry";
import { catalogEntityRegistry } from "../renderer/api/catalog-entity-registry";
import type { CategoryFilter } from "../renderer/api/catalog-category-registry"; import type { CategoryFilter } from "../renderer/api/catalog-category-registry";
import { catalogCategoryRegistry } from "../renderer/api/catalog-category-registry";
import type { TopBarRegistration } from "../renderer/components/layout/top-bar/top-bar-registration"; import type { TopBarRegistration } from "../renderer/components/layout/top-bar/top-bar-registration";
import type { KubernetesCluster } from "../common/catalog-entities"; import type { KubernetesCluster } from "../common/catalog-entities";
import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration"; import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration";
@ -23,15 +21,12 @@ import type { StatusBarRegistration } from "../renderer/components/status-bar/st
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration"; import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
import type { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration"; import type { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration";
import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration"; import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration";
import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import routesInjectable from "../renderer/routes/routes.injectable";
import { fromPairs, map, matches, toPairs } from "lodash/fp"; import { fromPairs, map, matches, toPairs } from "lodash/fp";
import extensionPageParametersInjectable from "../renderer/routes/extension-page-parameters.injectable";
import { pipeline } from "@ogre-tools/fp"; import { pipeline } from "@ogre-tools/fp";
import { navigateToRouteInjectionToken } from "../common/front-end-routing/navigate-to-route-injection-token";
import { getExtensionRoutePath } from "../renderer/routes/for-extension"; import { getExtensionRoutePath } from "../renderer/routes/for-extension";
import type { LensRendererExtensionDependencies } from "./lens-extension-set-dependencies";
export class LensRendererExtension extends LensExtension { export class LensRendererExtension extends LensExtension<LensRendererExtensionDependencies> {
globalPages: registries.PageRegistration[] = []; globalPages: registries.PageRegistration[] = [];
clusterPages: registries.PageRegistration[] = []; clusterPages: registries.PageRegistration[] = [];
clusterPageMenus: registries.ClusterPageMenuRegistration[] = []; clusterPageMenus: registries.ClusterPageMenuRegistration[] = [];
@ -51,9 +46,7 @@ export class LensRendererExtension extends LensExtension {
customCategoryViews: CustomCategoryViewRegistration[] = []; customCategoryViews: CustomCategoryViewRegistration[] = [];
async navigate(pageId?: string, params: object = {}) { async navigate(pageId?: string, params: object = {}) {
const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.renderer); const routes = this.dependencies.routes.get();
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
const routes = di.inject(routesInjectable).get();
const targetRegistration = [...this.globalPages, ...this.clusterPages] const targetRegistration = [...this.globalPages, ...this.clusterPages]
.find(registration => registration.id === (pageId || undefined)); .find(registration => registration.id === (pageId || undefined));
@ -68,7 +61,7 @@ export class LensRendererExtension extends LensExtension {
return; return;
} }
const normalizedParams = di.inject(extensionPageParametersInjectable, { const normalizedParams = this.dependencies.getExtensionPageParameters({
extension: this, extension: this,
registration: targetRegistration, registration: targetRegistration,
}); });
@ -82,7 +75,7 @@ export class LensRendererExtension extends LensExtension {
fromPairs, fromPairs,
); );
navigateToRoute(targetRoute, { this.dependencies.navigateToRoute(targetRoute, {
query, query,
}); });
} }
@ -103,7 +96,7 @@ export class LensRendererExtension extends LensExtension {
* @returns A function to clean up the filter * @returns A function to clean up the filter
*/ */
addCatalogFilter(fn: EntityFilter): Disposer { addCatalogFilter(fn: EntityFilter): Disposer {
const dispose = catalogEntityRegistry.addCatalogFilter(fn); const dispose = this.dependencies.entityRegistry.addCatalogFilter(fn);
this[Disposers].push(dispose); this[Disposers].push(dispose);
@ -116,7 +109,7 @@ export class LensRendererExtension extends LensExtension {
* @returns A function to clean up the filter * @returns A function to clean up the filter
*/ */
addCatalogCategoryFilter(fn: CategoryFilter): Disposer { addCatalogCategoryFilter(fn: CategoryFilter): Disposer {
const dispose = catalogCategoryRegistry.addCatalogCategoryFilter(fn); const dispose = this.dependencies.categoryRegistry.addCatalogCategoryFilter(fn);
this[Disposers].push(dispose); this[Disposers].push(dispose);

View File

@ -5,24 +5,26 @@
import type { CatalogCategory, CatalogEntity } from "../../common/catalog"; import type { CatalogCategory, CatalogEntity } from "../../common/catalog";
import { catalogEntityRegistry as registry } from "../../renderer/api/catalog-entity-registry"; import type { CatalogEntityOnBeforeRun } from "../../renderer/api/catalog/entity/registry";
import type { CatalogEntityOnBeforeRun } from "../../renderer/api/catalog-entity-registry";
import type { Disposer } from "../../common/utils"; import type { Disposer } from "../../common/utils";
import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable"; import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable";
import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
export const catalogCategories = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable); export const catalogCategories = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable);
const internalEntityRegistry = asLegacyGlobalForExtensionApi(catalogEntityRegistryInjectable);
export class CatalogEntityRegistry { export class CatalogEntityRegistry {
/** /**
* Currently active/visible entity * Currently active/visible entity
*/ */
get activeEntity() { get activeEntity() {
return registry.activeEntity; return internalEntityRegistry.activeEntity;
} }
get entities(): Map<string, CatalogEntity> { get entities(): Map<string, CatalogEntity> {
return registry.entities; return internalEntityRegistry.entities;
} }
getById(id: string) { getById(id: string) {
@ -30,11 +32,11 @@ export class CatalogEntityRegistry {
} }
getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] { getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] {
return registry.getItemsForApiKind<T>(apiVersion, kind); return internalEntityRegistry.getItemsForApiKind<T>(apiVersion, kind);
} }
getItemsForCategory<T extends CatalogEntity>(category: CatalogCategory): T[] { getItemsForCategory<T extends CatalogEntity>(category: CatalogCategory): T[] {
return registry.getItemsForCategory(category); return internalEntityRegistry.getItemsForCategory(category);
} }
/** /**
@ -46,7 +48,7 @@ export class CatalogEntityRegistry {
* @returns A function to remove that hook * @returns A function to remove that hook
*/ */
addOnBeforeRun(onBeforeRun: CatalogEntityOnBeforeRun): Disposer { addOnBeforeRun(onBeforeRun: CatalogEntityOnBeforeRun): Disposer {
return registry.addOnBeforeRun(onBeforeRun); return internalEntityRegistry.addOnBeforeRun(onBeforeRun);
} }
} }

View File

@ -0,0 +1,30 @@
/**
* 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 { createExtensionInstanceInjectionToken } from "../../extensions/extension-loader/create-extension-instance.token";
import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { LensMainExtensionDependencies } from "../../extensions/lens-extension-set-dependencies";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
const createExtensionInstanceInjectable = getInjectable({
id: "create-extension-instance",
instantiate: (di) => {
const deps: LensMainExtensionDependencies = {
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
};
return (ExtensionClass, extension) => {
const instance = new ExtensionClass(extension);
(instance as unknown as { dependencies: LensMainExtensionDependencies }).dependencies = deps;
return instance;
};
},
injectionToken: createExtensionInstanceInjectionToken,
});
export default createExtensionInstanceInjectable;

View File

@ -20,8 +20,8 @@ import loggerInjectable from "../common/logger.injectable";
import spawnInjectable from "./child-process/spawn.injectable"; import spawnInjectable from "./child-process/spawn.injectable";
import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable"; import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable";
import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store"; import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store";
import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable"; import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { FileSystemProvisionerStore } from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store"; import type { FileSystemProvisionerStore } from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable"; import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
import type { ClusterStore } from "../common/cluster-store/cluster-store"; import type { ClusterStore } from "../common/cluster-store/cluster-store";
import type { Cluster } from "../common/cluster/cluster"; import type { Cluster } from "../common/cluster/cluster";

View File

@ -3,24 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { CatalogEntityRegistry } from "../catalog-entity-registry";
import type { CatalogEntityData, CatalogEntityKindData } from "../catalog-entity";
import { CatalogCategory } from "../catalog-entity"; import { CatalogCategory } from "../catalog-entity";
import { KubernetesCluster, WebLink } from "../../../common/catalog-entities"; import { KubernetesCluster, WebLink } from "../../../common/catalog-entities";
import { observable } from "mobx"; import { observable } from "mobx";
import type { CatalogCategoryRegistry } from "../../../common/catalog"; import type { CatalogCategoryRegistry } from "../../../common/catalog";
import { categoryVersion } from "../../../common/catalog"; import { categoryVersion } from "../../../common/catalog";
import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import type { CatalogEntityRegistry } from "../catalog/entity/registry";
import catalogEntityRegistryInjectable from "../catalog/entity/registry.injectable";
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable"; import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
class TestCatalogEntityRegistry extends CatalogEntityRegistry {
replaceItems(items: Array<CatalogEntityData & CatalogEntityKindData>) {
this.updateItems(items);
}
}
class FooBarCategory extends CatalogCategory { class FooBarCategory extends CatalogCategory {
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1"; public readonly apiVersion = "entityRegistry.k8slens.dev/v1alpha1";
public readonly kind = "CatalogCategory"; public readonly kind = "CatalogCategory";
public metadata = { public metadata = {
name: "FooBars", name: "FooBars",
@ -81,17 +76,18 @@ const entitykc = new KubernetesCluster({
}); });
describe("CatalogEntityRegistry", () => { describe("CatalogEntityRegistry", () => {
let entityRegistry: CatalogEntityRegistry;
let catalogCategoryRegistry: CatalogCategoryRegistry; let catalogCategoryRegistry: CatalogCategoryRegistry;
beforeEach(() => { beforeEach(() => {
const di = getDiForUnitTesting(); const di = getDiForUnitTesting();
entityRegistry = di.inject(catalogEntityRegistryInjectable);
catalogCategoryRegistry = di.inject(catalogCategoryRegistryInjectable); catalogCategoryRegistry = di.inject(catalogCategoryRegistryInjectable);
}); });
describe("updateItems", () => { describe("updateItems", () => {
it("adds new catalog item", () => { it("adds new catalog item", () => {
const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry);
const items = [{ const items = [{
apiVersion: "entity.k8slens.dev/v1alpha1", apiVersion: "entity.k8slens.dev/v1alpha1",
kind: "KubernetesCluster", kind: "KubernetesCluster",
@ -107,8 +103,8 @@ describe("CatalogEntityRegistry", () => {
spec: {}, spec: {},
}]; }];
catalog.replaceItems(items); entityRegistry.updateItems(items);
expect(catalog.items.length).toEqual(1); expect(entityRegistry.items.get().length).toEqual(1);
items.push({ items.push({
apiVersion: "entity.k8slens.dev/v1alpha1", apiVersion: "entity.k8slens.dev/v1alpha1",
@ -125,12 +121,11 @@ describe("CatalogEntityRegistry", () => {
spec: {}, spec: {},
}); });
catalog.replaceItems(items); entityRegistry.updateItems(items);
expect(catalog.items.length).toEqual(2); expect(entityRegistry.items.get().length).toEqual(2);
}); });
it("updates existing items", () => { it("updates existing items", () => {
const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry);
const items = [{ const items = [{
apiVersion: "entity.k8slens.dev/v1alpha1", apiVersion: "entity.k8slens.dev/v1alpha1",
kind: "KubernetesCluster", kind: "KubernetesCluster",
@ -146,19 +141,18 @@ describe("CatalogEntityRegistry", () => {
spec: {}, spec: {},
}]; }];
catalog.replaceItems(items); entityRegistry.updateItems(items);
expect(catalog.items.length).toEqual(1); expect(entityRegistry.items.get().length).toEqual(1);
expect(catalog.items[0].status.phase).toEqual("disconnected"); expect(entityRegistry.items.get()[0].status.phase).toEqual("disconnected");
items[0].status.phase = "connected"; items[0].status.phase = "connected";
catalog.replaceItems(items); entityRegistry.updateItems(items);
expect(catalog.items.length).toEqual(1); expect(entityRegistry.items.get().length).toEqual(1);
expect(catalog.items[0].status.phase).toEqual("connected"); expect(entityRegistry.items.get()[0].status.phase).toEqual("connected");
}); });
it("updates activeEntity", () => { it("updates activeEntity", () => {
const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry);
const items = [{ const items = [{
apiVersion: "entity.k8slens.dev/v1alpha1", apiVersion: "entity.k8slens.dev/v1alpha1",
kind: "KubernetesCluster", kind: "KubernetesCluster",
@ -174,17 +168,16 @@ describe("CatalogEntityRegistry", () => {
spec: {}, spec: {},
}]; }];
catalog.replaceItems(items); entityRegistry.updateItems(items);
catalog.activeEntity = catalog.items[0]; entityRegistry.activeEntity = entityRegistry.items.get()[0];
expect(catalog.activeEntity.status.phase).toEqual("disconnected"); expect(entityRegistry.activeEntity.status.phase).toEqual("disconnected");
items[0].status.phase = "connected"; items[0].status.phase = "connected";
catalog.replaceItems(items); entityRegistry.updateItems(items);
expect(catalog.activeEntity.status.phase).toEqual("connected"); expect(entityRegistry.activeEntity.status.phase).toEqual("connected");
}); });
it("removes deleted items", () => { it("removes deleted items", () => {
const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry);
const items = [ const items = [
{ {
apiVersion: "entity.k8slens.dev/v1alpha1", apiVersion: "entity.k8slens.dev/v1alpha1",
@ -216,17 +209,16 @@ describe("CatalogEntityRegistry", () => {
}, },
]; ];
catalog.replaceItems(items); entityRegistry.updateItems(items);
items.splice(0, 1); items.splice(0, 1);
catalog.replaceItems(items); entityRegistry.updateItems(items);
expect(catalog.items.length).toEqual(1); expect(entityRegistry.items.get().length).toEqual(1);
expect(catalog.items[0].metadata.uid).toEqual("456"); expect(entityRegistry.items.get()[0].metadata.uid).toEqual("456");
}); });
}); });
describe("items", () => { describe("items", () => {
it("does not return items without matching category", () => { it("does not return items without matching category", () => {
const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry);
const items = [ const items = [
{ {
apiVersion: "entity.k8slens.dev/v1alpha1", apiVersion: "entity.k8slens.dev/v1alpha1",
@ -258,13 +250,12 @@ describe("CatalogEntityRegistry", () => {
}, },
]; ];
catalog.replaceItems(items); entityRegistry.updateItems(items);
expect(catalog.items.length).toBe(1); expect(entityRegistry.items.get().length).toBe(1);
}); });
}); });
it("does return items after matching category is added", () => { it("does return items after matching category is added", () => {
const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry);
const items = [ const items = [
{ {
apiVersion: "entity.k8slens.dev/v1alpha1", apiVersion: "entity.k8slens.dev/v1alpha1",
@ -282,29 +273,28 @@ describe("CatalogEntityRegistry", () => {
}, },
]; ];
catalog.replaceItems(items); entityRegistry.updateItems(items);
catalogCategoryRegistry.add(new FooBarCategory()); catalogCategoryRegistry.add(new FooBarCategory());
expect(catalog.items.length).toBe(1); expect(entityRegistry.items.get().length).toBe(1);
}); });
it("does not return items that are filtered out", () => { it("does not return items that are filtered out", () => {
const source = observable.array([entity, entity2, entitykc]); const source = observable.array([entity, entity2, entitykc]);
const catalog = new TestCatalogEntityRegistry(catalogCategoryRegistry);
catalog.replaceItems(source); entityRegistry.updateItems(source);
expect(catalog.items.length).toBe(3); expect(entityRegistry.items.get().length).toBe(3);
expect(catalog.filteredItems.length).toBe(3); expect(entityRegistry.filteredItems.length).toBe(3);
const d = catalog.addCatalogFilter(entity => entity.kind === KubernetesCluster.kind); const d = entityRegistry.addCatalogFilter(entity => entity.kind === KubernetesCluster.kind);
expect(catalog.items.length).toBe(3); expect(entityRegistry.items.get().length).toBe(3);
expect(catalog.filteredItems.length).toBe(1); expect(entityRegistry.filteredItems.length).toBe(1);
// Remove filter // Remove filter
d(); d();
expect(catalog.items.length).toBe(3); expect(entityRegistry.items.get().length).toBe(3);
expect(catalog.filteredItems.length).toBe(3); expect(entityRegistry.filteredItems.length).toBe(3);
}); });
}); });

View File

@ -3,5 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
export { catalogCategoryRegistry } from "../../common/catalog"; import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable";
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
export type { CategoryFilter } from "../../common/catalog"; export type { CategoryFilter } from "../../common/catalog";
export const catalogCategoryRegistry = asLegacyGlobalForExtensionApi(catalogCategoryRegistryInjectable);

View File

@ -1,13 +0,0 @@
/**
* 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 { catalogEntityRegistry } from "../catalog-entity-registry";
const catalogEntityRegistryInjectable = getInjectable({
id: "catalog-entity-registry",
instantiate: () => catalogEntityRegistry,
});
export default catalogEntityRegistryInjectable;

View File

@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
export { catalogEntityRunContext } from "./catalog-entity-registry";
export { CatalogCategory, CatalogEntity } from "../../common/catalog"; export { CatalogCategory, CatalogEntity } from "../../common/catalog";
export type { export type {
CatalogEntityData, CatalogEntityData,

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import catalogEntityRegistryInjectable from "./registry.injectable";
const activeEntityInjectable = getInjectable({
id: "active-entity",
instantiate: (di) => {
const registry = di.inject(catalogEntityRegistryInjectable);
return computed(() => registry.activeEntity);
},
});
export default activeEntityInjectable;

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogEntityRegistryInjectable from "./registry.injectable";
const catalogEnitiesInjectable = getInjectable({
id: "catalog-enities",
instantiate: (di) => di.inject(catalogEntityRegistryInjectable).items,
});
export default catalogEnitiesInjectable;

View File

@ -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 clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import type { Cluster } from "../../../../common/cluster/cluster";
import catalogEntityRegistryInjectable from "./registry.injectable";
export type GetActiveClusterEntity = () => Cluster | undefined;
const getActiveClusterEntityInjectable = getInjectable({
id: "get-active-cluster-entity",
instantiate: (di): GetActiveClusterEntity => {
const store = di.inject(clusterStoreInjectable);
const entityRegistry = di.inject(catalogEntityRegistryInjectable);
return () => store.getById(entityRegistry.activeEntity?.getId());
},
});
export default getActiveClusterEntityInjectable;

View File

@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { asLegacyGlobalFunctionForExtensionApi } from "../../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
import getActiveClusterEntityInjectable from "./get-active-cluster-entity.injectable";
/**
* @deprecated use `di.inject(getActiveClusterEntityInjectable)` instead
*/
export const getActiveClusterEntity = asLegacyGlobalFunctionForExtensionApi(getActiveClusterEntityInjectable);

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
import navigateInjectable from "../../../navigation/navigate.injectable";
import { CatalogEntityRegistry } from "./registry";
const catalogEntityRegistryInjectable = getInjectable({
id: "catalog-entity-registry",
instantiate: (di) => new CatalogEntityRegistry({
categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
navigate: di.inject(navigateInjectable),
}),
});
export default catalogEntityRegistryInjectable;

View File

@ -4,57 +4,54 @@
*/ */
import { computed, observable, makeObservable, action } from "mobx"; import { computed, observable, makeObservable, action } from "mobx";
import { ipcRendererOn } from "../../common/ipc"; import { ipcRendererOn } from "../../../../common/ipc";
import type { CatalogCategory, CatalogEntity, CatalogEntityData, CatalogCategoryRegistry, CatalogEntityKindData } from "../../common/catalog"; import type { CatalogCategory, CatalogEntity, CatalogEntityData, CatalogCategoryRegistry, CatalogEntityKindData } from "../../../../common/catalog";
import { catalogCategoryRegistry } from "../../common/catalog"; import "../../../../common/catalog-entities";
import "../../common/catalog-entities"; import { iter } from "../../../utils";
import type { Cluster } from "../../common/cluster/cluster"; import type { Disposer } from "../../../utils";
import { ClusterStore } from "../../common/cluster-store/cluster-store";
import { iter } from "../utils";
import type { Disposer } from "../utils";
import { once } from "lodash"; import { once } from "lodash";
import logger from "../../common/logger"; import logger from "../../../../common/logger";
import { CatalogRunEvent } from "../../common/catalog/catalog-run-event"; import { CatalogRunEvent } from "../../../../common/catalog/catalog-run-event";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { catalogInitChannel, catalogItemsChannel, catalogEntityRunListener } from "../../common/ipc/catalog"; import { catalogInitChannel, catalogItemsChannel, catalogEntityRunListener } from "../../../../common/ipc/catalog";
import { navigate } from "../navigation";
import { isMainFrame } from "process"; import { isMainFrame } from "process";
import type { Navigate } from "../../../navigation/navigate.injectable";
export type EntityFilter = (entity: CatalogEntity) => any; export type EntityFilter = (entity: CatalogEntity) => any;
export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise<void>; export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise<void>;
export const catalogEntityRunContext = { interface Dependencies {
navigate: (url: string) => navigate(url), navigate: Navigate;
setCommandPaletteContext: (entity?: CatalogEntity) => { readonly categoryRegistry: CatalogCategoryRegistry;
catalogEntityRegistry.activeEntity = entity; }
},
};
export class CatalogEntityRegistry { export class CatalogEntityRegistry {
@observable protected activeEntityId: string | undefined = undefined; protected readonly activeEntityId = observable.box<string | undefined>(undefined);
protected _entities = observable.map<string, CatalogEntity>([], { deep: true }); protected readonly _entities = observable.map<string, CatalogEntity>([], { deep: true });
protected filters = observable.set<EntityFilter>([], { protected readonly filters = observable.set<EntityFilter>([], {
deep: false, deep: false,
}); });
protected onBeforeRunHooks = observable.set<CatalogEntityOnBeforeRun>([], { protected readonly onBeforeRunHooks = observable.set<CatalogEntityOnBeforeRun>([], {
deep: false, deep: false,
}); });
/** /**
* Buffer for keeping entities that don't yet have CatalogCategory synced * Buffer for keeping entities that don't yet have CatalogCategory synced
*/ */
protected rawEntities: (CatalogEntityData & CatalogEntityKindData)[] = []; protected readonly rawEntities: (CatalogEntityData & CatalogEntityKindData)[] = [];
constructor(private categoryRegistry: CatalogCategoryRegistry) { constructor(protected readonly dependencies: Dependencies) {
makeObservable(this); makeObservable(this);
} }
protected getActiveEntityById() { protected getActiveEntityById() {
if (!this.activeEntityId) { const activeEntityId = this.activeEntityId.get();
if (!activeEntityId) {
return undefined; return undefined;
} }
return this._entities.get(this.activeEntityId); return this._entities.get(activeEntityId);
} }
get activeEntity(): CatalogEntity | undefined { get activeEntity(): CatalogEntity | undefined {
@ -78,9 +75,9 @@ export class CatalogEntityRegistry {
? raw ? raw
: raw.getId(); : raw.getId();
this.activeEntityId = id; this.activeEntityId.set(id);
} else { } else {
this.activeEntityId = undefined; this.activeEntityId.set(undefined);
} }
} }
@ -123,7 +120,7 @@ export class CatalogEntityRegistry {
const existing = this._entities.get(item.metadata.uid); const existing = this._entities.get(item.metadata.uid);
if (!existing) { if (!existing) {
const entity = this.categoryRegistry.getEntityForData(item); const entity = this.dependencies.categoryRegistry.getEntityForData(item);
if (entity) { if (entity) {
this._entities.set(entity.getId(), entity); this._entities.set(entity.getId(), entity);
@ -147,25 +144,25 @@ export class CatalogEntityRegistry {
} }
} }
@computed get items() { readonly items = computed(() => {
this.processRawEntities(); this.processRawEntities();
return Array.from(this._entities.values()); return Array.from(this._entities.values());
} });
@computed get filteredItems() { @computed get filteredItems() {
return Array.from( return Array.from(
iter.reduce( iter.reduce(
this.filters, this.filters,
iter.filter, iter.filter,
this.items.values(), this.items.get().values(),
), ),
); );
} }
@computed get entities(): Map<string, CatalogEntity> { @computed get entities(): Map<string, CatalogEntity> {
return new Map( return new Map(
this.items.map(entity => [entity.getId(), entity]), this.items.get().map(entity => [entity.getId(), entity]),
); );
} }
@ -175,13 +172,13 @@ export class CatalogEntityRegistry {
); );
} }
getById<T extends CatalogEntity>(id: string) { getById(id: string) {
return this.entities.get(id) as T; return this.entities.get(id);
} }
getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string, { filtered = false } = {}): T[] { getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string, { filtered = false } = {}): T[] {
const byApiKind = (item: CatalogEntity) => item.apiVersion === apiVersion && item.kind === kind; const byApiKind = (item: CatalogEntity) => item.apiVersion === apiVersion && item.kind === kind;
const entities = filtered ? this.filteredItems : this.items; const entities = filtered ? this.filteredItems : this.items.get();
return entities.filter(byApiKind) as T[]; return entities.filter(byApiKind) as T[];
} }
@ -189,7 +186,7 @@ export class CatalogEntityRegistry {
getItemsForCategory<T extends CatalogEntity>(category: CatalogCategory, { filtered = false } = {}): T[] { getItemsForCategory<T extends CatalogEntity>(category: CatalogCategory, { filtered = false } = {}): T[] {
const supportedVersions = new Set(category.spec.versions.map((v) => `${category.spec.group}/${v.name}`)); const supportedVersions = new Set(category.spec.versions.map((v) => `${category.spec.group}/${v.name}`));
const byApiVersionKind = (item: CatalogEntity) => supportedVersions.has(item.apiVersion) && item.kind === category.spec.names.kind; const byApiVersionKind = (item: CatalogEntity) => supportedVersions.has(item.apiVersion) && item.kind === category.spec.names.kind;
const entities = filtered ? this.filteredItems : this.items; const entities = filtered ? this.filteredItems : this.items.get();
return entities.filter(byApiVersionKind) as T[]; return entities.filter(byApiVersionKind) as T[];
} }
@ -249,7 +246,12 @@ export class CatalogEntityRegistry {
this.onBeforeRun(entity) this.onBeforeRun(entity)
.then(doOnRun => { .then(doOnRun => {
if (doOnRun) { if (doOnRun) {
return entity.onRun?.(catalogEntityRunContext); return entity.onRun?.({
navigate: this.dependencies.navigate,
setCommandPaletteContext: (entity) => {
this.activeEntity = entity;
},
});
} else { } else {
logger.debug(`onBeforeRun for ${entity.getId()} returned false`); logger.debug(`onBeforeRun for ${entity.getId()} returned false`);
} }
@ -257,9 +259,3 @@ export class CatalogEntityRegistry {
.catch(error => logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error)); .catch(error => logger.error(`[CATALOG-ENTITY-REGISTRY]: entity ${entity.getId()} onRun threw an error`, error));
} }
} }
export const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
export function getActiveClusterEntity(): Cluster | undefined {
return ClusterStore.getInstance().getById(catalogEntityRegistry.activeEntity?.getId());
}

View File

@ -9,7 +9,7 @@ import isActiveRouteInjectable from "../../navigation/is-route-active.injectable
import observableHistoryInjectable from "../../navigation/observable-history.injectable"; import observableHistoryInjectable from "../../navigation/observable-history.injectable";
import type { Disposer } from "../../utils"; import type { Disposer } from "../../utils";
import { disposer } from "../../utils"; import { disposer } from "../../utils";
import catalogEntityRegistryInjectable from "../catalog-entity-registry/catalog-entity-registry.injectable"; import catalogEntityRegistryInjectable from "../catalog/entity/registry.injectable";
export type WatchForGeneralEntityNavigation = () => Disposer; export type WatchForGeneralEntityNavigation = () => Disposer;

View File

@ -11,8 +11,8 @@ import rendererExtensionsInjectable from "../../../../extensions/renderer-extens
import { CatalogCategory } from "../../../api/catalog-entity"; import { CatalogCategory } from "../../../api/catalog-entity";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns"; import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
import type { CategoryColumns, GetCategoryColumnsParams } from "../get-category-columns.injectable"; import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable";
import getCategoryColumnsInjectable from "../get-category-columns.injectable"; import getCategoryColumnsInjectable from "../columns/get.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
class TestCategory extends CatalogCategory { class TestCategory extends CatalogCategory {

View File

@ -14,16 +14,22 @@ import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
import { isDevelopment } from "../../../common/vars"; import { isDevelopment } from "../../../common/vars";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { Avatar } from "../avatar"; import { Avatar } from "../avatar";
import { getLabelBadges } from "./helpers"; import type { GetLabelBadges } from "./get-label-badges.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import getLabelBadgesInjectable from "./get-label-badges.injectable";
export interface CatalogEntityDetailsProps<T extends CatalogEntity> { export interface CatalogEntityDetailsProps<Entity extends CatalogEntity> {
entity: T; entity: Entity;
hideDetails(): void; hideDetails(): void;
onRun: () => void; onRun: () => void;
} }
interface Dependencies {
getLabelBadges: GetLabelBadges;
}
@observer @observer
export class CatalogEntityDetails<T extends CatalogEntity> extends Component<CatalogEntityDetailsProps<T>> { class NonInjectedCatalogEntityDetails<Entity extends CatalogEntity> extends Component<CatalogEntityDetailsProps<Entity> & Dependencies> {
categoryIcon(category: CatalogCategory) { categoryIcon(category: CatalogCategory) {
if (Icon.isSvg(category.metadata.icon)) { if (Icon.isSvg(category.metadata.icon)) {
return <Icon svg={category.metadata.icon} smallest />; return <Icon svg={category.metadata.icon} smallest />;
@ -32,8 +38,8 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Cat
} }
} }
renderContent(entity: T) { renderContent(entity: Entity) {
const { onRun, hideDetails } = this.props; const { onRun, hideDetails, getLabelBadges } = this.props;
const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(entity.kind, entity.apiVersion); const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(entity.kind, entity.apiVersion);
const details = detailItems.map(({ components }, index) => <components.Details entity={entity} key={index} />); const details = detailItems.map(({ components }, index) => <components.Details entity={entity} key={index} />);
const showDefaultDetails = detailItems.find((item) => item.priority ?? 50 > 999) === undefined; const showDefaultDetails = detailItems.find((item) => item.priority ?? 50 > 999) === undefined;
@ -109,3 +115,10 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Cat
); );
} }
} }
export const CatalogEntityDetails = withInjectables<Dependencies, CatalogEntityDetailsProps<CatalogEntity>>(NonInjectedCatalogEntityDetails, {
getProps: (di, props) => ({
...props,
getLabelBadges: di.inject(getLabelBadgesInjectable),
}),
}) as <Entity extends CatalogEntity>(props: CatalogEntityDetailsProps<Entity>) => React.ReactElement;

View File

@ -4,7 +4,7 @@
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { catalogEntityStore } from "./catalog-entity.store"; import { catalogEntityStore } from "./catalog-entity.store";
import catalogEntityRegistryInjectable from "../../../api/catalog-entity-registry/catalog-entity-registry.injectable"; import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable"; import catalogCategoryRegistryInjectable from "../../../../common/catalog/category-registry.injectable";
const catalogEntityStoreInjectable = getInjectable({ const catalogEntityStoreInjectable = getInjectable({

View File

@ -5,7 +5,7 @@
import type { IComputedValue, IObservableValue } from "mobx"; import type { IComputedValue, IObservableValue } from "mobx";
import { computed, observable, reaction } from "mobx"; import { computed, observable, reaction } from "mobx";
import type { CatalogEntityRegistry } from "../../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
import type { CatalogEntity } from "../../../api/catalog-entity"; import type { CatalogEntity } from "../../../api/catalog-entity";
import type { CatalogCategory, CatalogCategoryRegistry } from "../../../../common/catalog"; import type { CatalogCategory, CatalogCategoryRegistry } from "../../../../common/catalog";
import type { Disposer } from "../../../../common/utils"; import type { Disposer } from "../../../../common/utils";

View File

@ -9,14 +9,14 @@ import userEvent from "@testing-library/user-event";
import { Catalog } from "./catalog"; import { Catalog } from "./catalog";
import { mockWindow } from "../../../../__mocks__/windowMock"; import { mockWindow } from "../../../../__mocks__/windowMock";
import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; import type { CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
import { CatalogCategoryRegistry, CatalogEntity } from "../../../common/catalog"; import { CatalogEntity } from "../../../common/catalog";
import { CatalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store"; import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import type { DiContainer } from "@ogre-tools/injectable"; import type { DiContainer } from "@ogre-tools/injectable";
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable"; import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
import catalogEntityRegistryInjectable from "../../api/catalog-entity-registry/catalog-entity-registry.injectable"; import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import type { DiRender } from "../test-utils/renderFor"; import type { DiRender } from "../test-utils/renderFor";
import { renderFor } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor";
import mockFs from "mock-fs"; import mockFs from "mock-fs";
@ -101,10 +101,7 @@ describe("<Catalog />", () => {
render = renderFor(di); render = renderFor(di);
onRun = jest.fn(); onRun = jest.fn();
catalogEntityItem = createMockCatalogEntity(onRun); catalogEntityItem = createMockCatalogEntity(onRun);
catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const catalogCategoryRegistry = new CatalogCategoryRegistry();
catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry); di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry);

View File

@ -30,8 +30,8 @@ import { Avatar } from "../avatar";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import catalogPreviousActiveTabStorageInjectable from "./catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable"; import catalogPreviousActiveTabStorageInjectable from "./catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable";
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable"; import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
import type { GetCategoryColumnsParams, CategoryColumns } from "./get-category-columns.injectable"; import type { GetCategoryColumnsParams, CategoryColumns } from "./columns/get.injectable";
import getCategoryColumnsInjectable from "./get-category-columns.injectable"; import getCategoryColumnsInjectable from "./columns/get.injectable";
import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable"; import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
import customCategoryViewsInjectable from "./custom-views.injectable"; import customCategoryViewsInjectable from "./custom-views.injectable";
import type { CustomCategoryViewComponents } from "./custom-views"; import type { CustomCategoryViewComponents } from "./custom-views";

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
import { getInjectable } from "@ogre-tools/injectable";
import namedCategoryColumnInjectable from "./named-category.injectable";
import defaultCategoryColumnsInjectable from "./default-category.injectable";
const defaultBrowseAllColumns: RegisteredAdditionalCategoryColumn[] = [
{
id: "kind",
priority: 5,
renderCell: entity => entity.kind,
titleProps: {
id: "kind",
sortBy: "kind",
title: "Kind",
},
sortCallback: entity => entity.kind,
},
];
const browseAllColumnsInjectable = getInjectable({
id: "browse-all-columns",
instantiate: (di) => [
...defaultBrowseAllColumns,
di.inject(namedCategoryColumnInjectable),
...di.inject(defaultCategoryColumnsInjectable),
],
});
export default browseAllColumnsInjectable;

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import styles from "../catalog.module.scss";
import React from "react";
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
import { KubeObject } from "../../../../common/k8s-api/kube-object";
import { getInjectable } from "@ogre-tools/injectable";
import getLabelBadgesInjectable from "../get-label-badges.injectable";
const defaultCategoryColumnsInjectable = getInjectable({
id: "default-category-columns",
instantiate: (di): RegisteredAdditionalCategoryColumn[] => {
const getLabelBadges = di.inject(getLabelBadgesInjectable);
return [
{
id: "source",
priority: 10,
renderCell: entity => entity.getSource(),
titleProps: {
title: "Source",
className: styles.sourceCell,
id: "source",
sortBy: "source",
},
sortCallback: entity => entity.getSource(),
searchFilter: entity => `source=${entity.getSource()}`,
},
{
id: "labels",
priority: 20,
renderCell: getLabelBadges,
titleProps: {
id: "labels",
title: "Labels",
className: `${styles.labelsCell} scrollable`,
},
searchFilter: entity => KubeObject.stringifyLabels(entity.metadata.labels),
},
{
id: "status",
priority: 30,
renderCell: entity => (
<span key="phase" className={entity.status.phase}>
{entity.status.phase}
</span>
),
titleProps: {
title: "Status",
className: styles.statusCell,
id: "status",
sortBy: "status",
},
searchFilter: entity => entity.status.phase,
sortCallback: entity => entity.status.phase,
},
];
},
});
export default defaultCategoryColumnsInjectable;

View File

@ -0,0 +1,63 @@
/**
* 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 { orderBy } from "lodash";
import type { CatalogCategory, CatalogEntity } from "../../../../common/catalog";
import type { ItemListLayoutProps } from "../../item-object-list";
import browseAllColumnsInjectable from "./browse-all.injectable";
import getColumnsForCategoryInjectable from "./specific-category.injectable";
export interface GetCategoryColumnsParams {
activeCategory: CatalogCategory | null | undefined;
}
export type CategoryColumns = Required<Pick<ItemListLayoutProps<CatalogEntity>, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>;
export type GetCategoryColumns = (params: GetCategoryColumnsParams) => CategoryColumns;
const getCategoryColumnsInjectable = getInjectable({
id: "get-category-columns",
instantiate: (di): GetCategoryColumns => {
const getColumnsForCategory = di.inject(getColumnsForCategoryInjectable);
const browseAllColumns = di.inject(browseAllColumnsInjectable);
return ({ activeCategory }) => {
const allRegistrations = orderBy(
activeCategory
? getColumnsForCategory(activeCategory)
: browseAllColumns,
"priority",
"asc",
);
const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {};
const searchFilters: CategoryColumns["searchFilters"] = [];
const renderTableHeader: CategoryColumns["renderTableHeader"] = [];
const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = [];
for (const registration of allRegistrations) {
if (registration.sortCallback) {
sortingCallbacks[registration.id] = registration.sortCallback;
}
if (registration.searchFilter) {
searchFilters.push(registration.searchFilter);
}
tableRowRenderers.push(registration.renderCell);
renderTableHeader.push(registration.titleProps);
}
return {
sortingCallbacks,
renderTableHeader,
renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)),
searchFilters,
};
};
},
});
export default getCategoryColumnsInjectable;

View File

@ -3,15 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import styles from "./catalog.module.scss"; import styles from "../catalog.module.scss";
import type { CatalogEntity } from "../../../common/catalog"; import type { CatalogEntity } from "../../../../common/catalog";
import { prevDefault } from "../../utils"; import { prevDefault } from "../../../utils";
import { Avatar } from "../avatar"; import { Avatar } from "../../avatar";
import { Icon } from "../icon"; import { Icon } from "../../icon";
import React from "react"; import React from "react";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns"; import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../common/hotbars/store"; import type { HotbarStore } from "../../../../common/hotbars/store";
const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => { const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => {
const isItemInHotbar = hotbarStore.isAddedToActive(entity); const isItemInHotbar = hotbarStore.isAddedToActive(entity);
@ -45,8 +45,7 @@ const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) =
); );
}; };
const namedCategoryColumnInjectable = getInjectable({
const nameCategoryColumnInjectable = getInjectable({
id: "name-category-column", id: "name-category-column",
instantiate: (di): RegisteredAdditionalCategoryColumn => ({ instantiate: (di): RegisteredAdditionalCategoryColumn => ({
id: "name", id: "name",
@ -63,4 +62,4 @@ const nameCategoryColumnInjectable = getInjectable({
}), }),
}); });
export default nameCategoryColumnInjectable; export default namedCategoryColumnInjectable;

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { CatalogCategory } from "../../../api/catalog-entity";
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
import categoryColumnsInjectable from "../custom-category-columns.injectable";
import defaultCategoryColumnsInjectable from "./default-category.injectable";
import namedCategoryColumnInjectable from "./named-category.injectable";
export type GetColumnsForCategory = (activeCategory: CatalogCategory) => RegisteredAdditionalCategoryColumn[];
const getColumnsForCategoryInjectable = getInjectable({
id: "get-columns-for-category",
instantiate: (di): GetColumnsForCategory => {
const extensionColumns = di.inject(categoryColumnsInjectable);
const defaultCategoryColumns = di.inject(defaultCategoryColumnsInjectable);
const nameCategoryColumn = di.inject(namedCategoryColumnInjectable);
return (activeCategory) => {
const fromExtensions = (
extensionColumns
.get()
.get(activeCategory.spec.group)
?.get(activeCategory.spec.names.kind)
?? []
);
const fromCategory = activeCategory.spec.displayColumns?.map(({ priority = 50, ...column }) => ({
priority,
...column,
})) ?? defaultCategoryColumns;
return [
nameCategoryColumn,
...fromExtensions,
...fromCategory,
];
};
},
});
export default getColumnsForCategoryInjectable;

View File

@ -1,98 +0,0 @@
/**
* 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 { orderBy } from "lodash";
import type { IComputedValue } from "mobx";
import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
import type { ItemListLayoutProps } from "../item-object-list";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
import categoryColumnsInjectable from "./custom-category-columns.injectable";
import { defaultCategoryColumns, browseAllColumns } from "./internal-category-columns";
import nameCategoryColumnInjectable from "./name-category-column.injectable";
interface Dependencies {
extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>;
nameCategoryColumn: RegisteredAdditionalCategoryColumn;
}
export interface GetCategoryColumnsParams {
activeCategory: CatalogCategory | null | undefined;
}
export type CategoryColumns = Required<Pick<ItemListLayoutProps<CatalogEntity>, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>;
function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>, nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] {
const fromExtensions = (
extensionColumns
.get()
.get(activeCategory.spec.group)
?.get(activeCategory.spec.names.kind)
?? []
);
const fromCategory = activeCategory.spec.displayColumns?.map(({ priority = 50, ...column }) => ({
priority,
...column,
})) ?? defaultCategoryColumns;
return [
nameCategoryColumn,
...fromExtensions,
...fromCategory,
];
}
function getBrowseAllColumns(nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] {
return [
...browseAllColumns,
nameCategoryColumn,
...defaultCategoryColumns,
];
}
const getCategoryColumns = ({ extensionColumns, nameCategoryColumn }: Dependencies) => ({ activeCategory }: GetCategoryColumnsParams): CategoryColumns => {
const allRegistrations = orderBy(
activeCategory
? getSpecificCategoryColumns(activeCategory, extensionColumns, nameCategoryColumn)
: getBrowseAllColumns(nameCategoryColumn),
"priority",
"asc",
);
const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {};
const searchFilters: CategoryColumns["searchFilters"] = [];
const renderTableHeader: CategoryColumns["renderTableHeader"] = [];
const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = [];
for (const registration of allRegistrations) {
if (registration.sortCallback) {
sortingCallbacks[registration.id] = registration.sortCallback;
}
if (registration.searchFilter) {
searchFilters.push(registration.searchFilter);
}
tableRowRenderers.push(registration.renderCell);
renderTableHeader.push(registration.titleProps);
}
return {
sortingCallbacks,
renderTableHeader,
renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)),
searchFilters,
};
};
const getCategoryColumnsInjectable = getInjectable({
id: "get-category-columns",
instantiate: (di) => getCategoryColumns({
extensionColumns: di.inject(categoryColumnsInjectable),
nameCategoryColumn: di.inject(nameCategoryColumnInjectable),
}),
});
export default getCategoryColumnsInjectable;

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import styles from "./catalog.module.scss";
import React from "react";
import { getInjectable } from "@ogre-tools/injectable";
import type { CatalogEntity } from "../../api/catalog-entity";
import searchUrlPageParamInjectable from "../input/search-url-page-param.injectable";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import { Badge } from "../badge";
export type GetLabelBadges = (entity: CatalogEntity, onClick?: ((evt: React.MouseEvent<any, MouseEvent>) => void) | undefined) => JSX.Element[];
const getLabelBadgesInjectable = getInjectable({
id: "get-label-badges",
instantiate: (di): GetLabelBadges => {
const searchUrlParam = di.inject(searchUrlPageParamInjectable);
return (entity, onClick) => (
KubeObject.stringifyLabels(entity.metadata.labels)
.map(label => (
<Badge
scrollable
className={styles.badge}
key={label}
label={label}
title={label}
onClick={(event) => {
searchUrlParam.set(label);
onClick?.(event);
event.stopPropagation();
}}
expandable={false}
/>
))
);
},
});
export default getLabelBadgesInjectable;

View File

@ -1,33 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import styles from "./catalog.module.scss";
import React from "react";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import type { CatalogEntity } from "../../api/catalog-entity";
import { Badge } from "../badge";
import { searchUrlParam } from "../input";
/**
* @param entity The entity to render badge labels for
*/
export function getLabelBadges(entity: CatalogEntity, onClick?: (evt: React.MouseEvent<any, MouseEvent>) => void) {
return KubeObject.stringifyLabels(entity.metadata.labels)
.map(label => (
<Badge
scrollable
className={styles.badge}
key={label}
label={label}
title={label}
onClick={(event) => {
searchUrlParam.set(label);
onClick?.(event);
event.stopPropagation();
}}
expandable={false}
/>
));
}

View File

@ -1,69 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import styles from "./catalog.module.scss";
import React from "react";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
import { getLabelBadges } from "./helpers";
import { KubeObject } from "../../../common/k8s-api/kube-object";
export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [
{
id: "kind",
priority: 5,
renderCell: entity => entity.kind,
titleProps: {
id: "kind",
sortBy: "kind",
title: "Kind",
},
sortCallback: entity => entity.kind,
},
];
export const defaultCategoryColumns: RegisteredAdditionalCategoryColumn[] = [
{
id: "source",
priority: 10,
renderCell: entity => entity.getSource(),
titleProps: {
title: "Source",
className: styles.sourceCell,
id: "source",
sortBy: "source",
},
sortCallback: entity => entity.getSource(),
searchFilter: entity => `source=${entity.getSource()}`,
},
{
id: "labels",
priority: 20,
renderCell: getLabelBadges,
titleProps: {
id: "labels",
title: "Labels",
className: `${styles.labelsCell} scrollable`,
},
searchFilter: entity => KubeObject.stringifyLabels(entity.metadata.labels),
},
{
id: "status",
priority: 30,
renderCell: entity => (
<span key="phase" className={entity.status.phase}>
{entity.status.phase}
</span>
),
titleProps: {
title: "Status",
className: styles.statusCell,
id: "status",
sortBy: "status",
},
searchFilter: entity => entity.status.phase,
sortCallback: entity => entity.status.phase,
},
];

View File

@ -17,7 +17,7 @@ import { ClusterIssues } from "./cluster-issues";
import { ClusterMetrics } from "./cluster-metrics"; import { ClusterMetrics } from "./cluster-metrics";
import type { ClusterOverviewStore } from "./cluster-overview-store/cluster-overview-store"; import type { ClusterOverviewStore } from "./cluster-overview-store/cluster-overview-store";
import { ClusterPieCharts } from "./cluster-pie-charts"; import { ClusterPieCharts } from "./cluster-pie-charts";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import type { EventStore } from "../+events/store"; import type { EventStore } from "../+events/store";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";

View File

@ -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 createPageParamInjectable from "../../navigation/create-page-param.injectable";
const crdGroupsUrlParamInjectable = getInjectable({
id: "crd-groups-url-param",
instantiate: (di) => {
const createPageParam = di.inject(createPageParamInjectable);
return createPageParam<string[]>({
name: "groups",
defaultValue: [],
});
},
});
export default crdGroupsUrlParamInjectable;

View File

@ -13,15 +13,14 @@ import { iter, stopPropagation } from "../../utils";
import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectListLayout } from "../kube-object-list-layout";
import { customResourceDefinitionStore } from "./legacy-store"; import { customResourceDefinitionStore } from "./legacy-store";
import { Select } from "../select"; import { Select } from "../select";
import { createPageParam } from "../../navigation";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { KubeObjectAge } from "../kube-object/age"; import { KubeObjectAge } from "../kube-object/age";
import { TabLayout } from "../layout/tab-layout-2"; import { TabLayout } from "../layout/tab-layout-2";
import type { PageParam } from "../../navigation";
export const crdGroupsUrlParam = createPageParam<string[]>({ import type { CustomResourceDefinitionStore } from "./definition.store";
name: "groups", import { withInjectables } from "@ogre-tools/injectable-react";
defaultValue: [], import crdGroupsUrlParamInjectable from "./crd-groups-url-param.injectable";
}); import customResourceDefinitionStoreInjectable from "./definition.store.injectable";
enum columnId { enum columnId {
kind = "kind", kind = "kind",
@ -31,25 +30,30 @@ enum columnId {
age = "age", age = "age",
} }
@observer interface Dependencies {
export class CustomResourceDefinitions extends React.Component { crdGroupsUrlParam: PageParam<string[]>;
private readonly selectedGroups = observable.set(crdGroupsUrlParam.get()); customResourceDefinitionStore: CustomResourceDefinitionStore;
}
constructor(props: {}) { @observer
class NonInjectedCustomResourceDefinitions extends React.Component<Dependencies> {
private readonly selectedGroups = observable.set(this.props.crdGroupsUrlParam.get());
constructor(props: Dependencies) {
super(props); super(props);
makeObservable(this); makeObservable(this);
} }
@computed get items() { @computed get items() {
if (this.selectedGroups.size) { if (this.selectedGroups.size) {
return customResourceDefinitionStore.items.filter(item => this.selectedGroups.has(item.getGroup())); return this.props.customResourceDefinitionStore.items.filter(item => this.selectedGroups.has(item.getGroup()));
} }
return customResourceDefinitionStore.items; // show all by default return this.props.customResourceDefinitionStore.items; // show all by default
} }
@computed get groupSelectOptions() { @computed get groupSelectOptions() {
return Object.keys(customResourceDefinitionStore.groups) return Object.keys(this.props.customResourceDefinitionStore.groups)
.map(group => ({ .map(group => ({
value: group, value: group,
label: group, label: group,
@ -61,7 +65,7 @@ export class CustomResourceDefinitions extends React.Component {
const groups = options.map(({ value }) => value); const groups = options.map(({ value }) => value);
this.selectedGroups.replace(groups); this.selectedGroups.replace(groups);
crdGroupsUrlParam.set(groups); this.props.crdGroupsUrlParam.set(groups);
}; };
private getPlaceholder() { private getPlaceholder() {
@ -159,3 +163,11 @@ export class CustomResourceDefinitions extends React.Component {
); );
} }
} }
export const CustomResourceDefinitions = withInjectables<Dependencies>(NonInjectedCustomResourceDefinitions, {
getProps: (di, props) => ({
...props,
crdGroupsUrlParam: di.inject(crdGroupsUrlParamInjectable),
customResourceDefinitionStore: di.inject(customResourceDefinitionStoreInjectable),
}),
});

View File

@ -9,10 +9,9 @@ import React from "react";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { observable, makeObservable, computed } from "mobx"; import { observable, makeObservable, computed } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { navigation } from "../../navigation";
import { Tabs, Tab } from "../tabs"; import { Tabs, Tab } from "../tabs";
import type { CatalogEntity } from "../../api/catalog-entity"; import type { CatalogEntity } from "../../api/catalog-entity";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import { EntitySettingRegistry } from "../../../extensions/registries"; import { EntitySettingRegistry } from "../../../extensions/registries";
import { groupBy } from "lodash"; import { groupBy } from "lodash";
import { SettingLayout } from "../layout/setting-layout"; import { SettingLayout } from "../layout/setting-layout";
@ -20,9 +19,14 @@ import logger from "../../../common/logger";
import { Avatar } from "../avatar"; import { Avatar } from "../avatar";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import entitySettingsRouteParametersInjectable from "./entity-settings-route-parameters.injectable"; import entitySettingsRouteParametersInjectable from "./entity-settings-route-parameters.injectable";
import type { ObservableHistory } from "mobx-observable-history";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import observableHistoryInjectable from "../../navigation/observable-history.injectable";
interface Dependencies { interface Dependencies {
entityId: IComputedValue<string>; entityId: IComputedValue<string>;
entityRegistry: CatalogEntityRegistry;
observableHistory: ObservableHistory<unknown>;
} }
@observer @observer
@ -33,7 +37,7 @@ class NonInjectedEntitySettings extends React.Component<Dependencies> {
super(props); super(props);
makeObservable(this); makeObservable(this);
const { hash } = navigation.location; const { hash } = this.props.observableHistory.location;
if (hash) { if (hash) {
const menuId = hash.slice(1); const menuId = hash.slice(1);
@ -50,8 +54,8 @@ class NonInjectedEntitySettings extends React.Component<Dependencies> {
return this.props.entityId.get(); return this.props.entityId.get();
} }
get entity(): CatalogEntity { get entity() {
return catalogEntityRegistry.getById(this.entityId); return this.props.entityRegistry.getById(this.entityId);
} }
get menuItems() { get menuItems() {
@ -70,7 +74,7 @@ class NonInjectedEntitySettings extends React.Component<Dependencies> {
this.activeTab = tabId; this.activeTab = tabId;
}; };
renderNavigation() { renderNavigation(entity: CatalogEntity) {
const groups = Object.entries(groupBy(this.menuItems, (item) => item.group || "Extensions")); const groups = Object.entries(groupBy(this.menuItems, (item) => item.group || "Extensions"));
groups.sort((a, b) => { groups.sort((a, b) => {
@ -84,14 +88,14 @@ class NonInjectedEntitySettings extends React.Component<Dependencies> {
<> <>
<div className="flex items-center pb-8"> <div className="flex items-center pb-8">
<Avatar <Avatar
title={this.entity.getName()} title={entity.getName()}
colorHash={`${this.entity.getName()}-${this.entity.metadata.source}`} colorHash={`${entity.getName()}-${entity.metadata.source}`}
src={this.entity.spec.icon?.src} src={entity.spec.icon?.src}
className={styles.settingsAvatar} className={styles.settingsAvatar}
size={40} size={40}
/> />
<div className={styles.entityName}> <div className={styles.entityName}>
{this.entity.getName()} {entity.getName()}
</div> </div>
</div> </div>
<Tabs <Tabs
@ -120,17 +124,17 @@ class NonInjectedEntitySettings extends React.Component<Dependencies> {
} }
render() { render() {
if (!this.entity) { const { activeSetting, entity } = this;
if (!entity) {
logger.error("[ENTITY-SETTINGS]: entity not found", this.entityId); logger.error("[ENTITY-SETTINGS]: entity not found", this.entityId);
return null; return null;
} }
const { activeSetting } = this;
return ( return (
<SettingLayout <SettingLayout
navigation={this.renderNavigation()} navigation={this.renderNavigation(entity)}
contentGaps={false} contentGaps={false}
> >
{ {
@ -138,7 +142,7 @@ class NonInjectedEntitySettings extends React.Component<Dependencies> {
<section> <section>
<h2 data-testid={`${activeSetting.id}-header`}>{activeSetting.title}</h2> <h2 data-testid={`${activeSetting.id}-header`}>{activeSetting.title}</h2>
<section> <section>
<activeSetting.components.View entity={this.entity} key={activeSetting.title} /> <activeSetting.components.View entity={entity} key={activeSetting.title} />
</section> </section>
</section> </section>
) )
@ -148,16 +152,10 @@ class NonInjectedEntitySettings extends React.Component<Dependencies> {
} }
} }
export const EntitySettings = withInjectables<Dependencies>( export const EntitySettings = withInjectables<Dependencies>(NonInjectedEntitySettings, {
NonInjectedEntitySettings, getProps: (di) => ({
...di.inject(entitySettingsRouteParametersInjectable),
{ entityRegistry: di.inject(catalogEntityRegistryInjectable),
getProps: (di) => { observableHistory: di.inject(observableHistoryInjectable),
const routeParameters = di.inject(entitySettingsRouteParametersInjectable); }),
});
return {
entityId: routeParameters.entityId,
};
},
},
);

View File

@ -14,25 +14,33 @@ import { getMetricsForNamespace, type PodMetricData, Namespace } from "../../../
import type { KubeObjectDetailsProps } from "../kube-object-details"; import type { KubeObjectDetailsProps } from "../kube-object-details";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { resourceQuotaStore } from "../+config-resource-quotas/legacy-store";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { limitRangeStore } from "../+config-limit-ranges/legacy-store";
import { ResourceMetrics } from "../resource-metrics"; import { ResourceMetrics } from "../resource-metrics";
import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { getDetailsUrl } from "../kube-detail-params";
import logger from "../../../common/logger"; import logger from "../../../common/logger";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable";
import type { ResourceQuotaStore } from "../+config-resource-quotas/store";
import type { LimitRangeStore } from "../+config-limit-ranges/store";
import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable";
import limitRangeStoreInjectable from "../+config-limit-ranges/store.injectable";
import resourceQuotaStoreInjectable from "../+config-resource-quotas/store.injectable";
export interface NamespaceDetailsProps extends KubeObjectDetailsProps<Namespace> { export interface NamespaceDetailsProps extends KubeObjectDetailsProps<Namespace> {
} }
interface Dependencies { interface Dependencies {
subscribeStores: SubscribeStores; subscribeStores: SubscribeStores;
getActiveClusterEntity: GetActiveClusterEntity;
getDetailsUrl: GetDetailsUrl;
resourceQuotaStore: ResourceQuotaStore;
limitRangeStore: LimitRangeStore;
} }
@observer @observer
@ -51,8 +59,8 @@ class NonInjectedNamespaceDetails extends React.Component<NamespaceDetailsProps
}), }),
this.props.subscribeStores([ this.props.subscribeStores([
resourceQuotaStore, this.props.resourceQuotaStore,
limitRangeStore, this.props.limitRangeStore,
]), ]),
]); ]);
} }
@ -60,13 +68,13 @@ class NonInjectedNamespaceDetails extends React.Component<NamespaceDetailsProps
@computed get quotas() { @computed get quotas() {
const namespace = this.props.object.getName(); const namespace = this.props.object.getName();
return resourceQuotaStore.getAllByNs(namespace); return this.props.resourceQuotaStore.getAllByNs(namespace);
} }
@computed get limitranges() { @computed get limitranges() {
const namespace = this.props.object.getName(); const namespace = this.props.object.getName();
return limitRangeStore.getAllByNs(namespace); return this.props.limitRangeStore.getAllByNs(namespace);
} }
loadMetrics = async () => { loadMetrics = async () => {
@ -74,7 +82,7 @@ class NonInjectedNamespaceDetails extends React.Component<NamespaceDetailsProps
}; };
render() { render() {
const { object: namespace } = this.props; const { object: namespace, getActiveClusterEntity, resourceQuotaStore, getDetailsUrl, limitRangeStore } = this.props;
if (!namespace) { if (!namespace) {
return null; return null;
@ -109,23 +117,19 @@ class NonInjectedNamespaceDetails extends React.Component<NamespaceDetailsProps
<DrawerItem name="Resource Quotas" className="quotas flex align-center"> <DrawerItem name="Resource Quotas" className="quotas flex align-center">
{!this.quotas && resourceQuotaStore.isLoading && <Spinner/>} {!this.quotas && resourceQuotaStore.isLoading && <Spinner/>}
{this.quotas.map(quota => { {this.quotas.map(quota => quota.selfLink && (
return quota.selfLink && (
<Link key={quota.getId()} to={getDetailsUrl(quota.selfLink)}> <Link key={quota.getId()} to={getDetailsUrl(quota.selfLink)}>
{quota.getName()} {quota.getName()}
</Link> </Link>
); ))}
})}
</DrawerItem> </DrawerItem>
<DrawerItem name="Limit Ranges"> <DrawerItem name="Limit Ranges">
{!this.limitranges && limitRangeStore.isLoading && <Spinner/>} {!this.limitranges && limitRangeStore.isLoading && <Spinner/>}
{this.limitranges.map(limitrange => { {this.limitranges.map(limitrange => limitrange.selfLink && (
return limitrange.selfLink && (
<Link key={limitrange.getId()} to={getDetailsUrl(limitrange.selfLink)}> <Link key={limitrange.getId()} to={getDetailsUrl(limitrange.selfLink)}>
{limitrange.getName()} {limitrange.getName()}
</Link> </Link>
); ))}
})}
</DrawerItem> </DrawerItem>
</div> </div>
); );
@ -136,6 +140,10 @@ export const NamespaceDetails = withInjectables<Dependencies, NamespaceDetailsPr
getProps: (di, props) => ({ getProps: (di, props) => ({
...props, ...props,
subscribeStores: di.inject(subscribeStoresInjectable), subscribeStores: di.inject(subscribeStoresInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
getDetailsUrl: di.inject(getDetailsUrlInjectable),
limitRangeStore: di.inject(limitRangeStoreInjectable),
resourceQuotaStore: di.inject(resourceQuotaStoreInjectable),
}), }),
}); });

View File

@ -17,7 +17,7 @@ import type { KubeObjectDetailsProps } from "../kube-object-details";
import { IngressCharts } from "./ingress-charts"; import { IngressCharts } from "./ingress-charts";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { computeRuleDeclarations, getMetricsForIngress, type IngressMetricData } from "../../../common/k8s-api/endpoints/ingress.api"; import { computeRuleDeclarations, getMetricsForIngress, type IngressMetricData } from "../../../common/k8s-api/endpoints/ingress.api";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import logger from "../../../common/logger"; import logger from "../../../common/logger";

View File

@ -19,7 +19,6 @@ import { NodeCharts } from "./node-charts";
import { makeObservable, observable, reaction } from "mobx"; import { makeObservable, observable, reaction } from "mobx";
import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import { NodeDetailsResources } from "./details-resources"; import { NodeDetailsResources } from "./details-resources";
import { DrawerTitle } from "../drawer/drawer-title"; import { DrawerTitle } from "../drawer/drawer-title";
@ -29,6 +28,8 @@ import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import type { PodStore } from "../+workloads-pods/store"; import type { PodStore } from "../+workloads-pods/store";
import podStoreInjectable from "../+workloads-pods/store.injectable"; import podStoreInjectable from "../+workloads-pods/store.injectable";
import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable";
export interface NodeDetailsProps extends KubeObjectDetailsProps<Node> { export interface NodeDetailsProps extends KubeObjectDetailsProps<Node> {
} }
@ -36,6 +37,7 @@ export interface NodeDetailsProps extends KubeObjectDetailsProps<Node> {
interface Dependencies { interface Dependencies {
subscribeStores: SubscribeStores; subscribeStores: SubscribeStores;
podStore: PodStore; podStore: PodStore;
getActiveClusterEntity: GetActiveClusterEntity;
} }
@observer @observer
@ -66,7 +68,7 @@ class NonInjectedNodeDetails extends React.Component<NodeDetailsProps & Dependen
}; };
render() { render() {
const { object: node, podStore } = this.props; const { object: node, podStore, getActiveClusterEntity } = this.props;
if (!node) { if (!node) {
return null; return null;
@ -193,6 +195,7 @@ export const NodeDetails = withInjectables<Dependencies, NodeDetailsProps>(NonIn
...props, ...props,
subscribeStores: di.inject(subscribeStoresInjectable), subscribeStores: di.inject(subscribeStoresInjectable),
podStore: di.inject(podStoreInjectable), podStore: di.inject(podStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
}), }),
}); });

View File

@ -16,7 +16,7 @@ import { ResourceMetrics } from "../resource-metrics";
import { VolumeClaimDiskChart } from "./volume-claim-disk-chart"; import { VolumeClaimDiskChart } from "./volume-claim-disk-chart";
import type { KubeObjectDetailsProps } from "../kube-object-details"; import type { KubeObjectDetailsProps } from "../kube-object-details";
import { getMetricsForPvc, type PersistentVolumeClaimMetricData, PersistentVolumeClaim } from "../../../common/k8s-api/endpoints"; import { getMetricsForPvc, type PersistentVolumeClaimMetricData, PersistentVolumeClaim } from "../../../common/k8s-api/endpoints";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { getDetailsUrl } from "../kube-detail-params"; import { getDetailsUrl } from "../kube-detail-params";

View File

@ -21,7 +21,6 @@ import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
import { makeObservable, observable, reaction } from "mobx"; import { makeObservable, observable, reaction } from "mobx";
import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import logger from "../../../common/logger"; import logger from "../../../common/logger";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
@ -29,6 +28,8 @@ import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import daemonSetStoreInjectable from "./store.injectable"; import daemonSetStoreInjectable from "./store.injectable";
import podStoreInjectable from "../+workloads-pods/store.injectable"; import podStoreInjectable from "../+workloads-pods/store.injectable";
import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable";
export interface DaemonSetDetailsProps extends KubeObjectDetailsProps<DaemonSet> { export interface DaemonSetDetailsProps extends KubeObjectDetailsProps<DaemonSet> {
} }
@ -37,6 +38,7 @@ interface Dependencies {
subscribeStores: SubscribeStores; subscribeStores: SubscribeStores;
daemonSetStore: DaemonSetStore; daemonSetStore: DaemonSetStore;
podStore: PodStore; podStore: PodStore;
getActiveClusterEntity: GetActiveClusterEntity;
} }
@observer @observer
@ -66,7 +68,7 @@ class NonInjectedDaemonSetDetails extends React.Component<DaemonSetDetailsProps
}; };
render() { render() {
const { object: daemonSet, daemonSetStore, podStore } = this.props; const { object: daemonSet, daemonSetStore, podStore, getActiveClusterEntity } = this.props;
if (!daemonSet) { if (!daemonSet) {
return null; return null;
@ -140,5 +142,6 @@ export const DaemonSetDetails = withInjectables<Dependencies, DaemonSetDetailsPr
subscribeStores: di.inject(subscribeStoresInjectable), subscribeStores: di.inject(subscribeStoresInjectable),
daemonSetStore: di.inject(daemonSetStoreInjectable), daemonSetStore: di.inject(daemonSetStoreInjectable),
podStore: di.inject(podStoreInjectable), podStore: di.inject(podStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
}), }),
}); });

View File

@ -23,7 +23,6 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import type { ReplicaSetStore } from "../+workloads-replicasets/store"; import type { ReplicaSetStore } from "../+workloads-replicasets/store";
import { DeploymentReplicaSets } from "./deployment-replicasets"; import { DeploymentReplicaSets } from "./deployment-replicasets";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import logger from "../../../common/logger"; import logger from "../../../common/logger";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
@ -33,6 +32,8 @@ import type { PodStore } from "../+workloads-pods/store";
import podStoreInjectable from "../+workloads-pods/store.injectable"; import podStoreInjectable from "../+workloads-pods/store.injectable";
import replicaSetStoreInjectable from "../+workloads-replicasets/store.injectable"; import replicaSetStoreInjectable from "../+workloads-replicasets/store.injectable";
import deploymentStoreInjectable from "./store.injectable"; import deploymentStoreInjectable from "./store.injectable";
import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable";
export interface DeploymentDetailsProps extends KubeObjectDetailsProps<Deployment> { export interface DeploymentDetailsProps extends KubeObjectDetailsProps<Deployment> {
} }
@ -42,6 +43,7 @@ interface Dependencies {
podStore: PodStore; podStore: PodStore;
replicaSetStore: ReplicaSetStore; replicaSetStore: ReplicaSetStore;
deploymentStore: DeploymentStore; deploymentStore: DeploymentStore;
getActiveClusterEntity: GetActiveClusterEntity;
} }
@observer @observer
@ -73,7 +75,7 @@ class NonInjectedDeploymentDetails extends React.Component<DeploymentDetailsProp
}; };
render() { render() {
const { object: deployment, podStore, replicaSetStore, deploymentStore } = this.props; const { object: deployment, podStore, replicaSetStore, deploymentStore, getActiveClusterEntity } = this.props;
if (!deployment) { if (!deployment) {
return null; return null;
@ -174,6 +176,7 @@ export const DeploymentDetails = withInjectables<Dependencies, DeploymentDetails
podStore: di.inject(podStoreInjectable), podStore: di.inject(podStoreInjectable),
replicaSetStore: di.inject(replicaSetStoreInjectable), replicaSetStore: di.inject(replicaSetStoreInjectable),
deploymentStore: di.inject(deploymentStoreInjectable), deploymentStore: di.inject(deploymentStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
}), }),
}); });

View File

@ -22,10 +22,8 @@ import { KubeObjectMeta } from "../kube-object-meta";
import { makeObservable, observable, reaction } from "mobx"; import { makeObservable, observable, reaction } from "mobx";
import { podMetricTabs, PodCharts } from "../+workloads-pods/pod-charts"; import { podMetricTabs, PodCharts } from "../+workloads-pods/pod-charts";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ResourceMetrics } from "../resource-metrics"; import { ResourceMetrics } from "../resource-metrics";
import { getDetailsUrl } from "../kube-detail-params"; import type { ApiManager } from "../../../common/k8s-api/api-manager";
import { apiManager } from "../../../common/k8s-api/api-manager";
import logger from "../../../common/logger"; import logger from "../../../common/logger";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
@ -33,6 +31,11 @@ import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.inj
import type { PodStore } from "../+workloads-pods/store"; import type { PodStore } from "../+workloads-pods/store";
import podStoreInjectable from "../+workloads-pods/store.injectable"; import podStoreInjectable from "../+workloads-pods/store.injectable";
import jobStoreInjectable from "./store.injectable"; import jobStoreInjectable from "./store.injectable";
import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable";
import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable";
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable";
export interface JobDetailsProps extends KubeObjectDetailsProps<Job> { export interface JobDetailsProps extends KubeObjectDetailsProps<Job> {
} }
@ -41,6 +44,9 @@ interface Dependencies {
subscribeStores: SubscribeStores; subscribeStores: SubscribeStores;
podStore: PodStore; podStore: PodStore;
jobStore: JobStore; jobStore: JobStore;
getActiveClusterEntity: GetActiveClusterEntity;
getDetailsUrl: GetDetailsUrl;
apiManager: ApiManager;
} }
@observer @observer
@ -70,7 +76,7 @@ class NonInjectedJobDetails extends React.Component<JobDetailsProps & Dependenci
}; };
render() { render() {
const { object: job, jobStore } = this.props; const { object: job, jobStore, getActiveClusterEntity, getDetailsUrl, apiManager } = this.props;
if (!job) { if (!job) {
return null; return null;
@ -178,6 +184,9 @@ export const JobDetails = withInjectables<Dependencies, JobDetailsProps>(NonInje
subscribeStores: di.inject(subscribeStoresInjectable), subscribeStores: di.inject(subscribeStoresInjectable),
podStore: di.inject(podStoreInjectable), podStore: di.inject(podStoreInjectable),
jobStore: di.inject(jobStoreInjectable), jobStore: di.inject(jobStoreInjectable),
getDetailsUrl: di.inject(getDetailsUrlInjectable),
apiManager: di.inject(apiManagerInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
}), }),
}); });

View File

@ -17,12 +17,13 @@ import { ResourceMetrics } from "../resource-metrics";
import type { MetricData } from "../../../common/k8s-api/endpoints/metrics.api"; import type { MetricData } from "../../../common/k8s-api/endpoints/metrics.api";
import { ContainerCharts } from "./container-charts"; import { ContainerCharts } from "./container-charts";
import { LocaleDate } from "../locale-date"; import { LocaleDate } from "../locale-date";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import type { PortForwardStore } from "../../port-forward"; import type { PortForwardStore } from "../../port-forward";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable"; import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable";
import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable";
export interface PodDetailsContainerProps { export interface PodDetailsContainerProps {
pod: Pod; pod: Pod;
@ -32,6 +33,7 @@ export interface PodDetailsContainerProps {
interface Dependencies { interface Dependencies {
portForwardStore: PortForwardStore; portForwardStore: PortForwardStore;
getActiveClusterEntity: GetActiveClusterEntity;
} }
@observer @observer
@ -82,7 +84,7 @@ class NonInjectedPodDetailsContainer extends React.Component<PodDetailsContainer
} }
render() { render() {
const { pod, container, metrics } = this.props; const { pod, container, metrics, getActiveClusterEntity } = this.props;
if (!pod || !container) return null; if (!pod || !container) return null;
const { name, image, imagePullPolicy, ports, volumeMounts, command, args } = container; const { name, image, imagePullPolicy, ports, volumeMounts, command, args } = container;
@ -211,13 +213,10 @@ class NonInjectedPodDetailsContainer extends React.Component<PodDetailsContainer
} }
} }
export const PodDetailsContainer = withInjectables<Dependencies, PodDetailsContainerProps>( export const PodDetailsContainer = withInjectables<Dependencies, PodDetailsContainerProps>(NonInjectedPodDetailsContainer, {
NonInjectedPodDetailsContainer,
{
getProps: (di, props) => ({ getProps: (di, props) => ({
portForwardStore: di.inject(portForwardStoreInjectable),
...props, ...props,
portForwardStore: di.inject(portForwardStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
}), }),
}, });
);

View File

@ -24,7 +24,7 @@ import type { KubeObjectDetailsProps } from "../kube-object-details";
import { getItemMetrics } from "../../../common/k8s-api/endpoints/metrics.api"; import { getItemMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
import { PodCharts, podMetricTabs } from "./pod-charts"; import { PodCharts, podMetricTabs } from "./pod-charts";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import { getDetailsUrl } from "../kube-detail-params"; import { getDetailsUrl } from "../kube-detail-params";
import logger from "../../../common/logger"; import logger from "../../../common/logger";

View File

@ -19,7 +19,6 @@ import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics";
import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import logger from "../../../common/logger"; import logger from "../../../common/logger";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
@ -29,6 +28,8 @@ import type { PodStore } from "../+workloads-pods/store";
import podStoreInjectable from "../+workloads-pods/store.injectable"; import podStoreInjectable from "../+workloads-pods/store.injectable";
import type { ReplicaSetStore } from "./store"; import type { ReplicaSetStore } from "./store";
import replicaSetStoreInjectable from "./store.injectable"; import replicaSetStoreInjectable from "./store.injectable";
import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable";
export interface ReplicaSetDetailsProps extends KubeObjectDetailsProps<ReplicaSet> { export interface ReplicaSetDetailsProps extends KubeObjectDetailsProps<ReplicaSet> {
} }
@ -37,6 +38,7 @@ interface Dependencies {
subscribeStores: SubscribeStores; subscribeStores: SubscribeStores;
podStore: PodStore; podStore: PodStore;
replicaSetStore: ReplicaSetStore; replicaSetStore: ReplicaSetStore;
getActiveClusterEntity: GetActiveClusterEntity;
} }
@observer @observer
@ -67,7 +69,7 @@ class NonInjectedReplicaSetDetails extends React.Component<ReplicaSetDetailsProp
}; };
render() { render() {
const { object: replicaSet, podStore, replicaSetStore } = this.props; const { object: replicaSet, podStore, replicaSetStore, getActiveClusterEntity } = this.props;
if (!replicaSet) { if (!replicaSet) {
return null; return null;
@ -141,5 +143,6 @@ export const ReplicaSetDetails = withInjectables<Dependencies, ReplicaSetDetails
subscribeStores: di.inject(subscribeStoresInjectable), subscribeStores: di.inject(subscribeStoresInjectable),
podStore: di.inject(podStoreInjectable), podStore: di.inject(podStoreInjectable),
replicaSetStore: di.inject(replicaSetStoreInjectable), replicaSetStore: di.inject(replicaSetStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
}), }),
}); });

View File

@ -21,7 +21,6 @@ import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics";
import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object-meta"; import { KubeObjectMeta } from "../kube-object-meta";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import logger from "../../../common/logger"; import logger from "../../../common/logger";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
@ -30,6 +29,8 @@ import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.inj
import type { PodStore } from "../+workloads-pods/store"; import type { PodStore } from "../+workloads-pods/store";
import podStoreInjectable from "../+workloads-pods/store.injectable"; import podStoreInjectable from "../+workloads-pods/store.injectable";
import statefulSetStoreInjectable from "./store.injectable"; import statefulSetStoreInjectable from "./store.injectable";
import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable";
import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable";
export interface StatefulSetDetailsProps extends KubeObjectDetailsProps<StatefulSet> { export interface StatefulSetDetailsProps extends KubeObjectDetailsProps<StatefulSet> {
} }
@ -38,6 +39,7 @@ interface Dependencies {
subscribeStores: SubscribeStores; subscribeStores: SubscribeStores;
podStore: PodStore; podStore: PodStore;
statefulSetStore: StatefulSetStore; statefulSetStore: StatefulSetStore;
getActiveClusterEntity: GetActiveClusterEntity;
} }
@observer @observer
@ -68,7 +70,7 @@ class NonInjectedStatefulSetDetails extends React.Component<StatefulSetDetailsPr
}; };
render() { render() {
const { object: statefulSet, podStore, statefulSetStore } = this.props; const { object: statefulSet, podStore, statefulSetStore, getActiveClusterEntity } = this.props;
if (!statefulSet) { if (!statefulSet) {
return null; return null;
@ -140,6 +142,7 @@ export const StatefulSetDetails = withInjectables<Dependencies, StatefulSetDetai
subscribeStores: di.inject(subscribeStoresInjectable), subscribeStores: di.inject(subscribeStoresInjectable),
podStore: di.inject(podStoreInjectable), podStore: di.inject(podStoreInjectable),
statefulSetStore: di.inject(statefulSetStoreInjectable), statefulSetStore: di.inject(statefulSetStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
}), }),
}); });

View File

@ -5,13 +5,12 @@
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import React from "react"; import React from "react";
import { broadcastMessage } from "../../../common/ipc"; import { broadcastMessage } from "../../../common/ipc";
import { catalogEntityRunListener } from "../../../common/ipc/catalog"; import { catalogEntityRunListener } from "../../../common/ipc/catalog";
import type { CatalogEntity } from "../../api/catalog-entity"; import type { CatalogEntity } from "../../api/catalog-entity";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import catalogEnitiesInjectable from "../../api/catalog/entity/entities.injectable";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import { Select } from "../select"; import { Select } from "../select";
@ -20,7 +19,10 @@ interface Dependencies {
entities: IComputedValue<CatalogEntity[]>; entities: IComputedValue<CatalogEntity[]>;
} }
const NonInjectedActivateEntityCommand = observer(({ closeCommandOverlay, entities }: Dependencies) => ( const NonInjectedActivateEntityCommand = observer(({
closeCommandOverlay,
entities,
}: Dependencies) => (
<Select <Select
id="activate-entity-input" id="activate-entity-input"
menuPortalTarget={null} menuPortalTarget={null}
@ -48,6 +50,6 @@ const NonInjectedActivateEntityCommand = observer(({ closeCommandOverlay, entiti
export const ActivateEntityCommand = withInjectables<Dependencies>(NonInjectedActivateEntityCommand, { export const ActivateEntityCommand = withInjectables<Dependencies>(NonInjectedActivateEntityCommand, {
getProps: di => ({ getProps: di => ({
closeCommandOverlay: di.inject(commandOverlayInjectable).close, closeCommandOverlay: di.inject(commandOverlayInjectable).close,
entities: computed(() => [...catalogEntityRegistry.items]), entities: di.inject(catalogEnitiesInjectable),
}), }),
}); });

View File

@ -16,11 +16,12 @@ import { Button } from "../button";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import type { KubeAuthUpdate } from "../../../common/cluster-types"; import type { KubeAuthUpdate } from "../../../common/cluster-types";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import { requestClusterActivation } from "../../ipc"; import { requestClusterActivation } from "../../ipc";
import type { NavigateToEntitySettings } from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; import type { NavigateToEntitySettings } from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import navigateToEntitySettingsInjectable from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; import navigateToEntitySettingsInjectable from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
export interface ClusterStatusProps { export interface ClusterStatusProps {
className?: IClassName; className?: IClassName;
@ -29,6 +30,7 @@ export interface ClusterStatusProps {
interface Dependencies { interface Dependencies {
navigateToEntitySettings: NavigateToEntitySettings; navigateToEntitySettings: NavigateToEntitySettings;
entityRegistry: CatalogEntityRegistry;
} }
@observer @observer
@ -46,7 +48,7 @@ class NonInjectedClusterStatus extends React.Component<ClusterStatusProps & Depe
} }
@computed get entity() { @computed get entity() {
return catalogEntityRegistry.getById(this.cluster.id); return this.props.entityRegistry.getById(this.cluster.id);
} }
@computed get hasErrors(): boolean { @computed get hasErrors(): boolean {
@ -158,13 +160,10 @@ class NonInjectedClusterStatus extends React.Component<ClusterStatusProps & Depe
} }
} }
export const ClusterStatus = withInjectables<Dependencies, ClusterStatusProps>( export const ClusterStatus = withInjectables<Dependencies, ClusterStatusProps>(NonInjectedClusterStatus, {
NonInjectedClusterStatus,
{
getProps: (di, props) => ({ getProps: (di, props) => ({
navigateToEntitySettings: di.inject(navigateToEntitySettingsInjectable),
...props, ...props,
navigateToEntitySettings: di.inject(navigateToEntitySettingsInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}), }),
}, });
);

View File

@ -12,18 +12,20 @@ import { ClusterStatus } from "./cluster-status";
import type { ClusterFrameHandler } from "./lens-views"; import type { ClusterFrameHandler } from "./lens-views";
import type { Cluster } from "../../../common/cluster/cluster"; import type { Cluster } from "../../../common/cluster/cluster";
import { ClusterStore } from "../../../common/cluster-store/cluster-store"; import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import { requestClusterActivation } from "../../ipc"; import { requestClusterActivation } from "../../ipc";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import clusterViewRouteParametersInjectable from "./cluster-view-route-parameters.injectable"; import clusterViewRouteParametersInjectable from "./cluster-view-route-parameters.injectable";
import clusterFramesInjectable from "./lens-views.injectable"; import clusterFramesInjectable from "./lens-views.injectable";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
interface Dependencies { interface Dependencies {
clusterId: IComputedValue<string>; clusterId: IComputedValue<string>;
clusterFrames: ClusterFrameHandler; clusterFrames: ClusterFrameHandler;
navigateToCatalog: NavigateToCatalog; navigateToCatalog: NavigateToCatalog;
entityRegistry: CatalogEntityRegistry;
} }
@observer @observer
@ -60,7 +62,7 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
componentWillUnmount() { componentWillUnmount() {
this.props.clusterFrames.clearVisibleCluster(); this.props.clusterFrames.clearVisibleCluster();
catalogEntityRegistry.activeEntity = undefined; this.props.entityRegistry.activeEntity = undefined;
} }
bindEvents() { bindEvents() {
@ -69,7 +71,7 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
this.props.clusterFrames.setVisibleCluster(clusterId); this.props.clusterFrames.setVisibleCluster(clusterId);
this.props.clusterFrames.initView(clusterId); this.props.clusterFrames.initView(clusterId);
requestClusterActivation(clusterId, false); // activate and fetch cluster's state from main requestClusterActivation(clusterId, false); // activate and fetch cluster's state from main
catalogEntityRegistry.activeEntity = clusterId; this.props.entityRegistry.activeEntity = clusterId;
}, { }, {
fireImmediately: true, fireImmediately: true,
}), }),
@ -106,6 +108,7 @@ export const ClusterView = withInjectables<Dependencies>(NonInjectedClusterView,
clusterId: di.inject(clusterViewRouteParametersInjectable).clusterId, clusterId: di.inject(clusterViewRouteParametersInjectable).clusterId,
navigateToCatalog: di.inject(navigateToCatalogInjectable), navigateToCatalog: di.inject(navigateToCatalogInjectable),
clusterFrames: di.inject(clusterFramesInjectable), clusterFrames: di.inject(clusterFramesInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}), }),
}); });

View File

@ -13,20 +13,22 @@ import type { ClusterId } from "../../../common/cluster-types";
import type { CommandOverlay } from "./command-overlay.injectable"; import type { CommandOverlay } from "./command-overlay.injectable";
import commandOverlayInjectable from "./command-overlay.injectable"; import commandOverlayInjectable from "./command-overlay.injectable";
import { isMac } from "../../../common/vars"; import { isMac } from "../../../common/vars";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import { broadcastMessage, ipcRendererOn } from "../../../common/ipc"; import { broadcastMessage, ipcRendererOn } from "../../../common/ipc";
import type { Disposer } from "../../utils"; import type { Disposer } from "../../utils";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import windowAddEventListenerInjectable from "../../window/event-listener.injectable"; import windowAddEventListenerInjectable from "../../window/event-listener.injectable";
import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import matchedClusterIdInjectable from "../../navigation/matched-cluster-id.injectable"; import matchedClusterIdInjectable from "../../navigation/matched-cluster-id.injectable";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import hostedClusterIdInjectable from "../../../common/cluster-store/hosted-cluster-id.injectable";
interface Dependencies { interface Dependencies {
addWindowEventListener: <K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions) => Disposer; addWindowEventListener: <K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions) => Disposer;
commandOverlay: CommandOverlay; commandOverlay: CommandOverlay;
clusterId?: ClusterId; clusterId?: ClusterId;
matchedClusterId: IComputedValue<ClusterId>; matchedClusterId: IComputedValue<ClusterId>;
entityRegistry: CatalogEntityRegistry;
} }
@observer @observer
@ -41,12 +43,12 @@ class NonInjectedCommandContainer extends React.Component<Dependencies> {
} }
handleCommandPalette = () => { handleCommandPalette = () => {
const { commandOverlay } = this.props; const { commandOverlay, entityRegistry } = this.props;
const clusterIsActive = this.props.matchedClusterId.get() !== undefined; const clusterIsActive = this.props.matchedClusterId.get() !== undefined;
if (clusterIsActive) { if (clusterIsActive) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
broadcastMessage(`command-palette:${catalogEntityRegistry.activeEntity!.getId()}:open`); broadcastMessage(`command-palette:${entityRegistry.activeEntity!.getId()}:open`);
} else { } else {
commandOverlay.open(<CommandDialog />); commandOverlay.open(<CommandDialog />);
} }
@ -97,19 +99,13 @@ class NonInjectedCommandContainer extends React.Component<Dependencies> {
} }
} }
export const CommandContainer = withInjectables<Dependencies>( export const CommandContainer = withInjectables<Dependencies>(NonInjectedCommandContainer, {
NonInjectedCommandContainer, getProps: (di, props) => ({
{ ...props,
getProps: (di, props) => { clusterId: di.inject(hostedClusterIdInjectable),
const hostedCluster = di.inject(hostedClusterInjectable);
return {
clusterId: hostedCluster?.id,
addWindowEventListener: di.inject(windowAddEventListenerInjectable), addWindowEventListener: di.inject(windowAddEventListenerInjectable),
commandOverlay: di.inject(commandOverlayInjectable), commandOverlay: di.inject(commandOverlayInjectable),
matchedClusterId: di.inject(matchedClusterIdInjectable), matchedClusterId: di.inject(matchedClusterIdInjectable),
...props, entityRegistry: di.inject(catalogEntityRegistryInjectable),
}; }),
}, });
},
);

View File

@ -10,24 +10,38 @@ import { observer } from "mobx-react";
import React, { useState } from "react"; import React, { useState } from "react";
import commandOverlayInjectable from "./command-overlay.injectable"; import commandOverlayInjectable from "./command-overlay.injectable";
import type { CatalogEntity } from "../../../common/catalog"; import type { CatalogEntity } from "../../../common/catalog";
import { navigate } from "../../navigation";
import { broadcastMessage } from "../../../common/ipc"; import { broadcastMessage } from "../../../common/ipc";
import { IpcRendererNavigationEvents } from "../../navigation/events"; import { IpcRendererNavigationEvents } from "../../navigation/events";
import type { RegisteredCommand } from "./registered-commands/commands"; import type { RegisteredCommand } from "./registered-commands/commands";
import { iter } from "../../utils"; import { iter } from "../../utils";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import registeredCommandsInjectable from "./registered-commands/registered-commands.injectable"; import registeredCommandsInjectable from "./registered-commands/registered-commands.injectable";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import type { SingleValue } from "react-select"; import type { SingleValue } from "react-select";
import type { Navigate } from "../../navigation/navigate.injectable";
import navigateInjectable from "../../navigation/navigate.injectable";
import activeEntityInjectable from "../../api/catalog/entity/active.injectable";
interface Dependencies { interface Dependencies {
commands: IComputedValue<Map<string, RegisteredCommand>>; commands: IComputedValue<Map<string, RegisteredCommand>>;
activeEntity: CatalogEntity; activeEntity: IComputedValue<CatalogEntity | undefined>;
closeCommandOverlay: () => void; closeCommandOverlay: () => void;
navigate: Navigate;
} }
const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeCommandOverlay }: Dependencies) => { const NonInjectedCommandDialog = observer(({
commands,
activeEntity,
closeCommandOverlay,
navigate,
}: Dependencies) => {
const [searchValue, setSearchValue] = useState(""); const [searchValue, setSearchValue] = useState("");
const entity = activeEntity.get();
if (!entity) {
return null;
}
const context = { entity };
const executeAction = (option: SingleValue<typeof activeCommands[number]>) => { const executeAction = (option: SingleValue<typeof activeCommands[number]>) => {
if (!option) { if (!option) {
@ -37,7 +51,7 @@ const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeComman
try { try {
closeCommandOverlay(); closeCommandOverlay();
option.value.action({ option.value.action({
entity: activeEntity, ...context,
navigate: (url, opts = {}) => { navigate: (url, opts = {}) => {
const { forceRootFrame = false } = opts; const { forceRootFrame = false } = opts;
@ -53,10 +67,6 @@ const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeComman
} }
}; };
const context = {
entity: activeEntity,
};
const activeCommands = iter.pipeline(commands.get().values()) const activeCommands = iter.pipeline(commands.get().values())
.filter(command => { .filter(command => {
try { try {
@ -101,9 +111,8 @@ const NonInjectedCommandDialog = observer(({ commands, activeEntity, closeComman
export const CommandDialog = withInjectables<Dependencies>(NonInjectedCommandDialog, { export const CommandDialog = withInjectables<Dependencies>(NonInjectedCommandDialog, {
getProps: di => ({ getProps: di => ({
commands: di.inject(registeredCommandsInjectable), commands: di.inject(registeredCommandsInjectable),
// TODO: replace with injection activeEntity: di.inject(activeEntityInjectable),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
activeEntity: catalogEntityRegistry.activeEntity!,
closeCommandOverlay: di.inject(commandOverlayInjectable).close, closeCommandOverlay: di.inject(commandOverlayInjectable).close,
navigate: di.inject(navigateInjectable),
}), }),
}); });

View File

@ -6,11 +6,11 @@
import styles from "./hotbar-entity-icon.module.scss"; import styles from "./hotbar-entity-icon.module.scss";
import React from "react"; import React from "react";
import type { IComputedValue } from "mobx";
import { observable } from "mobx"; import { observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { CatalogCategoryRegistry, CatalogEntity, CatalogEntityContextMenu } from "../../../common/catalog"; import type { CatalogCategoryRegistry, CatalogEntity, CatalogEntityContextMenu } from "../../../common/catalog";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import type { IClassName } from "../../utils"; import type { IClassName } from "../../utils";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { Icon } from "../icon"; import { Icon } from "../icon";
@ -21,6 +21,7 @@ import { navigate } from "../../navigation";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable"; import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable"; import onContextMenuOpenInjectable from "../../../common/catalog/on-context-menu-open.injectable";
import activeEntityInjectable from "../../api/catalog/entity/active.injectable";
export interface HotbarEntityIconProps { export interface HotbarEntityIconProps {
entity: CatalogEntity; entity: CatalogEntity;
@ -36,6 +37,7 @@ export interface HotbarEntityIconProps {
interface Dependencies { interface Dependencies {
onContextMenuOpen: OnContextMenuOpen; onContextMenuOpen: OnContextMenuOpen;
catalogCategoryRegistry: CatalogCategoryRegistry; catalogCategoryRegistry: CatalogCategoryRegistry;
activeEntity: IComputedValue<CatalogEntity | undefined>;
} }
@observer @observer
@ -73,7 +75,7 @@ class NonInjectedHotbarEntityIcon extends React.Component<HotbarEntityIconProps
} }
isActive(item: CatalogEntity) { isActive(item: CatalogEntity) {
return catalogEntityRegistry.activeEntity?.metadata?.uid == item.getId(); return this.props.activeEntity.get()?.metadata?.uid == item.getId();
} }
onMenuOpen() { onMenuOpen() {
@ -123,5 +125,6 @@ export const HotbarEntityIcon = withInjectables<Dependencies, HotbarEntityIconPr
...props, ...props,
catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable), catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable),
onContextMenuOpen: di.inject(onContextMenuOpenInjectable), onContextMenuOpen: di.inject(onContextMenuOpenInjectable),
activeEntity: di.inject(activeEntityInjectable),
}), }),
}); });

View File

@ -10,7 +10,7 @@ import { observer } from "mobx-react";
import { HotbarEntityIcon } from "./hotbar-entity-icon"; import { HotbarEntityIcon } from "./hotbar-entity-icon";
import type { IClassName } from "../../utils"; import type { IClassName } from "../../utils";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import type { CatalogEntity } from "../../api/catalog-entity"; import type { CatalogEntity } from "../../api/catalog-entity";
import { DragDropContext, Draggable, Droppable, type DropResult } from "react-beautiful-dnd"; import { DragDropContext, Draggable, Droppable, type DropResult } from "react-beautiful-dnd";
import { HotbarSelector } from "./hotbar-selector"; import { HotbarSelector } from "./hotbar-selector";
@ -22,6 +22,7 @@ import { action, makeObservable, observable } from "mobx";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import type { HotbarStore } from "../../../common/hotbars/store"; import type { HotbarStore } from "../../../common/hotbars/store";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
export interface HotbarMenuProps { export interface HotbarMenuProps {
className?: IClassName; className?: IClassName;
@ -29,6 +30,7 @@ export interface HotbarMenuProps {
interface Dependencies { interface Dependencies {
hotbarStore: HotbarStore; hotbarStore: HotbarStore;
entityRegistry: CatalogEntityRegistry;
} }
@observer @observer
@ -51,7 +53,7 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
return null; return null;
} }
return catalogEntityRegistry.getById(item.entity.uid) ?? null; return this.props.entityRegistry.getById(item.entity.uid) ?? null;
} }
@action @action
@ -140,7 +142,7 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
key={index} key={index}
index={index} index={index}
entity={entity} entity={entity}
onClick={() => catalogEntityRegistry.onRun(entity)} onClick={() => this.props.entityRegistry.onRun(entity)}
className={cssNames({ isDragging: snapshot.isDragging })} className={cssNames({ isDragging: snapshot.isDragging })}
remove={this.removeItem} remove={this.removeItem}
add={this.addItem} add={this.addItem}
@ -192,13 +194,10 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
} }
} }
export const HotbarMenu = withInjectables<Dependencies, HotbarMenuProps>( export const HotbarMenu = withInjectables<Dependencies, HotbarMenuProps>(NonInjectedHotbarMenu, {
NonInjectedHotbarMenu,
{
getProps: (di, props) => ({ getProps: (di, props) => ({
hotbarStore: di.inject(hotbarStoreInjectable),
...props, ...props,
hotbarStore: di.inject(hotbarStoreInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}), }),
}, });
);

View File

@ -9,26 +9,27 @@ import { autorun, observable, makeObservable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import type { InputProps } from "./input"; import type { InputProps } from "./input";
import { SearchInput } from "./search-input"; import { SearchInput } from "./search-input";
import { createPageParam } from "../../navigation"; import type { PageParam } from "../../navigation";
import { withInjectables } from "@ogre-tools/injectable-react";
export const searchUrlParam = createPageParam({ import searchUrlPageParamInjectable from "./search-url-page-param.injectable";
name: "search",
defaultValue: "",
});
export interface SearchInputUrlProps extends InputProps { export interface SearchInputUrlProps extends InputProps {
compact?: boolean; // show only search-icon when not focused compact?: boolean; // show only search-icon when not focused
} }
interface Dependencies {
searchUrlParam: PageParam<string>;
}
@observer @observer
export class SearchInputUrl extends React.Component<SearchInputUrlProps> { class NonInjectedSearchInputUrl extends React.Component<SearchInputUrlProps & Dependencies> {
@observable inputVal = ""; // fix: use empty string on init to avoid react warnings @observable inputVal = ""; // fix: use empty string on init to avoid react warnings
updateUrl = debounce((val: string) => searchUrlParam.set(val), 250); readonly updateUrl = debounce((val: string) => this.props.searchUrlParam.set(val), 250);
componentDidMount(): void { componentDidMount(): void {
disposeOnUnmount(this, [ disposeOnUnmount(this, [
autorun(() => this.inputVal = searchUrlParam.get()), autorun(() => this.inputVal = this.props.searchUrlParam.get()),
]); ]);
} }
@ -50,7 +51,7 @@ export class SearchInputUrl extends React.Component<SearchInputUrlProps> {
} }
}; };
constructor(props: SearchInputUrlProps) { constructor(props: SearchInputUrlProps & Dependencies) {
super(props); super(props);
makeObservable(this); makeObservable(this);
} }
@ -68,3 +69,10 @@ export class SearchInputUrl extends React.Component<SearchInputUrlProps> {
); );
} }
} }
export const SearchInputUrl = withInjectables<Dependencies, SearchInputUrlProps>(NonInjectedSearchInputUrl, {
getProps: (di, props) => ({
...props,
searchUrlParam: di.inject(searchUrlPageParamInjectable),
}),
});

View File

@ -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 createPageParamInjectable from "../../navigation/create-page-param.injectable";
const searchUrlPageParamInjectable = getInjectable({
id: "search-url-page-param",
instantiate: (di) => {
const createPageParam = di.inject(createPageParamInjectable);
return createPageParam({
name: "search",
defaultValue: "",
});
},
});
export default searchUrlPageParamInjectable;

View File

@ -20,8 +20,7 @@ import { AddRemoveButtons } from "../add-remove-buttons";
import { NoItems } from "../no-items"; import { NoItems } from "../no-items";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import type { ItemObject } from "../../../common/item.store"; import type { ItemObject } from "../../../common/item.store";
import type { Filter } from "./page-filters.store"; import type { Filter, PageFiltersStore } from "./page-filters/store";
import { pageFilters } from "./page-filters.store";
import type { ThemeStore } from "../../themes/store"; import type { ThemeStore } from "../../themes/store";
import { MenuActions } from "../menu/menu-actions"; import { MenuActions } from "../menu/menu-actions";
import { MenuItem } from "../menu"; import { MenuItem } from "../menu";
@ -30,6 +29,7 @@ import { UserStore } from "../../../common/user-store";
import type { ItemListStore } from "./list-layout"; import type { ItemListStore } from "./list-layout";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import themeStoreInjectable from "../../themes/store.injectable"; import themeStoreInjectable from "../../themes/store.injectable";
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStores extends boolean> { export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStores extends boolean> {
getFilters: () => Filter[]; getFilters: () => Filter[];
@ -68,10 +68,14 @@ export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStor
interface Dependencies { interface Dependencies {
themeStore: ThemeStore; themeStore: ThemeStore;
pageFiltersStore: PageFiltersStore;
} }
@observer @observer
class NonInjectedItemListLayoutContent<Item extends ItemObject, PreLoadStores extends boolean> extends React.Component<ItemListLayoutContentProps<Item, PreLoadStores> & Dependencies> { class NonInjectedItemListLayoutContent<
Item extends ItemObject,
PreLoadStores extends boolean,
> extends React.Component<ItemListLayoutContentProps<Item, PreLoadStores> & Dependencies> {
constructor(props: ItemListLayoutContentProps<Item, PreLoadStores> & Dependencies) { constructor(props: ItemListLayoutContentProps<Item, PreLoadStores> & Dependencies) {
super(props); super(props);
makeObservable(this); makeObservable(this);
@ -221,7 +225,7 @@ class NonInjectedItemListLayoutContent<Item extends ItemObject, PreLoadStores ex
<NoItems> <NoItems>
No items found. No items found.
<p> <p>
<a onClick={() => pageFilters.reset()} className="contrast"> <a onClick={() => this.props.pageFiltersStore.reset()} className="contrast">
Reset filters? Reset filters?
</a> </a>
</p> </p>
@ -367,5 +371,6 @@ export const ItemListLayoutContent = withInjectables<Dependencies, ItemListLayou
getProps: (di, props) => ({ getProps: (di, props) => ({
...props, ...props,
themeStore: di.inject(themeStoreInjectable), themeStore: di.inject(themeStoreInjectable),
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
}), }),
}) as <Item extends ItemObject, PreLoadStores extends boolean>(props: ItemListLayoutContentProps<Item, PreLoadStores>) => React.ReactElement; }) as <Item extends ItemObject, PreLoadStores extends boolean>(props: ItemListLayoutContentProps<Item, PreLoadStores>) => React.ReactElement;

View File

@ -6,7 +6,7 @@
import React from "react"; import React from "react";
import type { IconProps } from "../icon"; import type { IconProps } from "../icon";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { FilterType } from "./page-filters.store"; import { FilterType } from "./page-filters/store";
export interface FilterIconProps extends Partial<IconProps> { export interface FilterIconProps extends Partial<IconProps> {
type: FilterType; type: FilterType;

View File

@ -6,9 +6,9 @@
import "./item-list-layout.scss"; import "./item-list-layout.scss";
import React from "react"; import React from "react";
import { PageFiltersList } from "./page-filters-list"; import { PageFiltersList } from "./page-filters/list";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { Filter } from "./page-filters.store"; import type { Filter } from "./page-filters/store";
export interface ItemListLayoutFilterProps { export interface ItemListLayoutFilterProps {
getIsReady: () => boolean; getIsReady: () => boolean;

View File

@ -11,7 +11,7 @@ import { observer } from "mobx-react";
import type { IClassName } from "../../utils"; import type { IClassName } from "../../utils";
import { cssNames, isDefined } from "../../utils"; import { cssNames, isDefined } from "../../utils";
import type { ItemObject } from "../../../common/item.store"; import type { ItemObject } from "../../../common/item.store";
import type { Filter } from "./page-filters.store"; import type { Filter } from "./page-filters/store";
import type { HeaderCustomizer, HeaderPlaceholders, ItemListStore, SearchFilter } from "./list-layout"; import type { HeaderCustomizer, HeaderPlaceholders, ItemListStore, SearchFilter } from "./list-layout";
import { SearchInputUrl } from "../input"; import { SearchInputUrl } from "../input";

View File

@ -16,8 +16,9 @@ import { autoBind, cssNames, noop } from "../../utils";
import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import type { AddRemoveButtonsProps } from "../add-remove-buttons";
import type { ItemObject } from "../../../common/item.store"; import type { ItemObject } from "../../../common/item.store";
import type { SearchInputUrlProps } from "../input"; import type { SearchInputUrlProps } from "../input";
import { FilterType, pageFilters } from "./page-filters.store"; import type { PageFiltersStore } from "./page-filters/store";
import { PageFiltersList } from "./page-filters-list"; import { FilterType } from "./page-filters/store";
import { PageFiltersList } from "./page-filters/list";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import itemListLayoutStorageInjectable from "./storage.injectable"; import itemListLayoutStorageInjectable from "./storage.injectable";
import { ItemListLayoutContent } from "./content"; import { ItemListLayoutContent } from "./content";
@ -28,6 +29,7 @@ import { observer } from "mobx-react";
import type { Primitive } from "type-fest"; import type { Primitive } from "type-fest";
import type { SubscribableStore } from "../../kube-watch-api/kube-watch-api"; import type { SubscribableStore } from "../../kube-watch-api/kube-watch-api";
import selectedFilterNamespacesInjectable from "../../../common/k8s-api/selected-filter-namespaces.injectable"; import selectedFilterNamespacesInjectable from "../../../common/k8s-api/selected-filter-namespaces.injectable";
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
export type SearchFilter<I extends ItemObject> = (item: I) => SingleOrMany<string | number | undefined | null>; export type SearchFilter<I extends ItemObject> = (item: I) => SingleOrMany<string | number | undefined | null>;
export type SearchFilters<I extends ItemObject> = Record<string, SearchFilter<I>>; export type SearchFilters<I extends ItemObject> = Record<string, SearchFilter<I>>;
@ -161,6 +163,7 @@ export interface ItemListLayoutStorage {
interface Dependencies { interface Dependencies {
selectedFilterNamespaces: IComputedValue<string[]>; selectedFilterNamespaces: IComputedValue<string[]>;
itemListLayoutStorage: StorageLayer<ItemListLayoutStorage>; itemListLayoutStorage: StorageLayer<ItemListLayoutStorage>;
pageFiltersStore: PageFiltersStore;
} }
@observer @observer
@ -197,7 +200,7 @@ class NonInjectedItemListLayout<I extends ItemObject, PreLoadStores extends bool
} }
@computed get filters() { @computed get filters() {
let { activeFilters } = pageFilters; let { activeFilters } = this.props.pageFiltersStore;
const { searchFilters = [] } = this.props; const { searchFilters = [] } = this.props;
if (searchFilters.length === 0) { if (searchFilters.length === 0) {
@ -229,7 +232,7 @@ class NonInjectedItemListLayout<I extends ItemObject, PreLoadStores extends bool
private filterCallbacks: ItemsFilters<I> = { private filterCallbacks: ItemsFilters<I> = {
[FilterType.SEARCH]: items => { [FilterType.SEARCH]: items => {
const { searchFilters = [] } = this.props; const { searchFilters = [] } = this.props;
const search = pageFilters.getValues(FilterType.SEARCH)[0] || ""; const search = this.props.pageFiltersStore.getValues(FilterType.SEARCH)[0] || "";
if (search && searchFilters.length) { if (search && searchFilters.length) {
const searchTexts = [search].map(normalizeText); const searchTexts = [search].map(normalizeText);
@ -326,17 +329,14 @@ class NonInjectedItemListLayout<I extends ItemObject, PreLoadStores extends bool
} }
} }
const InjectedItemListLayout = withInjectables<Dependencies, ItemListLayoutProps<ItemObject, boolean>>(NonInjectedItemListLayout, { export const ItemListLayout = withInjectables<Dependencies, ItemListLayoutProps<ItemObject, boolean>>(NonInjectedItemListLayout, {
getProps: (di, props) => ({ getProps: (di, props) => ({
...props,
selectedFilterNamespaces: di.inject(selectedFilterNamespacesInjectable), selectedFilterNamespaces: di.inject(selectedFilterNamespacesInjectable),
itemListLayoutStorage: di.inject(itemListLayoutStorageInjectable), itemListLayoutStorage: di.inject(itemListLayoutStorageInjectable),
...props, pageFiltersStore: di.inject(pageFiltersStoreInjectable),
}), }),
}); }) as <I extends ItemObject, PreLoadStores extends boolean = true>(props: ItemListLayoutProps<I, PreLoadStores>) => React.ReactElement;
export function ItemListLayout<I extends ItemObject, PreLoadStores extends boolean = true>(props: ItemListLayoutProps<I, PreLoadStores>) {
return <InjectedItemListLayout {...(props as never)} />;
}
function applyFilters<I extends ItemObject>(filters: ItemsFilter<I>[], items: I[]): I[] { function applyFilters<I extends ItemObject>(filters: ItemsFilter<I>[], items: I[]): I[] {
if (!filters || !filters.length) { if (!filters || !filters.length) {

View File

@ -1,85 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./page-filters-list.scss";
import React from "react";
import { observer } from "mobx-react";
import { Badge } from "../badge";
import { cssNames } from "../../utils";
import type { Filter } from "./page-filters.store";
import { pageFilters } from "./page-filters.store";
import { FilterIcon } from "./filter-icon";
import { Icon } from "../icon";
import { searchUrlParam } from "../input";
export interface PageFiltersListProps {
filters?: Filter[];
}
@observer
export class PageFiltersList extends React.Component<PageFiltersListProps> {
static defaultProps: PageFiltersListProps = {
get filters() {
return pageFilters.activeFilters;
},
};
reset = () => pageFilters.reset();
remove = (filter: Filter) => {
pageFilters.removeFilter(filter);
searchUrlParam.clear();
};
renderContent() {
const { filters = [] } = this.props;
if (!filters.length) {
return null;
}
return (
<>
<div className="header flex gaps">
<span>Currently applied filters:</span>
<a onClick={this.reset} className="reset">
Reset
</a>
</div>
<div className="labels">
{filters.map(filter => {
const { value, type } = filter;
return (
<Badge
key={`${type}-${value}`}
title={type}
className={cssNames("Badge flex gaps filter align-center", type)}
label={(
<>
<FilterIcon type={type}/>
<span className="value">{value}</span>
<Icon
small
material="close"
onClick={() => this.remove(filter)}
/>
</>
)}
/>
);
})}
</div>
</>
);
}
render() {
return (
<div className="PageFiltersList">
{this.renderContent()}
</div>
);
}
}

View File

@ -0,0 +1,95 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./list.scss";
import React from "react";
import { observer } from "mobx-react";
import { Badge } from "../../badge";
import { cssNames } from "../../../utils";
import type { Filter, PageFiltersStore } from "./store";
import { FilterIcon } from "../filter-icon";
import { Icon } from "../../icon";
import type { PageParam } from "../../../navigation";
import { withInjectables } from "@ogre-tools/injectable-react";
import pageFiltersStoreInjectable from "./store.injectable";
import searchUrlPageParamInjectable from "../../input/search-url-page-param.injectable";
export interface PageFiltersListProps {
filters?: Filter[];
}
interface Dependencies {
pageFiltersStore: PageFiltersStore;
searchUrlParam: PageParam<string>;
}
const NonInjectedPageFiltersList = observer(({
pageFiltersStore,
searchUrlParam,
filters: rawFilters,
}: Dependencies & PageFiltersListProps) => {
const filters = rawFilters ?? pageFiltersStore.activeFilters;
const reset = () => pageFiltersStore.reset();
const remove = (filter: Filter) => {
pageFiltersStore.removeFilter(filter);
searchUrlParam.clear();
};
const renderContent = () => {
if (filters.length === 0) {
return null;
}
return (
<>
<div className="header flex gaps">
<span>Currently applied filters:</span>
<a onClick={reset} className="reset">
Reset
</a>
</div>
<div className="labels">
{filters.map(filter => {
const { value, type } = filter;
return (
<Badge
key={`${type}-${value}`}
title={type}
className={cssNames("Badge flex gaps filter align-center", type)}
label={(
<>
<FilterIcon type={type}/>
<span className="value">{value}</span>
<Icon
small
material="close"
onClick={() => remove(filter)}
/>
</>
)}
/>
);
})}
</div>
</>
);
};
return (
<div className="PageFiltersList">
{renderContent()}
</div>
);
});
export const PageFiltersList = withInjectables<Dependencies, PageFiltersListProps>(NonInjectedPageFiltersList, {
getProps: (di, props) => ({
...props,
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
searchUrlParam: di.inject(searchUrlPageParamInjectable),
}),
});

View File

@ -0,0 +1,16 @@
/**
* 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 searchUrlPageParamInjectable from "../../input/search-url-page-param.injectable";
import { PageFiltersStore } from "./store";
const pageFiltersStoreInjectable = getInjectable({
id: "page-filters-store",
instantiate: (di) => new PageFiltersStore({
searchUrlParam: di.inject(searchUrlPageParamInjectable),
}),
});
export default pageFiltersStoreInjectable;

View File

@ -4,8 +4,8 @@
*/ */
import { computed, observable, reaction, makeObservable, action } from "mobx"; import { computed, observable, reaction, makeObservable, action } from "mobx";
import { autoBind } from "../../utils"; import type { PageParam } from "../../../navigation";
import { searchUrlParam } from "../input/search-input-url"; import { autoBind } from "../../../utils";
export enum FilterType { export enum FilterType {
SEARCH = "search", SEARCH = "search",
@ -16,6 +16,10 @@ export interface Filter {
value: string; value: string;
} }
interface Dependencies {
readonly searchUrlParam: PageParam<string>;
}
export class PageFiltersStore { export class PageFiltersStore {
protected filters = observable.array<Filter>([], { deep: false }); protected filters = observable.array<Filter>([], { deep: false });
protected isDisabled = observable.map<FilterType, boolean>(); protected isDisabled = observable.map<FilterType, boolean>();
@ -24,7 +28,7 @@ export class PageFiltersStore {
return this.filters.filter(filter => !this.isDisabled.get(filter.type)); return this.filters.filter(filter => !this.isDisabled.get(filter.type));
} }
constructor() { constructor(protected readonly dependencies: Dependencies) {
makeObservable(this); makeObservable(this);
autoBind(this); autoBind(this);
@ -33,8 +37,8 @@ export class PageFiltersStore {
protected syncWithGlobalSearch() { protected syncWithGlobalSearch() {
const disposers = [ const disposers = [
reaction(() => this.getValues(FilterType.SEARCH)[0], search => searchUrlParam.set(search)), reaction(() => this.getValues(FilterType.SEARCH)[0], search => this.dependencies.searchUrlParam.set(search)),
reaction(() => searchUrlParam.get(), search => { reaction(() => this.dependencies.searchUrlParam.get(), search => {
const filter = this.getByType(FilterType.SEARCH); const filter = this.getByType(FilterType.SEARCH);
if (filter) { if (filter) {
@ -102,12 +106,10 @@ export class PageFiltersStore {
@action @action
reset() { reset() {
if (this.isEnabled(FilterType.SEARCH)) { if (this.isEnabled(FilterType.SEARCH)) {
searchUrlParam.clear(); this.dependencies.searchUrlParam.clear();
} }
this.filters.length = 0; this.filters.length = 0;
this.isDisabled.clear(); this.isDisabled.clear();
} }
} }
export const pageFilters = new PageFiltersStore();

View File

@ -11,7 +11,7 @@ import getDetailsUrlInjectable from "./get-details-url.injectable";
* @param resetSelected If true then will reset the selected kube object (which object is highlighted generally) * @param resetSelected If true then will reset the selected kube object (which object is highlighted generally)
* @default resetSelected true * @default resetSelected true
*/ */
export type ShowDetails = (selfLink: string, resetSelected?: boolean) => void; export type ShowDetails = (selfLink: string | undefined, resetSelected?: boolean) => void;
const showDetailsInjectable = getInjectable({ const showDetailsInjectable = getInjectable({
id: "show-details", id: "show-details",
@ -19,7 +19,7 @@ const showDetailsInjectable = getInjectable({
const observableHistory = di.inject(observableHistoryInjectable); const observableHistory = di.inject(observableHistoryInjectable);
const getDetailsUrl = di.inject(getDetailsUrlInjectable); const getDetailsUrl = di.inject(getDetailsUrlInjectable);
return (selfLink, resetSelected = true) => { return (selfLink = "", resetSelected = true) => {
observableHistory.merge({ observableHistory.merge({
search: getDetailsUrl(selfLink, resetSelected), search: getDetailsUrl(selfLink, resetSelected),
}); });

View File

@ -2,13 +2,16 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getActiveClusterEntity } from "../../../api/catalog-entity-registry";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import getActiveClusterEntityInjectable from "../../../api/catalog/entity/get-active-cluster-entity.injectable";
const clusterInjectable = getInjectable({ const clusterInjectable = getInjectable({
id: "cluster", id: "cluster",
instantiate: () => getActiveClusterEntity(), instantiate: (di) => {
const getActiveClusterEntity = di.inject(getActiveClusterEntityInjectable);
return getActiveClusterEntity();
},
lifecycle: lifecycleEnum.transient, lifecycle: lifecycleEnum.transient,
}); });

View File

@ -14,7 +14,6 @@ import asyncFn from "@async-fn/jest";
import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import clusterInjectable from "./dependencies/cluster.injectable"; import clusterInjectable from "./dependencies/cluster.injectable";
import hideDetailsInjectable from "./dependencies/hide-details.injectable";
import type { DiRender } from "../test-utils/renderFor"; import type { DiRender } from "../test-utils/renderFor";
import { renderFor } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor";
import type { Cluster } from "../../../common/cluster/cluster"; import type { Cluster } from "../../../common/cluster/cluster";
@ -26,6 +25,7 @@ import { computed } from "mobx";
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable"; import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
import createEditResourceTabInjectable from "../dock/edit-resource/edit-resource-tab.injectable"; import createEditResourceTabInjectable from "../dock/edit-resource/edit-resource-tab.injectable";
import hideDetailsInjectable from "../kube-detail-params/hide-details.injectable";
// TODO: Make tooltips free of side effects by making it deterministic // TODO: Make tooltips free of side effects by making it deterministic
jest.mock("../tooltip"); jest.mock("../tooltip");

View File

@ -9,20 +9,25 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { SidebarItem } from "./sidebar-item"; import { SidebarItem } from "./sidebar-item";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import { SidebarCluster } from "./sidebar-cluster"; import { SidebarCluster } from "./sidebar-cluster";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; import type { HierarchicalSidebarItem } from "./sidebar-items.injectable";
import sidebarItemsInjectable from "./sidebar-items.injectable"; import sidebarItemsInjectable from "./sidebar-items.injectable";
import type { IComputedValue } from "mobx"; import type { IComputedValue } from "mobx";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
interface Dependencies { interface Dependencies {
sidebarItems: IComputedValue<HierarchicalSidebarItem[]>; sidebarItems: IComputedValue<HierarchicalSidebarItem[]>;
entityRegistry: CatalogEntityRegistry;
} }
export const NonInjectedSidebar = observer(({ sidebarItems }: Dependencies) => ( export const NonInjectedSidebar = observer(({
sidebarItems,
entityRegistry,
}: Dependencies) => (
<div className={cssNames("flex flex-col")} data-testid="cluster-sidebar"> <div className={cssNames("flex flex-col")} data-testid="cluster-sidebar">
<SidebarCluster clusterEntity={catalogEntityRegistry.activeEntity} /> <SidebarCluster clusterEntity={entityRegistry.activeEntity} />
<div className={`${styles.sidebarNav} sidebar-active-status`}> <div className={`${styles.sidebarNav} sidebar-active-status`}>
{ {
@ -42,6 +47,7 @@ export const Sidebar = withInjectables<Dependencies>(NonInjectedSidebar, {
getProps: (di, props) => ({ getProps: (di, props) => ({
...props, ...props,
sidebarItems: di.inject(sidebarItemsInjectable), sidebarItems: di.inject(sidebarItemsInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}), }),
}); });

View File

@ -0,0 +1,19 @@
/**
* 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 createPageParamInjectable from "../../navigation/create-page-param.injectable";
const orderByUrlParamInjectable = getInjectable({
id: "order-by-url-param",
instantiate: (di) => {
const createPageParam = di.inject(createPageParamInjectable);
return createPageParam({
name: "order",
});
},
});
export default orderByUrlParamInjectable;

View File

@ -0,0 +1,19 @@
/**
* 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 createPageParamInjectable from "../../navigation/create-page-param.injectable";
const sortByUrlParamInjectable = getInjectable({
id: "sort-by-url-param",
instantiate: (di) => {
const createPageParam = di.inject(createPageParamInjectable);
return createPageParam({
name: "sort",
});
},
});
export default sortByUrlParamInjectable;

View File

@ -14,7 +14,7 @@ import type { TableHeadElem } from "./table-head";
import { TableHead } from "./table-head"; import { TableHead } from "./table-head";
import type { TableCellElem } from "./table-cell"; import type { TableCellElem } from "./table-cell";
import { VirtualList } from "../virtual-list"; import { VirtualList } from "../virtual-list";
import { createPageParam } from "../../navigation"; import type { PageParam } from "../../navigation";
import { computed, makeObservable } from "mobx"; import { computed, makeObservable } from "mobx";
import { getSorted } from "./sorting"; import { getSorted } from "./sorting";
import type { TableModel } from "./table-model/table-model"; import type { TableModel } from "./table-model/table-model";
@ -22,6 +22,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import tableModelInjectable from "./table-model/table-model.injectable"; import tableModelInjectable from "./table-model/table-model.injectable";
import type { ItemObject } from "../../../common/item.store"; import type { ItemObject } from "../../../common/item.store";
import assert from "assert"; import assert from "assert";
import orderByUrlParamInjectable from "./order-by-url-param.injectable";
import sortByUrlParamInjectable from "./sort-by-url-param.injectable";
export type TableSortBy = string; export type TableSortBy = string;
export type TableOrderBy = "asc" | "desc"; export type TableOrderBy = "asc" | "desc";
@ -75,16 +77,10 @@ export interface TableProps<Item> extends React.DOMAttributes<HTMLDivElement> {
renderRow?: (item: Item) => React.ReactElement<TableRowProps<Item>> | undefined | null; renderRow?: (item: Item) => React.ReactElement<TableRowProps<Item>> | undefined | null;
} }
export const sortByUrlParam = createPageParam({
name: "sort",
});
export const orderByUrlParam = createPageParam({
name: "order",
});
interface Dependencies { interface Dependencies {
model: TableModel; model: TableModel;
sortByUrlParam: PageParam<string>;
orderByUrlParam: PageParam<string>;
} }
@observer @observer
@ -178,8 +174,8 @@ class NonInjectedTable<Item extends ItemObject> extends React.Component<TablePro
const { sortSyncWithUrl, onSort } = this.props; const { sortSyncWithUrl, onSort } = this.props;
if (sortSyncWithUrl) { if (sortSyncWithUrl) {
sortByUrlParam.set(sortBy); this.props.sortByUrlParam.set(sortBy);
orderByUrlParam.set(orderBy); this.props.orderByUrlParam.set(orderBy);
} }
onSort?.({ sortBy, orderBy }); onSort?.({ sortBy, orderBy });
@ -269,18 +265,12 @@ class NonInjectedTable<Item extends ItemObject> extends React.Component<TablePro
} }
} }
const InjectedTable = withInjectables<Dependencies, TableProps<any>>( export const Table = withInjectables<Dependencies, TableProps<ItemObject>>(NonInjectedTable, {
NonInjectedTable,
{
getProps: (di, props) => ({ getProps: (di, props) => ({
model: di.inject(tableModelInjectable),
...props, ...props,
model: di.inject(tableModelInjectable),
orderByUrlParam: di.inject(orderByUrlParamInjectable),
sortByUrlParam: di.inject(sortByUrlParamInjectable),
}), }),
}, }) as <Item>(props: TableProps<Item>) => React.ReactElement;
);
export function Table<Item>(props: TableProps<Item>) {
return <InjectedTable {...props} />;
}

View File

@ -0,0 +1,38 @@
/**
* 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 catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable";
import { createExtensionInstanceInjectionToken } from "../../extensions/extension-loader/create-extension-instance.token";
import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { LensRendererExtensionDependencies } from "../../extensions/lens-extension-set-dependencies";
import catalogEntityRegistryInjectable from "../api/catalog/entity/registry.injectable";
import getExtensionPageParametersInjectable from "../routes/get-extension-page-parameters.injectable";
import navigateToRouteInjectable from "../routes/navigate-to-route.injectable";
import routesInjectable from "../routes/routes.injectable";
const createExtensionInstanceInjectable = getInjectable({
id: "create-extension-instance",
instantiate: (di) => {
const deps: LensRendererExtensionDependencies = {
categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
getExtensionPageParameters: di.inject(getExtensionPageParametersInjectable),
navigateToRoute: di.inject(navigateToRouteInjectable),
routes: di.inject(routesInjectable),
};
return (ExtensionClass, extension) => {
const instance = new ExtensionClass(extension);
(instance as unknown as { dependencies: LensRendererExtensionDependencies }).dependencies = deps;
return instance;
};
},
injectionToken: createExtensionInstanceInjectionToken,
});
export default createExtensionInstanceInjectable;

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { initClusterFrame } from "./init-cluster-frame"; import { initClusterFrame } from "./init-cluster-frame";
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable"; import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
import catalogEntityRegistryInjectable from "../../../api/catalog-entity-registry/catalog-entity-registry.injectable"; import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
import frameRoutingIdInjectable from "./frame-routing-id/frame-routing-id.injectable"; import frameRoutingIdInjectable from "./frame-routing-id/frame-routing-id.injectable";
import hostedClusterInjectable from "../../../../common/cluster-store/hosted-cluster.injectable"; import hostedClusterInjectable from "../../../../common/cluster-store/hosted-cluster.injectable";
import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { Cluster } from "../../../../common/cluster/cluster"; import type { Cluster } from "../../../../common/cluster/cluster";
import type { CatalogEntityRegistry } from "../../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
import logger from "../../../../main/logger"; import logger from "../../../../main/logger";
import type { KubernetesCluster } from "../../../../common/catalog-entities"; import type { KubernetesCluster } from "../../../../common/catalog-entities";
import { Notifications } from "../../../components/notifications"; import { Notifications } from "../../../components/notifications";
@ -51,7 +51,7 @@ export const initClusterFrame = ({
// Only load the extensions once the catalog has been populated. // Only load the extensions once the catalog has been populated.
// Note that the Catalog might still have unprocessed entities until the extensions are fully loaded. // Note that the Catalog might still have unprocessed entities until the extensions are fully loaded.
when( when(
() => catalogEntityRegistry.items.length > 0, () => catalogEntityRegistry.items.get().length > 0,
() => () =>
loadExtensions(() => catalogEntityRegistry.activeEntity as KubernetesCluster), loadExtensions(() => catalogEntityRegistry.activeEntity as KubernetesCluster),
{ {

View File

@ -8,33 +8,19 @@ import extensionLoaderInjectable from "../../../../extensions/extension-loader/e
import ipcRendererInjectable from "../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable"; import ipcRendererInjectable from "../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
import bindProtocolAddRouteHandlersInjectable from "../../../protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable"; import bindProtocolAddRouteHandlersInjectable from "../../../protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable";
import lensProtocolRouterRendererInjectable from "../../../protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable"; import lensProtocolRouterRendererInjectable from "../../../protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable";
import catalogEntityRegistryInjectable from "../../../api/catalog-entity-registry/catalog-entity-registry.injectable"; import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable";
import registerIpcListenersInjectable from "../../../ipc/register-ipc-listeners.injectable"; import registerIpcListenersInjectable from "../../../ipc/register-ipc-listeners.injectable";
const initRootFrameInjectable = getInjectable({ const initRootFrameInjectable = getInjectable({
id: "init-root-frame", id: "init-root-frame",
instantiate: (di) => initRootFrame({
instantiate: (di) => { loadExtensions: di.inject(extensionLoaderInjectable).loadOnClusterManagerRenderer,
const extensionLoader = di.inject(extensionLoaderInjectable);
return initRootFrame({
loadExtensions: extensionLoader.loadOnClusterManagerRenderer,
registerIpcListeners: di.inject(registerIpcListenersInjectable), registerIpcListeners: di.inject(registerIpcListenersInjectable),
ipcRenderer: di.inject(ipcRendererInjectable), ipcRenderer: di.inject(ipcRendererInjectable),
bindProtocolAddRouteHandlers: di.inject(bindProtocolAddRouteHandlersInjectable),
bindProtocolAddRouteHandlers: di.inject( lensProtocolRouterRenderer: di.inject(lensProtocolRouterRendererInjectable),
bindProtocolAddRouteHandlersInjectable,
),
lensProtocolRouterRenderer: di.inject(
lensProtocolRouterRendererInjectable,
),
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable), catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
}); }),
},
}); });
export default initRootFrameInjectable; export default initRootFrameInjectable;

View File

@ -6,7 +6,7 @@ import { delay } from "../../../../common/utils";
import { broadcastMessage } from "../../../../common/ipc"; import { broadcastMessage } from "../../../../common/ipc";
import logger from "../../../../common/logger"; import logger from "../../../../common/logger";
import type { ExtensionLoading } from "../../../../extensions/extension-loader"; import type { ExtensionLoading } from "../../../../extensions/extension-loader";
import type { CatalogEntityRegistry } from "../../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry";
import { bundledExtensionsLoaded } from "../../../../common/ipc/extension-handling"; import { bundledExtensionsLoaded } from "../../../../common/ipc/extension-handling";

View File

@ -15,8 +15,8 @@ import registerIpcChannelListenerInjectable from "./app-paths/get-value-from-reg
import focusWindowInjectable from "./ipc-channel-listeners/focus-window.injectable"; import focusWindowInjectable from "./ipc-channel-listeners/focus-window.injectable";
import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable"; import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable";
import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store"; import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store";
import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable"; import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { FileSystemProvisionerStore } from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store"; import type { FileSystemProvisionerStore } from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable"; import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
import type { ClusterStore } from "../common/cluster-store/cluster-store"; import type { ClusterStore } from "../common/cluster-store/cluster-store";
import type { Cluster } from "../common/cluster/cluster"; import type { Cluster } from "../common/cluster/cluster";

View File

@ -5,7 +5,6 @@
import { asLegacyGlobalFunctionForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; import { asLegacyGlobalFunctionForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import createPageParamInjectable from "./create-page-param.injectable";
import matchRouteInjectable from "./match-route.injectable"; import matchRouteInjectable from "./match-route.injectable";
import navigateInjectable from "./navigate.injectable"; import navigateInjectable from "./navigate.injectable";
import observableHistoryInjectable from "./observable-history.injectable"; import observableHistoryInjectable from "./observable-history.injectable";
@ -22,11 +21,6 @@ export const navigation = asLegacyGlobalForExtensionApi(observableHistoryInjecta
*/ */
export const navigate = asLegacyGlobalFunctionForExtensionApi(navigateInjectable); export const navigate = asLegacyGlobalFunctionForExtensionApi(navigateInjectable);
/**
* @deprecated use `di.inject(createPageParamInjectable)` instead
*/
export const createPageParam = asLegacyGlobalFunctionForExtensionApi(createPageParamInjectable);
/** /**
* @deprecated use `di.inject(matchRouteInjectable)` instead * @deprecated use `di.inject(matchRouteInjectable)` instead
*/ */

View File

@ -12,24 +12,21 @@ import navigateToExtensionsInjectable from "../../../common/front-end-routing/ro
import navigateToEntitySettingsInjectable from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; import navigateToEntitySettingsInjectable from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
import navigateToClusterViewInjectable from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable"; import navigateToClusterViewInjectable from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable";
import navigateToPreferenceTabIdInjectable from "./navigate-to-preference-tab-id.injectable"; import navigateToPreferenceTabIdInjectable from "./navigate-to-preference-tab-id.injectable";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
const bindProtocolAddRouteHandlersInjectable = getInjectable({ const bindProtocolAddRouteHandlersInjectable = getInjectable({
id: "bind-protocol-add-route-handlers", id: "bind-protocol-add-route-handlers",
instantiate: (di) => instantiate: (di) => bindProtocolAddRouteHandlers({
bindProtocolAddRouteHandlers({
attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable), attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable),
lensProtocolRouterRenderer: di.inject(lensProtocolRouterRendererInjectable ),
lensProtocolRouterRenderer: di.inject(
lensProtocolRouterRendererInjectable,
),
navigateToCatalog: di.inject(navigateToCatalogInjectable), navigateToCatalog: di.inject(navigateToCatalogInjectable),
navigateToAddCluster: di.inject(navigateToAddClusterInjectable), navigateToAddCluster: di.inject(navigateToAddClusterInjectable),
navigateToExtensions: di.inject(navigateToExtensionsInjectable), navigateToExtensions: di.inject(navigateToExtensionsInjectable),
navigateToEntitySettings: di.inject(navigateToEntitySettingsInjectable), navigateToEntitySettings: di.inject(navigateToEntitySettingsInjectable),
navigateToClusterView: di.inject(navigateToClusterViewInjectable), navigateToClusterView: di.inject(navigateToClusterViewInjectable),
navigateToPreferenceTabId: di.inject(navigateToPreferenceTabIdInjectable), navigateToPreferenceTabId: di.inject(navigateToPreferenceTabIdInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
}), }),
}); });

View File

@ -5,7 +5,7 @@
import React from "react"; import React from "react";
import type { LensProtocolRouterRenderer } from "../lens-protocol-router-renderer/lens-protocol-router-renderer"; import type { LensProtocolRouterRenderer } from "../lens-protocol-router-renderer/lens-protocol-router-renderer";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import { ClusterStore } from "../../../common/cluster-store/cluster-store"; import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import { import {
EXTENSION_NAME_MATCH, EXTENSION_NAME_MATCH,
@ -30,11 +30,20 @@ interface Dependencies {
navigateToEntitySettings: NavigateToEntitySettings; navigateToEntitySettings: NavigateToEntitySettings;
navigateToClusterView: NavigateToClusterView; navigateToClusterView: NavigateToClusterView;
navigateToPreferenceTabId: (tabId: string) => void; navigateToPreferenceTabId: (tabId: string) => void;
entityRegistry: CatalogEntityRegistry;
} }
export const bindProtocolAddRouteHandlers = export const bindProtocolAddRouteHandlers = ({
({ attemptInstallByInfo, lensProtocolRouterRenderer, navigateToCatalog, navigateToAddCluster, navigateToExtensions, navigateToEntitySettings, navigateToClusterView, navigateToPreferenceTabId }: Dependencies) => attemptInstallByInfo,
() => { lensProtocolRouterRenderer,
navigateToCatalog,
navigateToAddCluster,
navigateToExtensions,
navigateToEntitySettings,
navigateToClusterView,
navigateToPreferenceTabId,
entityRegistry,
}: Dependencies) => () => {
lensProtocolRouterRenderer lensProtocolRouterRenderer
.addInternalHandler("/preferences", ({ search: { highlight: tabId }}) => { .addInternalHandler("/preferences", ({ search: { highlight: tabId }}) => {
if (tabId) { if (tabId) {
@ -60,20 +69,15 @@ export const bindProtocolAddRouteHandlers =
.addInternalHandler("/landing", () => { .addInternalHandler("/landing", () => {
navigateToCatalog(); navigateToCatalog();
}) })
.addInternalHandler( .addInternalHandler("/landing/view/:group/:kind", ({ pathname: { group, kind }}) => {
"/landing/view/:group/:kind",
({ pathname: { group, kind }}) => {
navigateToCatalog({ group, kind }); navigateToCatalog({ group, kind });
}, })
)
.addInternalHandler("/cluster", () => { .addInternalHandler("/cluster", () => {
navigateToAddCluster(); navigateToAddCluster();
}) })
.addInternalHandler( .addInternalHandler("/entity/:entityId/settings", ({ pathname: { entityId }}) => {
"/entity/:entityId/settings",
({ pathname: { entityId }}) => {
assert(entityId); assert(entityId);
const entity = catalogEntityRegistry.getById(entityId); const entity = entityRegistry.getById(entityId);
if (entity) { if (entity) {
navigateToEntitySettings(entityId); navigateToEntitySettings(entityId);
@ -86,12 +90,9 @@ export const bindProtocolAddRouteHandlers =
</p>, </p>,
); );
} }
}, })
)
// Handlers below are deprecated and only kept for backward compact purposes // Handlers below are deprecated and only kept for backward compact purposes
.addInternalHandler( .addInternalHandler("/cluster/:clusterId", ({ pathname: { clusterId }}) => {
"/cluster/:clusterId",
({ pathname: { clusterId }}) => {
assert(clusterId); assert(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId); const cluster = ClusterStore.getInstance().getById(clusterId);
@ -106,11 +107,8 @@ export const bindProtocolAddRouteHandlers =
</p>, </p>,
); );
} }
}, })
) .addInternalHandler("/cluster/:clusterId/settings", ({ pathname: { clusterId }}) => {
.addInternalHandler(
"/cluster/:clusterId/settings",
({ pathname: { clusterId }}) => {
assert(clusterId); assert(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId); const cluster = ClusterStore.getInstance().getById(clusterId);
@ -125,14 +123,11 @@ export const bindProtocolAddRouteHandlers =
</p>, </p>,
); );
} }
}, })
)
.addInternalHandler("/extensions", () => { .addInternalHandler("/extensions", () => {
navigateToExtensions(); navigateToExtensions();
}) })
.addInternalHandler( .addInternalHandler(`/extensions/install${LensProtocolRouter.ExtensionUrlSchema}`, ({ pathname, search: { version }}) => {
`/extensions/install${LensProtocolRouter.ExtensionUrlSchema}`,
({ pathname, search: { version }}) => {
const name = [ const name = [
pathname[EXTENSION_PUBLISHER_MATCH], pathname[EXTENSION_PUBLISHER_MATCH],
pathname[EXTENSION_NAME_MATCH], pathname[EXTENSION_NAME_MATCH],
@ -142,6 +137,5 @@ export const bindProtocolAddRouteHandlers =
navigateToExtensions(); navigateToExtensions();
attemptInstallByInfo({ name, version, requireConfirmation: true }); attemptInstallByInfo({ name, version, requireConfirmation: true });
}, });
); };
};

View File

@ -7,10 +7,11 @@ import { pipeline } from "@ogre-tools/fp";
import type { PageParamInit } from "../navigation"; import type { PageParamInit } from "../navigation";
import type { LensRendererExtension } from "../../extensions/lens-renderer-extension"; import type { LensRendererExtension } from "../../extensions/lens-renderer-extension";
import type { PageRegistration } from "../../extensions/registries"; import type { PageRegistration } from "../../extensions/registries";
import { fromPairs, map } from "lodash/fp"; import { map } from "lodash/fp";
import createPageParamInjectable from "../navigation/create-page-param.injectable"; import createPageParamInjectable from "../navigation/create-page-param.injectable";
import { object } from "../utils";
interface InstantiationParameter { export interface ExtensionPageParametersInstantiationParam {
extension: LensRendererExtension; extension: LensRendererExtension;
registration: PageRegistration; registration: PageRegistration;
} }
@ -18,7 +19,7 @@ interface InstantiationParameter {
const extensionPageParametersInjectable = getInjectable({ const extensionPageParametersInjectable = getInjectable({
id: "extension-page-parameters", id: "extension-page-parameters",
instantiate: (di, { registration }: InstantiationParameter) => { instantiate: (di, { registration }: ExtensionPageParametersInstantiationParam) => {
const createPageParam = di.inject(createPageParamInjectable); const createPageParam = di.inject(createPageParamInjectable);
return pipeline( return pipeline(
@ -30,15 +31,15 @@ const extensionPageParametersInjectable = getInjectable({
? convertStringToPageParamInit(key, value) ? convertStringToPageParamInit(key, value)
: convertPartialPageParamInitToFull(key, value), : convertPartialPageParamInitToFull(key, value),
]), ]),
map(([key, value]) => [key, createPageParam(value)]), map(([key, value]) => [key, createPageParam(value)] as const),
fromPairs, object.fromEntries,
); );
}, },
lifecycle: lifecycleEnum.keyedSingleton({ lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: ( getInstanceKey: (
di, di,
{ extension, registration }: InstantiationParameter, { extension, registration }: ExtensionPageParametersInstantiationParam,
) => `${extension.sanitizedExtensionId}-${registration?.id}`, ) => `${extension.sanitizedExtensionId}-${registration?.id}`,
}), }),
}); });

Some files were not shown because too many files have changed in this diff Show More