mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
make Pages and PageMenus observable and reactive
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
e718b250cc
commit
1e2069466a
@ -1,6 +1,6 @@
|
|||||||
// Lens-extensions api developer's kit
|
// Lens-extensions api developer's kit
|
||||||
export * from "../lens-main-extension";
|
export { LensMainExtension } from "../lens-main-extension";
|
||||||
export * from "../lens-renderer-extension";
|
export { LensRendererExtension } from "../lens-renderer-extension";
|
||||||
|
|
||||||
// APIs
|
// APIs
|
||||||
import * as App from "./app";
|
import * as App from "./app";
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import type { LensRendererExtension } from "./lens-renderer-extension";
|
|||||||
import * as registries from "./registries";
|
import * as registries from "./registries";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
|
|
||||||
export function extensionPackagesRoot() {
|
export function extensionPackagesRoot() {
|
||||||
return path.join((app || remote.app).getPath("userData"));
|
return path.join((app || remote.app).getPath("userData"));
|
||||||
}
|
}
|
||||||
@ -66,10 +65,22 @@ export class ExtensionLoader {
|
|||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get allEnabledInstances(): LensExtension[] {
|
||||||
|
const res: LensExtension[] = [];
|
||||||
|
|
||||||
|
for (const [extId, ext] of this.instances) {
|
||||||
|
if (this.extensions.get(extId).isEnabled) {
|
||||||
|
res.push(ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
getExtensionByName(name: string): LensExtension | null {
|
getExtensionByName(name: string): LensExtension | null {
|
||||||
for (const [, val] of this.instances) {
|
for (const [extId, ext] of this.instances) {
|
||||||
if (val.name === name) {
|
if (ext.name === name && this.extensions.get(extId).isEnabled) {
|
||||||
return val;
|
return ext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,8 +221,6 @@ export class ExtensionLoader {
|
|||||||
logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
||||||
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||||
const removeItems = [
|
const removeItems = [
|
||||||
registries.globalPageRegistry.add(extension.globalPages, extension),
|
|
||||||
registries.globalPageMenuRegistry.add(extension.globalPageMenus, extension),
|
|
||||||
registries.appPreferenceRegistry.add(extension.appPreferences),
|
registries.appPreferenceRegistry.add(extension.appPreferences),
|
||||||
registries.clusterFeatureRegistry.add(extension.clusterFeatures),
|
registries.clusterFeatureRegistry.add(extension.clusterFeatures),
|
||||||
registries.statusBarRegistry.add(extension.statusBarItems),
|
registries.statusBarRegistry.add(extension.statusBarItems),
|
||||||
@ -240,8 +249,6 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const removeItems = [
|
const removeItems = [
|
||||||
registries.clusterPageRegistry.add(extension.clusterPages, extension),
|
|
||||||
registries.clusterPageMenuRegistry.add(extension.clusterPageMenus, extension),
|
|
||||||
registries.kubeObjectMenuRegistry.add(extension.kubeObjectMenuItems),
|
registries.kubeObjectMenuRegistry.add(extension.kubeObjectMenuItems),
|
||||||
registries.kubeObjectDetailRegistry.add(extension.kubeObjectDetailItems),
|
registries.kubeObjectDetailRegistry.add(extension.kubeObjectDetailItems),
|
||||||
registries.kubeObjectStatusRegistry.add(extension.kubeObjectStatusTexts),
|
registries.kubeObjectStatusRegistry.add(extension.kubeObjectStatusTexts),
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export class LensMainExtension extends LensExtension {
|
|||||||
async navigate<P extends object>(pageId?: string, params?: P, frameId?: number) {
|
async navigate<P extends object>(pageId?: string, params?: P, frameId?: number) {
|
||||||
const windowManager = WindowManager.getInstance<WindowManager>();
|
const windowManager = WindowManager.getInstance<WindowManager>();
|
||||||
const pageUrl = getExtensionPageUrl({
|
const pageUrl = getExtensionPageUrl({
|
||||||
extensionId: this.name,
|
extensionName: this.name,
|
||||||
pageId,
|
pageId,
|
||||||
params: params ?? {}, // compile to url with params
|
params: params ?? {}, // compile to url with params
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,14 +1,56 @@
|
|||||||
import type { AppPreferenceRegistration, ClusterFeatureRegistration, ClusterPageMenuRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries";
|
import { AppPreferenceRegistration, ClusterFeatureRegistration, ClusterPageMenuRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, getRegisteredPage, Registrable, StatusBarRegistration, recitfyRegisterable, getRegisteredPageMenu, } from "./registries";
|
||||||
import type { Cluster } from "../main/cluster";
|
import type { Cluster } from "../main/cluster";
|
||||||
import { LensExtension } from "./lens-extension";
|
import { LensExtension } from "./lens-extension";
|
||||||
import { getExtensionPageUrl } from "./registries/page-registry";
|
import { getExtensionPageUrl } from "./registries/page-registry";
|
||||||
import { CommandRegistration } from "./registries/command-registry";
|
import { CommandRegistration } from "./registries/command-registry";
|
||||||
|
import { computed, observable } from "mobx";
|
||||||
|
import { getHostedCluster } from "../common/cluster-store";
|
||||||
|
|
||||||
|
export const registeredClusterPages = Symbol("registeredClusterPages");
|
||||||
|
export const registeredGlobalPages = Symbol("registeredGlobalPages");
|
||||||
|
export const registeredGlobalPageMenus = Symbol("registeredGlobalPageMenus");
|
||||||
|
export const registeredClusterPageMenus = Symbol("registeredClusterPageMenus");
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
globalPages: PageRegistration[] = [];
|
#privateGetters = {
|
||||||
clusterPages: PageRegistration[] = [];
|
[registeredGlobalPages]: computed(() => (
|
||||||
globalPageMenus: PageMenuRegistration[] = [];
|
recitfyRegisterable(this.globalPages)
|
||||||
clusterPageMenus: ClusterPageMenuRegistration[] = [];
|
.map(page => getRegisteredPage(page, this.name))
|
||||||
|
)),
|
||||||
|
[registeredClusterPages]: computed(() => (
|
||||||
|
recitfyRegisterable(this.clusterPages, getHostedCluster)
|
||||||
|
.map(page => getRegisteredPage(page, this.name))
|
||||||
|
)),
|
||||||
|
[registeredGlobalPageMenus]: computed(() => (
|
||||||
|
recitfyRegisterable(this.globalPageMenus)
|
||||||
|
.map(pageMenu => getRegisteredPageMenu(pageMenu, this.name))
|
||||||
|
)),
|
||||||
|
[registeredClusterPageMenus]: computed(() => (
|
||||||
|
recitfyRegisterable(this.clusterPageMenus, getHostedCluster)
|
||||||
|
.map(pageMenu => getRegisteredPageMenu(pageMenu, this.name))
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
@observable globalPages: Registrable<PageRegistration> = [];
|
||||||
|
get [registeredGlobalPages]() {
|
||||||
|
return this.#privateGetters[registeredGlobalPages].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable clusterPages: Registrable<PageRegistration> = [];
|
||||||
|
get [registeredClusterPages]() {
|
||||||
|
return this.#privateGetters[registeredClusterPages].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable globalPageMenus: Registrable<PageMenuRegistration> = [];
|
||||||
|
get [registeredGlobalPageMenus]() {
|
||||||
|
return this.#privateGetters[registeredGlobalPageMenus].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable clusterPageMenus: Registrable<ClusterPageMenuRegistration> = [];
|
||||||
|
get [registeredClusterPageMenus]() {
|
||||||
|
return this.#privateGetters[registeredClusterPageMenus].get();
|
||||||
|
}
|
||||||
|
|
||||||
kubeObjectStatusTexts: KubeObjectStatusRegistration[] = [];
|
kubeObjectStatusTexts: KubeObjectStatusRegistration[] = [];
|
||||||
appPreferences: AppPreferenceRegistration[] = [];
|
appPreferences: AppPreferenceRegistration[] = [];
|
||||||
clusterFeatures: ClusterFeatureRegistration[] = [];
|
clusterFeatures: ClusterFeatureRegistration[] = [];
|
||||||
@ -20,7 +62,7 @@ export class LensRendererExtension extends LensExtension {
|
|||||||
async navigate<P extends object>(pageId?: string, params?: P) {
|
async navigate<P extends object>(pageId?: string, params?: P) {
|
||||||
const { navigate } = await import("../renderer/navigation");
|
const { navigate } = await import("../renderer/navigation");
|
||||||
const pageUrl = getExtensionPageUrl({
|
const pageUrl = getExtensionPageUrl({
|
||||||
extensionId: this.name,
|
extensionName: this.name,
|
||||||
pageId,
|
pageId,
|
||||||
params: params ?? {}, // compile to url with params
|
params: params ?? {}, // compile to url with params
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,126 +1,134 @@
|
|||||||
import { getExtensionPageUrl, globalPageRegistry, PageParams } from "../page-registry";
|
import { getExtensionPageUrl, PageParams } from "../page-registry";
|
||||||
import { LensExtension } from "../../lens-extension";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { LensRendererExtension } from "../../core-api";
|
||||||
|
import { findRegisteredPage, PageRegistration } from "..";
|
||||||
|
import { extensionLoader } from "../../extension-loader";
|
||||||
|
|
||||||
let ext: LensExtension = null;
|
jest.mock("../../extension-loader");
|
||||||
|
|
||||||
describe("getPageUrl", () => {
|
describe("getPageUrl", () => {
|
||||||
|
const extensionName = "foo-bar";
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
ext = new LensExtension({
|
jest.spyOn(extensionLoader, "getExtensionByName")
|
||||||
manifest: {
|
.mockImplementation(name => {
|
||||||
name: "foo-bar",
|
if (name !== extensionName) {
|
||||||
version: "0.1.1"
|
return undefined;
|
||||||
},
|
}
|
||||||
id: "/this/is/fake/package.json",
|
|
||||||
absolutePath: "/absolute/fake/",
|
const ext = new LensRendererExtension({
|
||||||
manifestPath: "/this/is/fake/package.json",
|
manifest: {
|
||||||
isBundled: false,
|
name: extensionName,
|
||||||
isEnabled: true
|
version: "0.1.1"
|
||||||
});
|
},
|
||||||
globalPageRegistry.add({
|
id: "/this/is/fake/package.json",
|
||||||
id: "page-with-params",
|
absolutePath: "/absolute/fake/",
|
||||||
components: {
|
manifestPath: "/this/is/fake/package.json",
|
||||||
Page: () => React.createElement("Page with params")
|
isBundled: false,
|
||||||
},
|
isEnabled: true
|
||||||
params: {
|
});
|
||||||
test1: "test1-default",
|
|
||||||
test2: "" // no default value, just declaration
|
(ext.globalPages as PageRegistration[]).push({
|
||||||
},
|
id: "page-with-params",
|
||||||
}, ext);
|
components: {
|
||||||
|
Page: () => React.createElement("Page with params")
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
test1: "test1-default",
|
||||||
|
test2: "" // no default value, just declaration
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return ext;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a page url for extension", () => {
|
it("returns a page url for extension", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar");
|
expect(getExtensionPageUrl({ extensionName })).toBe("/extension/foo-bar");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows to pass base url as parameter", () => {
|
it("allows to pass base url as parameter", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test");
|
expect(getExtensionPageUrl({ extensionName, pageId: "/test" })).toBe("/extension/foo-bar/test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("removes @ and replace `/` to `--`", () => {
|
it("removes @ and replace `/` to `--`", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo--bar");
|
expect(getExtensionPageUrl({ extensionName: "@foo/bar" })).toBe("/extension/foo--bar");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds / prefix", () => {
|
it("adds / prefix", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test");
|
expect(getExtensionPageUrl({ extensionName, pageId: "test" })).toBe("/extension/foo-bar/test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("normalize possible multi-slashes in page.id", () => {
|
it("normalize possible multi-slashes in page.id", () => {
|
||||||
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "//test/" })).toBe("/extension/foo-bar/test");
|
expect(getExtensionPageUrl({ extensionName, pageId: "//test/" })).toBe("/extension/foo-bar/test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets page url with custom params", () => {
|
it("gets page url with custom params", () => {
|
||||||
const params: PageParams<string> = { test1: "one", test2: "2" };
|
const params: PageParams<string> = { test1: "one", test2: "2" };
|
||||||
const searchParams = new URLSearchParams(params);
|
const searchParams = new URLSearchParams(params);
|
||||||
const pageUrl = getExtensionPageUrl({ extensionId: ext.name, pageId: "page-with-params", params });
|
const pageUrl = getExtensionPageUrl({ extensionName, pageId: "page-with-params", params });
|
||||||
|
|
||||||
expect(pageUrl).toBe(`/extension/foo-bar/page-with-params?${searchParams}`);
|
expect(pageUrl).toBe(`/extension/foo-bar/page-with-params?${searchParams}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets page url with default custom params", () => {
|
it("gets page url with default custom params", () => {
|
||||||
const defaultPageUrl = getExtensionPageUrl({ extensionId: ext.name, pageId: "page-with-params", });
|
const defaultPageUrl = getExtensionPageUrl({ extensionName, pageId: "page-with-params", });
|
||||||
|
|
||||||
expect(defaultPageUrl).toBe(`/extension/foo-bar/page-with-params?test1=test1-default`);
|
expect(defaultPageUrl).toBe(`/extension/foo-bar/page-with-params?test1=test1-default`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("globalPageRegistry", () => {
|
describe("globalPageRegistry", () => {
|
||||||
beforeEach(async () => {
|
const extensionName = "@acme/foo-bar";
|
||||||
ext = new LensExtension({
|
const ext = new LensRendererExtension({
|
||||||
manifest: {
|
manifest: {
|
||||||
name: "@acme/foo-bar",
|
name: extensionName,
|
||||||
version: "0.1.1"
|
version: "0.1.1"
|
||||||
},
|
},
|
||||||
id: "/this/is/fake/package.json",
|
id: "/this/is/fake/package.json",
|
||||||
absolutePath: "/absolute/fake/",
|
absolutePath: "/absolute/fake/",
|
||||||
manifestPath: "/this/is/fake/package.json",
|
manifestPath: "/this/is/fake/package.json",
|
||||||
isBundled: false,
|
isBundled: false,
|
||||||
isEnabled: true
|
isEnabled: true
|
||||||
});
|
|
||||||
globalPageRegistry.add([
|
|
||||||
{
|
|
||||||
id: "test-page",
|
|
||||||
components: {
|
|
||||||
Page: () => React.createElement("Text")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "another-page",
|
|
||||||
components: {
|
|
||||||
Page: () => React.createElement("Text")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
components: {
|
|
||||||
Page: () => React.createElement("Default")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
], ext);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getByPageTarget", () => {
|
(ext.globalPages as PageRegistration[]).push(
|
||||||
|
{
|
||||||
|
id: "test-page",
|
||||||
|
components: {
|
||||||
|
Page: () => React.createElement("Text")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "another-page",
|
||||||
|
components: {
|
||||||
|
Page: () => React.createElement("Text")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
components: {
|
||||||
|
Page: () => React.createElement("Default")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("findRegisteredPage", () => {
|
||||||
it("matching to first registered page without id", () => {
|
it("matching to first registered page without id", () => {
|
||||||
const page = globalPageRegistry.getByPageTarget({ extensionId: ext.name });
|
const page = findRegisteredPage(ext);
|
||||||
|
|
||||||
expect(page.id).toEqual(undefined);
|
expect(page.id).toEqual(undefined);
|
||||||
expect(page.extensionId).toEqual(ext.name);
|
expect(page.extensionName).toEqual(ext.name);
|
||||||
expect(page.url).toEqual(getExtensionPageUrl({ extensionId: ext.name }));
|
expect(page.url).toEqual(getExtensionPageUrl({ extensionName }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns matching page", () => {
|
it("returns matching page", () => {
|
||||||
const page = globalPageRegistry.getByPageTarget({
|
const page = findRegisteredPage(ext, "test-page");
|
||||||
pageId: "test-page",
|
|
||||||
extensionId: ext.name
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(page.id).toEqual("test-page");
|
expect(page.id).toEqual("test-page");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns null if target not found", () => {
|
it("returns null if target not found", () => {
|
||||||
const page = globalPageRegistry.getByPageTarget({
|
const page = findRegisteredPage(ext, "wrong-page");
|
||||||
pageId: "wrong-page",
|
|
||||||
extensionId: ext.name
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(page).toBeNull();
|
expect(page).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,8 +20,9 @@ export class BaseRegistry<T, I = T> {
|
|||||||
return () => this.remove(...itemArray);
|
return () => this.remove(...itemArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
|
||||||
protected getRegisteredItem(item: T, extension?: LensExtension): I {
|
protected getRegisteredItem(item: T, extension?: LensExtension): I {
|
||||||
|
void extension;
|
||||||
|
|
||||||
return item as any;
|
return item as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
// All registries managed by extensions api
|
// All registries managed by extensions api
|
||||||
|
|
||||||
|
import { Cluster } from "../../main/cluster";
|
||||||
|
|
||||||
export * from "./page-registry";
|
export * from "./page-registry";
|
||||||
export * from "./page-menu-registry";
|
export * from "./page-menu-registry";
|
||||||
export * from "./menu-registry";
|
export * from "./menu-registry";
|
||||||
@ -10,3 +12,14 @@ export * from "./kube-object-menu-registry";
|
|||||||
export * from "./cluster-feature-registry";
|
export * from "./cluster-feature-registry";
|
||||||
export * from "./kube-object-status-registry";
|
export * from "./kube-object-status-registry";
|
||||||
export * from "./command-registry";
|
export * from "./command-registry";
|
||||||
|
export * from "./sources";
|
||||||
|
|
||||||
|
export type Registrable<T> = (T[]) | ((cluster?: Cluster | null) => T[]);
|
||||||
|
|
||||||
|
export function recitfyRegisterable<T>(src: Registrable<T>, getCluster?: () => Cluster | null | undefined): T[] {
|
||||||
|
if (typeof src === "function") {
|
||||||
|
return src(getCluster());
|
||||||
|
}
|
||||||
|
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|||||||
@ -2,9 +2,14 @@
|
|||||||
import type { IconProps } from "../../renderer/components/icon";
|
import type { IconProps } from "../../renderer/components/icon";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import type { PageTarget, RegisteredPage } from "./page-registry";
|
import type { PageTarget, RegisteredPage } from "./page-registry";
|
||||||
import { action } from "mobx";
|
import { RegisteredPageTarget } from ".";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { LensRendererExtension } from "../core-api";
|
||||||
import { LensExtension } from "../lens-extension";
|
import { extensionLoader } from "../extension-loader";
|
||||||
|
import { registeredClusterPageMenus, registeredGlobalPageMenus } from "../lens-renderer-extension";
|
||||||
|
|
||||||
|
export interface PageMenuComponents {
|
||||||
|
Icon: React.ComponentType<IconProps>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PageMenuRegistration {
|
export interface PageMenuRegistration {
|
||||||
target?: PageTarget;
|
target?: PageTarget;
|
||||||
@ -12,50 +17,57 @@ export interface PageMenuRegistration {
|
|||||||
components: PageMenuComponents;
|
components: PageMenuComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RegisteredPageMenuTarget {
|
||||||
|
target: RegisteredPageTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RegisteredPageMenu = PageMenuRegistration & RegisteredPageMenuTarget;
|
||||||
|
|
||||||
export interface ClusterPageMenuRegistration extends PageMenuRegistration {
|
export interface ClusterPageMenuRegistration extends PageMenuRegistration {
|
||||||
id?: string;
|
id?: string;
|
||||||
parentId?: string;
|
parentId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageMenuComponents {
|
export type RegisteredClusterPageMenu = ClusterPageMenuRegistration & RegisteredPageMenuTarget;
|
||||||
Icon: React.ComponentType<IconProps>;
|
|
||||||
|
export function getRegisteredPageMenu<T extends PageMenuRegistration>({ target: { pageId, params } = {}, ...rest }: T, extensionName: string): T & RegisteredPageMenuTarget {
|
||||||
|
const target: RegisteredPageTarget = {
|
||||||
|
params,
|
||||||
|
pageId,
|
||||||
|
extensionName,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...rest, target } as T & RegisteredPageMenuTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
|
export function getGlobalPageMenus(): RegisteredPageMenu[] {
|
||||||
@action
|
const extensions = extensionLoader.allEnabledInstances as LensRendererExtension[];
|
||||||
add(items: T[], ext: LensExtension) {
|
|
||||||
const normalizedItems = items.map(menuItem => {
|
|
||||||
menuItem.target = {
|
|
||||||
extensionId: ext.name,
|
|
||||||
...(menuItem.target || {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return menuItem;
|
return extensions.flatMap(ext => ext[registeredGlobalPageMenus]);
|
||||||
});
|
|
||||||
|
|
||||||
return super.add(normalizedItems);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClusterPageMenuRegistry extends PageMenuRegistry<ClusterPageMenuRegistration> {
|
function getClusterPageMenus(): RegisteredClusterPageMenu[] {
|
||||||
getRootItems() {
|
const extensions = extensionLoader.allEnabledInstances as LensRendererExtension[];
|
||||||
return this.getItems().filter((item) => !item.parentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSubItems(parent: ClusterPageMenuRegistration) {
|
return extensions.flatMap(ext => ext[registeredClusterPageMenus]);
|
||||||
return this.getItems().filter((item) => (
|
}
|
||||||
item.parentId === parent.id &&
|
|
||||||
item.target.extensionId === parent.target.extensionId
|
export function getRootClusterPageMenus(): RegisteredClusterPageMenu[] {
|
||||||
|
return getClusterPageMenus().filter(pageMenu => !pageMenu.parentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChildClusterPageMenus(parentMenu: RegisteredClusterPageMenu): RegisteredClusterPageMenu[] {
|
||||||
|
return getClusterPageMenus()
|
||||||
|
.filter(pageMenu => (
|
||||||
|
pageMenu.parentId === parentMenu.id
|
||||||
|
&& pageMenu.target.extensionName === parentMenu.target.extensionName
|
||||||
));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
getByPage({ id: pageId, extensionId }: RegisteredPage) {
|
|
||||||
return this.getItems().find((item) => (
|
|
||||||
item.target.pageId == pageId &&
|
|
||||||
item.target.extensionId === extensionId
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const globalPageMenuRegistry = new PageMenuRegistry();
|
export function getClusterPageMenuByPage({ id: pageId, extensionName }: RegisteredPage): RegisteredClusterPageMenu {
|
||||||
export const clusterPageMenuRegistry = new ClusterPageMenuRegistry();
|
return getClusterPageMenus()
|
||||||
|
.find(pageMenu => (
|
||||||
|
pageMenu.target.pageId == pageId
|
||||||
|
&& pageMenu.target.extensionName === extensionName
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|||||||
@ -2,10 +2,15 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { sanitizeExtensionName } from "../lens-extension";
|
||||||
import { LensExtension, sanitizeExtensionName } from "../lens-extension";
|
|
||||||
import { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
|
import { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
|
||||||
import { createPageParam } from "../../renderer/navigation/helpers";
|
import { createPageParam } from "../../renderer/navigation/helpers";
|
||||||
|
import { extensionLoader } from "../extension-loader";
|
||||||
|
import { LensRendererExtension } from "../core-api";
|
||||||
|
import { registeredClusterPages, registeredGlobalPages } from "../lens-renderer-extension";
|
||||||
|
import { TabLayoutRoute } from "../renderer-api/components";
|
||||||
|
import { RegistrationScope } from "./sources";
|
||||||
|
import { getChildClusterPageMenus, RegisteredClusterPageMenu } from "./page-menu-registry";
|
||||||
|
|
||||||
export interface PageRegistration {
|
export interface PageRegistration {
|
||||||
/**
|
/**
|
||||||
@ -24,12 +29,16 @@ export interface PageComponents {
|
|||||||
Page: React.ComponentType<any>;
|
Page: React.ComponentType<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageTarget<P = PageParams> {
|
export interface PageTarget<P extends PageParams = PageParams> {
|
||||||
extensionId?: string;
|
extensionName?: string;
|
||||||
pageId?: string;
|
pageId?: string;
|
||||||
params?: P;
|
params?: P;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RegisteredPageTarget<P extends PageParams = PageParams> extends PageTarget<P> {
|
||||||
|
extensionName: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface PageParams<V = any> {
|
export interface PageParams<V = any> {
|
||||||
[paramName: string]: V;
|
[paramName: string]: V;
|
||||||
}
|
}
|
||||||
@ -42,81 +51,153 @@ export interface PageComponentProps<P extends PageParams = {}> {
|
|||||||
|
|
||||||
export interface RegisteredPage {
|
export interface RegisteredPage {
|
||||||
id: string;
|
id: string;
|
||||||
extensionId: string;
|
extensionName: string;
|
||||||
url: string; // registered extension's page URL (without page params)
|
url: string; // registered extension's page URL (without page params)
|
||||||
params: PageParams<PageParam>; // normalized params
|
params: PageParams<PageParam>; // normalized params
|
||||||
components: PageComponents; // normalized components
|
components: PageComponents; // normalized components
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExtensionPageUrl(target: PageTarget): string {
|
/**
|
||||||
const { extensionId, pageId = "", params: targetParams = {} } = target;
|
* Finds the first registered page on `extension` matching `pageId` in all of `sources`' scopes
|
||||||
|
* @param extension The extension to query for a matching `RegisteredPage`
|
||||||
|
* @param pageId The `PageId` to search for
|
||||||
|
* @param sources Whether to search for global pages or cluster pages or both
|
||||||
|
*/
|
||||||
|
export function findRegisteredPage(extension: LensRendererExtension | undefined, pageId?: string, sources = new Set([RegistrationScope.GLOBAL, RegistrationScope.CLUSTER])): RegisteredPage | null {
|
||||||
|
if (sources.has(RegistrationScope.GLOBAL)) {
|
||||||
|
const page = extension?.[registeredGlobalPages].find(page => page.id === pageId);
|
||||||
|
|
||||||
const pagePath = ["/extension", sanitizeExtensionName(extensionId), pageId]
|
if (page) {
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sources.has(RegistrationScope.CLUSTER)) {
|
||||||
|
const page = extension?.[registeredClusterPages].find(page => page.id === pageId);
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getExtensionPageUrl(target: PageTarget): string {
|
||||||
|
const { extensionName, pageId = "", params: targetParams = {} } = target;
|
||||||
|
|
||||||
|
const pagePath = ["/extension", sanitizeExtensionName(extensionName), pageId]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("/").replace(/\/+/g, "/").replace(/\/$/, ""); // normalize multi-slashes (e.g. coming from page.id)
|
.join("/").replace(/\/+/g, "/").replace(/\/$/, ""); // normalize multi-slashes (e.g. coming from page.id)
|
||||||
|
|
||||||
const pageUrl = new URL(pagePath, `http://localhost`);
|
const pageUrl = new URL(pagePath, `http://localhost`);
|
||||||
|
|
||||||
// stringify params to matched target page
|
// stringify params to matched target page
|
||||||
const registeredPage = globalPageRegistry.getByPageTarget(target) || clusterPageRegistry.getByPageTarget(target);
|
const extension = extensionLoader.getExtensionByName(extensionName);
|
||||||
|
|
||||||
if (registeredPage?.params) {
|
if (extension instanceof LensRendererExtension) {
|
||||||
Object.entries(registeredPage.params).forEach(([name, param]) => {
|
const registeredPage = findRegisteredPage(extension, target.pageId);
|
||||||
const paramValue = param.stringify(targetParams[name]);
|
|
||||||
|
|
||||||
if (param.init.skipEmpty && param.isEmpty(paramValue)) {
|
if (registeredPage?.params) {
|
||||||
pageUrl.searchParams.delete(name);
|
Object.entries(registeredPage.params).forEach(([name, param]) => {
|
||||||
} else {
|
const paramValue = param.stringify(targetParams[name]);
|
||||||
pageUrl.searchParams.set(name, paramValue);
|
|
||||||
}
|
if (param.init.skipEmpty && param.isEmpty(paramValue)) {
|
||||||
});
|
pageUrl.searchParams.delete(name);
|
||||||
|
} else {
|
||||||
|
pageUrl.searchParams.set(name, paramValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return pageUrl.href.replace(pageUrl.origin, "");
|
return pageUrl.href.replace(pageUrl.origin, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage> {
|
function normalizeComponents(components: PageComponents, params?: PageParams<PageParam>): PageComponents {
|
||||||
protected getRegisteredItem(page: PageRegistration, ext: LensExtension): RegisteredPage {
|
if (params) {
|
||||||
const { id: pageId } = page;
|
const { Page } = components;
|
||||||
const extensionId = ext.name;
|
|
||||||
const params = this.normalizeParams(page.params);
|
|
||||||
const components = this.normalizeComponents(page.components, params);
|
|
||||||
const url = getExtensionPageUrl({ extensionId, pageId });
|
|
||||||
|
|
||||||
return {
|
components.Page = observer((props: object) => React.createElement(Page, { params, ...props }));
|
||||||
id: pageId, extensionId, params, components, url,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected normalizeComponents(components: PageComponents, params?: PageParams<PageParam>): PageComponents {
|
return components;
|
||||||
if (params) {
|
}
|
||||||
const { Page } = components;
|
|
||||||
|
|
||||||
components.Page = observer((props: object) => React.createElement(Page, { params, ...props }));
|
function normalizeParams(params?: PageParams<string | ExtensionPageParamInit>): PageParams<PageParam> {
|
||||||
}
|
if (!params) {
|
||||||
|
return;
|
||||||
return components;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected normalizeParams(params?: PageParams<string | ExtensionPageParamInit>): PageParams<PageParam> {
|
Object.entries(params).forEach(([name, value]) => {
|
||||||
if (!params) {
|
const paramInit: PageParamInit = typeof value === "object"
|
||||||
return;
|
? { name, ...value }
|
||||||
}
|
: { name, defaultValue: value };
|
||||||
Object.entries(params).forEach(([name, value]) => {
|
|
||||||
const paramInit: PageParamInit = typeof value === "object"
|
|
||||||
? { name, ...value }
|
|
||||||
: { name, defaultValue: value };
|
|
||||||
|
|
||||||
params[paramInit.name] = createPageParam(paramInit);
|
params[paramInit.name] = createPageParam(paramInit);
|
||||||
});
|
});
|
||||||
|
|
||||||
return params as PageParams<PageParam>;
|
return params as PageParams<PageParam>;
|
||||||
}
|
}
|
||||||
|
|
||||||
getByPageTarget(target: PageTarget): RegisteredPage | null {
|
export function getRegisteredPage({ id, ...page}: PageRegistration, extensionName: string): RegisteredPage {
|
||||||
return this.getItems().find(page => page.extensionId === target.extensionId && page.id === target.pageId) || null;
|
const params = normalizeParams(page.params);
|
||||||
|
const components = normalizeComponents(page.components);
|
||||||
|
|
||||||
|
const pagePath = ["/extension", sanitizeExtensionName(extensionName), id]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("/").replace(/\/+/g, "/").replace(/\/$/, ""); // normalize multi-slashes (e.g. coming from page.id)
|
||||||
|
|
||||||
|
const pageUrl = new URL(pagePath, `http://localhost`);
|
||||||
|
const url = pageUrl.href.replace(pageUrl.origin, "");
|
||||||
|
|
||||||
|
return { id, params, components, extensionName, url };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the `RegisteredPage` of an extension looking through all `sources`
|
||||||
|
* @param target The `extensionName` and `pageId` for the desired page
|
||||||
|
* @param sources Whether to search for global pages or cluster pages or both
|
||||||
|
*/
|
||||||
|
export function getByPageTarget(target?: PageTarget, sources = new Set([RegistrationScope.GLOBAL, RegistrationScope.CLUSTER])): RegisteredPage | null {
|
||||||
|
return findRegisteredPage(
|
||||||
|
extensionLoader.getExtensionByName(target?.extensionName) as LensRendererExtension,
|
||||||
|
target?.pageId,
|
||||||
|
sources,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the registered pages from all extensions
|
||||||
|
* @param source Whether to get all the global or cluster pages
|
||||||
|
*/
|
||||||
|
export function getAllRegisteredPages(source: RegistrationScope): RegisteredPage[] {
|
||||||
|
const extensions = extensionLoader.allEnabledInstances as LensRendererExtension[];
|
||||||
|
|
||||||
|
switch (source) {
|
||||||
|
case RegistrationScope.GLOBAL:
|
||||||
|
return extensions.flatMap(ext => ext[registeredGlobalPages]);
|
||||||
|
case RegistrationScope.CLUSTER:
|
||||||
|
return extensions.flatMap(ext => ext[registeredClusterPages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const globalPageRegistry = new PageRegistry();
|
export function getTabLayoutRoutes(parentMenu: RegisteredClusterPageMenu): TabLayoutRoute[] {
|
||||||
export const clusterPageRegistry = new PageRegistry();
|
if (!parentMenu.id) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return getChildClusterPageMenus(parentMenu)
|
||||||
|
.map(subMenu => [
|
||||||
|
getByPageTarget(subMenu.target),
|
||||||
|
subMenu,
|
||||||
|
] as const)
|
||||||
|
.filter(([subPage]) => subPage)
|
||||||
|
.map(([{ components, extensionName, id: pageId, url }, { title, target: { params } }]) => ({
|
||||||
|
routePath: url,
|
||||||
|
url: getExtensionPageUrl({ extensionName, pageId, params }),
|
||||||
|
title,
|
||||||
|
component: components.Page,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|||||||
7
src/extensions/registries/sources.ts
Normal file
7
src/extensions/registries/sources.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* This represents which sources the RegisteredPages should be searched from
|
||||||
|
*/
|
||||||
|
export enum RegistrationScope {
|
||||||
|
GLOBAL = "global",
|
||||||
|
CLUSTER = "cluster",
|
||||||
|
}
|
||||||
@ -33,14 +33,13 @@ import { Terminal } from "./dock/terminal";
|
|||||||
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
|
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { webFrame } from "electron";
|
import { webFrame } from "electron";
|
||||||
import { clusterPageRegistry, getExtensionPageUrl } from "../../extensions/registries/page-registry";
|
|
||||||
import { extensionLoader } from "../../extensions/extension-loader";
|
import { extensionLoader } from "../../extensions/extension-loader";
|
||||||
import { appEventBus } from "../../common/event-bus";
|
import { appEventBus } from "../../common/event-bus";
|
||||||
import { broadcastMessage, requestMain } from "../../common/ipc";
|
import { broadcastMessage, requestMain } from "../../common/ipc";
|
||||||
import whatInput from "what-input";
|
import whatInput from "what-input";
|
||||||
import { clusterSetFrameIdHandler } from "../../common/cluster-ipc";
|
import { clusterSetFrameIdHandler } from "../../common/cluster-ipc";
|
||||||
import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../extensions/registries";
|
import { getAllRegisteredPages, getByPageTarget, getChildClusterPageMenus, getClusterPageMenuByPage, getExtensionPageUrl, getRootClusterPageMenus, getTabLayoutRoutes, RegisteredClusterPageMenu, RegistrationScope } from "../../extensions/registries";
|
||||||
import { TabLayout, TabLayoutRoute } from "./layout/tab-layout";
|
import { TabLayout } from "./layout/tab-layout";
|
||||||
import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog";
|
import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog";
|
||||||
import { eventStore } from "./+events/event.store";
|
import { eventStore } from "./+events/event.store";
|
||||||
import { nodesStore } from "./+nodes/nodes.store";
|
import { nodesStore } from "./+nodes/nodes.store";
|
||||||
@ -100,38 +99,32 @@ export class App extends React.Component {
|
|||||||
return nodesStore.getWarningsCount() + eventStore.getWarningsCount();
|
return nodesStore.getWarningsCount() + eventStore.getWarningsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
getTabLayoutRoutes(menuItem: ClusterPageMenuRegistration) {
|
getTabLayoutRoutes(menuItem: RegisteredClusterPageMenu) {
|
||||||
const routes: TabLayoutRoute[] = [];
|
|
||||||
|
|
||||||
if (!menuItem.id) {
|
if (!menuItem.id) {
|
||||||
return routes;
|
return [];
|
||||||
}
|
}
|
||||||
clusterPageMenuRegistry.getSubItems(menuItem).forEach((subMenu) => {
|
|
||||||
const page = clusterPageRegistry.getByPageTarget(subMenu.target);
|
|
||||||
|
|
||||||
if (page) {
|
return getChildClusterPageMenus(menuItem)
|
||||||
routes.push({
|
.map(subMenu => [getByPageTarget(subMenu.target), subMenu] as const)
|
||||||
routePath: page.url,
|
.filter(([page]) => page)
|
||||||
url: getExtensionPageUrl(subMenu.target),
|
.map(([page, subMenu]) => ({
|
||||||
title: subMenu.title,
|
routePath: page.url,
|
||||||
component: page.components.Page,
|
url: getExtensionPageUrl(subMenu.target),
|
||||||
});
|
title: subMenu.title,
|
||||||
}
|
component: page.components.Page,
|
||||||
});
|
}));
|
||||||
|
|
||||||
return routes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderExtensionTabLayoutRoutes() {
|
renderExtensionTabLayoutRoutes() {
|
||||||
return clusterPageMenuRegistry.getRootItems().map((menu, index) => {
|
return getRootClusterPageMenus().map((menu, index) => {
|
||||||
const tabRoutes = this.getTabLayoutRoutes(menu);
|
const tabRoutes = getTabLayoutRoutes(menu);
|
||||||
|
|
||||||
if (tabRoutes.length > 0) {
|
if (tabRoutes.length > 0) {
|
||||||
const pageComponent = () => <TabLayout tabs={tabRoutes}/>;
|
const pageComponent = () => <TabLayout tabs={tabRoutes}/>;
|
||||||
|
|
||||||
return <Route key={`extension-tab-layout-route-${index}`} component={pageComponent} path={tabRoutes.map((tab) => tab.routePath)}/>;
|
return <Route key={`extension-tab-layout-route-${index}`} component={pageComponent} path={tabRoutes.map((tab) => tab.routePath)}/>;
|
||||||
} else {
|
} else {
|
||||||
const page = clusterPageRegistry.getByPageTarget(menu.target);
|
const page = getByPageTarget(menu.target, new Set([RegistrationScope.CLUSTER]));
|
||||||
|
|
||||||
if (page) {
|
if (page) {
|
||||||
return <Route key={`extension-tab-layout-route-${index}`} path={page.url} component={page.components.Page}/>;
|
return <Route key={`extension-tab-layout-route-${index}`} path={page.url} component={page.components.Page}/>;
|
||||||
@ -141,8 +134,8 @@ export class App extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderExtensionRoutes() {
|
renderExtensionRoutes() {
|
||||||
return clusterPageRegistry.getItems().map((page, index) => {
|
return getAllRegisteredPages(RegistrationScope.CLUSTER).map((page, index) => {
|
||||||
const menu = clusterPageMenuRegistry.getByPage(page);
|
const menu = getClusterPageMenuByPage(page);
|
||||||
|
|
||||||
if (!menu) {
|
if (!menu) {
|
||||||
return <Route key={`extension-route-${index}`} path={page.url} component={page.components.Page}/>;
|
return <Route key={`extension-route-${index}`} path={page.url} component={page.components.Page}/>;
|
||||||
|
|||||||
@ -14,9 +14,9 @@ import { ClusterSettings, clusterSettingsRoute } from "../+cluster-settings";
|
|||||||
import { clusterViewRoute, clusterViewURL } from "./cluster-view.route";
|
import { clusterViewRoute, clusterViewURL } from "./cluster-view.route";
|
||||||
import { clusterStore } from "../../../common/cluster-store";
|
import { clusterStore } from "../../../common/cluster-store";
|
||||||
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
||||||
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
|
|
||||||
import { Extensions, extensionsRoute } from "../+extensions";
|
import { Extensions, extensionsRoute } from "../+extensions";
|
||||||
import { getMatchedClusterId } from "../../navigation";
|
import { getMatchedClusterId } from "../../navigation";
|
||||||
|
import { getAllRegisteredPages, RegistrationScope } from "../../../extensions/registries";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ClusterManager extends React.Component {
|
export class ClusterManager extends React.Component {
|
||||||
@ -69,7 +69,7 @@ export class ClusterManager extends React.Component {
|
|||||||
<Route component={AddCluster} {...addClusterRoute} />
|
<Route component={AddCluster} {...addClusterRoute} />
|
||||||
<Route component={ClusterView} {...clusterViewRoute} />
|
<Route component={ClusterView} {...clusterViewRoute} />
|
||||||
<Route component={ClusterSettings} {...clusterSettingsRoute} />
|
<Route component={ClusterSettings} {...clusterSettingsRoute} />
|
||||||
{globalPageRegistry.getItems().map(({ url, components: { Page } }) => {
|
{getAllRegisteredPages(RegistrationScope.GLOBAL).map(({ url, components: { Page } }) => {
|
||||||
return <Route key={url} path={url} component={Page}/>;
|
return <Route key={url} path={url} component={Page}/>;
|
||||||
})}
|
})}
|
||||||
<Redirect exact to={this.startUrl}/>
|
<Redirect exact to={this.startUrl}/>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { addClusterURL } from "../+add-cluster";
|
|||||||
import { landingURL } from "../+landing-page";
|
import { landingURL } from "../+landing-page";
|
||||||
import { clusterViewURL } from "./cluster-view.route";
|
import { clusterViewURL } from "./cluster-view.route";
|
||||||
import { ClusterActions } from "./cluster-actions";
|
import { ClusterActions } from "./cluster-actions";
|
||||||
import { getExtensionPageUrl, globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
|
import { getByPageTarget, getExtensionPageUrl, getGlobalPageMenus, RegistrationScope } from "../../../extensions/registries";
|
||||||
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
||||||
import { CommandOverlay } from "../command-palette/command-container";
|
import { CommandOverlay } from "../command-palette/command-container";
|
||||||
import { computed, observable } from "mobx";
|
import { computed, observable } from "mobx";
|
||||||
@ -135,8 +135,8 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
<div className="extensions">
|
<div className="extensions">
|
||||||
{globalPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => {
|
{getGlobalPageMenus().map(({ title, target, components: { Icon } }) => {
|
||||||
const registeredPage = globalPageRegistry.getByPageTarget(target);
|
const registeredPage = getByPageTarget(target, new Set([RegistrationScope.GLOBAL]));
|
||||||
|
|
||||||
if (!registeredPage){
|
if (!registeredPage){
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -27,8 +27,8 @@ import { CustomResources } from "../+custom-resources/custom-resources";
|
|||||||
import { isActiveRoute } from "../../navigation";
|
import { isActiveRoute } from "../../navigation";
|
||||||
import { isAllowedResource } from "../../../common/rbac";
|
import { isAllowedResource } from "../../../common/rbac";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { ClusterPageMenuRegistration, clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries";
|
|
||||||
import { SidebarItem } from "./sidebar-item";
|
import { SidebarItem } from "./sidebar-item";
|
||||||
|
import { getByPageTarget, getExtensionPageUrl, getRootClusterPageMenus, getTabLayoutRoutes, RegistrationScope } from "../../../extensions/registries";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -50,14 +50,12 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
||||||
const submenus: TabLayoutRoute[] = crds.map((crd) => {
|
const submenus: TabLayoutRoute[] = crds.map(crd => ({
|
||||||
return {
|
title: crd.getResourceKind(),
|
||||||
title: crd.getResourceKind(),
|
component: CrdList,
|
||||||
component: CrdList,
|
url: crd.getResourceUrl(),
|
||||||
url: crd.getResourceUrl(),
|
routePath: String(crdResourcesRoute.path),
|
||||||
routePath: String(crdResourcesRoute.path),
|
}));
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
@ -72,43 +70,18 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTabLayoutRoutes(menu: ClusterPageMenuRegistration): TabLayoutRoute[] {
|
|
||||||
const routes: TabLayoutRoute[] = [];
|
|
||||||
|
|
||||||
if (!menu.id) {
|
|
||||||
return routes;
|
|
||||||
}
|
|
||||||
|
|
||||||
clusterPageMenuRegistry.getSubItems(menu).forEach((subMenu) => {
|
|
||||||
const subPage = clusterPageRegistry.getByPageTarget(subMenu.target);
|
|
||||||
|
|
||||||
if (subPage) {
|
|
||||||
const { extensionId, id: pageId } = subPage;
|
|
||||||
|
|
||||||
routes.push({
|
|
||||||
routePath: subPage.url,
|
|
||||||
url: getExtensionPageUrl({ extensionId, pageId, params: subMenu.target.params }),
|
|
||||||
title: subMenu.title,
|
|
||||||
component: subPage.components.Page,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return routes;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRegisteredMenus() {
|
renderRegisteredMenus() {
|
||||||
return clusterPageMenuRegistry.getRootItems().map((menuItem, index) => {
|
return getRootClusterPageMenus().map((menuItem, index) => {
|
||||||
const registeredPage = clusterPageRegistry.getByPageTarget(menuItem.target);
|
const registeredPage = getByPageTarget(menuItem.target, new Set([RegistrationScope.CLUSTER]));
|
||||||
const tabRoutes = this.getTabLayoutRoutes(menuItem);
|
const tabRoutes = getTabLayoutRoutes(menuItem);
|
||||||
const id = `registered-item-${index}`;
|
const id = `registered-item-${index}`;
|
||||||
let pageUrl: string;
|
let pageUrl: string;
|
||||||
let isActive = false;
|
let isActive = false;
|
||||||
|
|
||||||
if (registeredPage) {
|
if (registeredPage) {
|
||||||
const { extensionId, id: pageId } = registeredPage;
|
const { extensionName, id: pageId } = registeredPage;
|
||||||
|
|
||||||
pageUrl = getExtensionPageUrl({ extensionId, pageId, params: menuItem.target.params });
|
pageUrl = getExtensionPageUrl({ extensionName, pageId, params: menuItem.target.params });
|
||||||
isActive = isActiveRoute(registeredPage.url);
|
isActive = isActiveRoute(registeredPage.url);
|
||||||
} else if (tabRoutes.length > 0) {
|
} else if (tabRoutes.length > 0) {
|
||||||
pageUrl = tabRoutes[0].url;
|
pageUrl = tabRoutes[0].url;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user