mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
PageRegistration & BaseRegistry refactoring (#1334)
Signed-off-by: Roman <ixrock@gmail.com> Co-authored-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
6432b3bb9e
commit
faa1cef307
43 files changed
+432
-369
No files matched your search
@@ -1,15 +0,0 @@
|
||||
import React from "react";
|
||||
import { cssNames } from "../renderer/utils";
|
||||
import { TabLayout } from "../renderer/components/layout/tab-layout";
|
||||
import { PageRegistration } from "./registries/page-registry"
|
||||
|
||||
export class DynamicPage extends React.Component<{ page: PageRegistration }> {
|
||||
render() {
|
||||
const { className, components: { Page }, subPages = [] } = this.props.page;
|
||||
return (
|
||||
<TabLayout className={cssNames("ExtensionPage", className)} tabs={subPages}>
|
||||
<Page/>
|
||||
</TabLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -56,30 +56,31 @@ export class ExtensionLoader {
|
||||
|
||||
loadOnMain() {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
||||
this.autoInitExtensions((extension: LensMainExtension) => [
|
||||
registries.menuRegistry.add(...extension.appMenus)
|
||||
this.autoInitExtensions((ext: LensMainExtension) => [
|
||||
registries.menuRegistry.add(ext.appMenus, { key: ext })
|
||||
]);
|
||||
}
|
||||
|
||||
loadOnClusterManagerRenderer() {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
||||
this.autoInitExtensions((extension: LensRendererExtension) => [
|
||||
registries.globalPageRegistry.add(...extension.globalPages),
|
||||
registries.appPreferenceRegistry.add(...extension.appPreferences),
|
||||
registries.clusterFeatureRegistry.add(...extension.clusterFeatures),
|
||||
registries.statusBarRegistry.add(...extension.statusBarItems),
|
||||
this.autoInitExtensions((ext: LensRendererExtension) => [
|
||||
registries.globalPageRegistry.add(ext.globalPages, { key: ext }),
|
||||
registries.globalPageMenuRegistry.add(ext.globalPageMenus, { key: ext }),
|
||||
registries.appPreferenceRegistry.add(ext.appPreferences, { key: ext }),
|
||||
registries.clusterFeatureRegistry.add(ext.clusterFeatures, { key: ext }),
|
||||
registries.statusBarRegistry.add(ext.statusBarItems, { key: ext }),
|
||||
]);
|
||||
}
|
||||
|
||||
loadOnClusterRenderer() {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
||||
this.autoInitExtensions((extension: LensRendererExtension) => [
|
||||
registries.clusterPageRegistry.add(...extension.clusterPages),
|
||||
registries.kubeObjectMenuRegistry.add(...extension.kubeObjectMenuItems),
|
||||
registries.kubeObjectDetailRegistry.add(...extension.kubeObjectDetailItems),
|
||||
registries.kubeObjectStatusRegistry.add(...extension.kubeObjectStatusTexts)
|
||||
this.autoInitExtensions((ext: LensRendererExtension) => [
|
||||
registries.clusterPageRegistry.add(ext.clusterPages, { key: ext }),
|
||||
registries.clusterPageMenuRegistry.add(ext.clusterPageMenus, { key: ext }),
|
||||
registries.kubeObjectMenuRegistry.add(ext.kubeObjectMenuItems, { key: ext }),
|
||||
registries.kubeObjectDetailRegistry.add(ext.kubeObjectDetailItems, { key: ext }),
|
||||
registries.kubeObjectStatusRegistry.add(ext.kubeObjectStatusTexts, { key: ext })
|
||||
])
|
||||
|
||||
}
|
||||
|
||||
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
|
||||
|
||||
@@ -4,4 +4,5 @@ export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from ".
|
||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry"
|
||||
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"
|
||||
export type { PageRegistration, PageComponents } from "../registries/page-registry"
|
||||
export type { PageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry"
|
||||
export type { StatusBarRegistration } from "../registries/status-bar-registry"
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { InstalledExtension } from "./extension-manager";
|
||||
import { action, observable, reaction } from "mobx";
|
||||
import { compile } from "path-to-regexp"
|
||||
import logger from "../main/logger";
|
||||
|
||||
export type LensExtensionId = string; // path to manifest (package.json)
|
||||
@@ -14,6 +15,7 @@ export interface LensExtensionManifest {
|
||||
}
|
||||
|
||||
export class LensExtension {
|
||||
readonly routePrefix = "/extension/:name"
|
||||
readonly manifest: LensExtensionManifest;
|
||||
readonly manifestPath: string;
|
||||
readonly isBundled: boolean;
|
||||
@@ -42,6 +44,14 @@ export class LensExtension {
|
||||
return this.manifest.description
|
||||
}
|
||||
|
||||
getPageUrl(baseUrl = "") {
|
||||
return compile(this.routePrefix)({ name: this.name }) + baseUrl;
|
||||
}
|
||||
|
||||
getPageRoute(baseRoute = "") {
|
||||
return this.routePrefix + baseRoute;
|
||||
}
|
||||
|
||||
@action
|
||||
async enable() {
|
||||
if (this.isEnabled) return;
|
||||
|
||||
@@ -6,7 +6,9 @@ import { WindowManager } from "../main/window-manager";
|
||||
export class LensMainExtension extends LensExtension {
|
||||
@observable.shallow appMenus: MenuRegistration[] = []
|
||||
|
||||
async navigate(location: string, frameId?: number) {
|
||||
await WindowManager.getInstance<WindowManager>().navigate(location, frameId)
|
||||
async navigate(location?: string, frameId?: number) {
|
||||
const windowManager = WindowManager.getInstance<WindowManager>();
|
||||
const url = this.getPageUrl(location); // get full path to extension's page
|
||||
await windowManager.navigate(url, frameId);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
import type {
|
||||
AppPreferenceRegistration, ClusterFeatureRegistration,
|
||||
KubeObjectMenuRegistration, KubeObjectDetailRegistration,
|
||||
PageRegistration, StatusBarRegistration, KubeObjectStatusRegistration
|
||||
} from "./registries"
|
||||
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries"
|
||||
import { observable } from "mobx";
|
||||
import { LensExtension } from "./lens-extension"
|
||||
import { ipcRenderer } from "electron"
|
||||
|
||||
export class LensRendererExtension extends LensExtension {
|
||||
@observable.shallow globalPages: PageRegistration[] = []
|
||||
@observable.shallow clusterPages: PageRegistration[] = []
|
||||
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []
|
||||
@observable.shallow globalPageMenus: PageMenuRegistration[] = []
|
||||
@observable.shallow clusterPageMenus: PageMenuRegistration[] = []
|
||||
@observable.shallow kubeObjectStatusTexts: KubeObjectStatusRegistration[] = []
|
||||
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
|
||||
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
|
||||
@observable.shallow statusBarItems: StatusBarRegistration[] = []
|
||||
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = []
|
||||
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
|
||||
|
||||
navigate(location: string) {
|
||||
ipcRenderer.emit("renderer:navigate", location)
|
||||
async navigate(location?: string) {
|
||||
const { navigate } = await import("../renderer/navigation");
|
||||
navigate(this.getPageUrl(location));
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import type React from "react"
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
|
||||
|
||||
export interface AppPreferenceComponents {
|
||||
Hint: React.ComponentType<any>;
|
||||
Input: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface AppPreferenceRegistration {
|
||||
export interface AppPreferenceRegistration extends BaseRegistryItem {
|
||||
title: string;
|
||||
components: AppPreferenceComponents;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,65 @@
|
||||
// Base class for extensions-api registries
|
||||
import { action, observable } from "mobx";
|
||||
import { LensExtension } from "../lens-extension";
|
||||
import { getRandId } from "../../common/utils";
|
||||
|
||||
export class BaseRegistry<T = any> {
|
||||
protected items = observable<T>([], { deep: false });
|
||||
export type BaseRegistryKey = LensExtension | null;
|
||||
export type BaseRegistryItemId = string | symbol;
|
||||
|
||||
getItems(): T[] {
|
||||
return this.items.toJS();
|
||||
export interface BaseRegistryItem {
|
||||
id?: BaseRegistryItemId; // uniq id, generated automatically when not provided
|
||||
}
|
||||
|
||||
export interface BaseRegistryAddMeta {
|
||||
key?: BaseRegistryKey;
|
||||
merge?: boolean
|
||||
}
|
||||
|
||||
export class BaseRegistry<T extends BaseRegistryItem = any> {
|
||||
private items = observable.map<BaseRegistryKey, T[]>([], { deep: false });
|
||||
|
||||
getItems(): (T & { extension?: LensExtension | null })[] {
|
||||
return Array.from(this.items).map(([ext, items]) => {
|
||||
return items.map(item => ({
|
||||
...item,
|
||||
extension: ext,
|
||||
}))
|
||||
}).flat()
|
||||
}
|
||||
|
||||
getById(itemId: BaseRegistryItemId, key?: BaseRegistryKey): T {
|
||||
const byId = (item: BaseRegistryItem) => item.id === itemId;
|
||||
if (key) {
|
||||
return this.items.get(key)?.find(byId)
|
||||
}
|
||||
return this.getItems().find(byId);
|
||||
}
|
||||
|
||||
@action
|
||||
add(...items: T[]) {
|
||||
this.items.push(...items);
|
||||
return () => this.remove(...items);
|
||||
add(items: T | T[], { key = null, merge = true }: BaseRegistryAddMeta = {}) {
|
||||
const normalizedItems = (Array.isArray(items) ? items : [items]).map((item: T) => {
|
||||
item.id = item.id || getRandId();
|
||||
return item;
|
||||
});
|
||||
if (merge && this.items.has(key)) {
|
||||
const newItems = new Set(this.items.get(key));
|
||||
normalizedItems.forEach(item => newItems.add(item))
|
||||
this.items.set(key, [...newItems]);
|
||||
} else {
|
||||
this.items.set(key, normalizedItems);
|
||||
}
|
||||
return () => this.remove(normalizedItems, key)
|
||||
}
|
||||
|
||||
@action
|
||||
remove(...items: T[]) {
|
||||
items.forEach(item => {
|
||||
this.items.remove(item); // works because of {deep: false};
|
||||
})
|
||||
remove(items: T[], key: BaseRegistryKey = null) {
|
||||
const storedItems = this.items.get(key);
|
||||
if (!storedItems) return;
|
||||
const newItems = storedItems.filter(item => !items.includes(item)); // works because of {deep: false};
|
||||
if (newItems.length > 0) {
|
||||
this.items.set(key, newItems)
|
||||
} else {
|
||||
this.items.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
import type React from "react"
|
||||
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
|
||||
import { ClusterFeature } from "../cluster-feature";
|
||||
|
||||
export interface ClusterFeatureComponents {
|
||||
Description: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface ClusterFeatureRegistration {
|
||||
export interface ClusterFeatureRegistration extends BaseRegistryItem {
|
||||
title: string;
|
||||
components: ClusterFeatureComponents
|
||||
feature: ClusterFeature
|
||||
}
|
||||
|
||||
export class ClusterFeatureRegistry extends BaseRegistry<ClusterFeatureRegistration> {}
|
||||
export class ClusterFeatureRegistry extends BaseRegistry<ClusterFeatureRegistration> {
|
||||
}
|
||||
|
||||
export const clusterFeatureRegistry = new ClusterFeatureRegistry()
|
||||
@@ -1,6 +1,7 @@
|
||||
// All registries managed by extensions api
|
||||
|
||||
export * from "./page-registry"
|
||||
export * from "./page-menu-registry"
|
||||
export * from "./menu-registry"
|
||||
export * from "./app-preference-registry"
|
||||
export * from "./status-bar-registry"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from "react"
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
|
||||
|
||||
export interface KubeObjectDetailComponents {
|
||||
Details: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface KubeObjectDetailRegistration {
|
||||
export interface KubeObjectDetailRegistration extends BaseRegistryItem {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
components: KubeObjectDetailComponents;
|
||||
@@ -14,7 +14,7 @@ export interface KubeObjectDetailRegistration {
|
||||
|
||||
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
|
||||
getItemsForKind(kind: string, apiVersion: string) {
|
||||
const items = this.items.filter((item) => {
|
||||
const items = this.getItems().filter((item) => {
|
||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||
}).map((item) => {
|
||||
if (item.priority === null) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from "react"
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
|
||||
|
||||
export interface KubeObjectMenuComponents {
|
||||
MenuItem: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface KubeObjectMenuRegistration {
|
||||
export interface KubeObjectMenuRegistration extends BaseRegistryItem {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
components: KubeObjectMenuComponents;
|
||||
@@ -13,7 +13,7 @@ export interface KubeObjectMenuRegistration {
|
||||
|
||||
export class KubeObjectMenuRegistry extends BaseRegistry<KubeObjectMenuRegistration> {
|
||||
getItemsForKind(kind: string, apiVersion: string) {
|
||||
return this.items.filter((item) => {
|
||||
return this.getItems().filter((item) => {
|
||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api";
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
|
||||
|
||||
export interface KubeObjectStatusRegistration {
|
||||
export interface KubeObjectStatusRegistration extends BaseRegistryItem {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
resolve: (object: KubeObject) => KubeObjectStatus;
|
||||
@@ -9,7 +9,7 @@ export interface KubeObjectStatusRegistration {
|
||||
|
||||
export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
|
||||
getItemsForKind(kind: string, apiVersion: string) {
|
||||
return this.items.filter((item) => {
|
||||
return this.getItems().filter((item) => {
|
||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Extensions-api -> Register page menu items
|
||||
|
||||
import type React from "react";
|
||||
import type { IconProps } from "../../renderer/components/icon";
|
||||
import { BaseRegistry, BaseRegistryItem, BaseRegistryItemId } from "./base-registry";
|
||||
|
||||
export interface PageMenuRegistration extends BaseRegistryItem {
|
||||
id: BaseRegistryItemId; // required id from page-registry item to match with
|
||||
url?: string; // when not provided initial extension's path used, e.g. "/extension/lens-extension-name"
|
||||
title: React.ReactNode;
|
||||
components: PageMenuComponents;
|
||||
subMenus?: PageSubMenuRegistration[];
|
||||
}
|
||||
|
||||
export interface PageSubMenuRegistration {
|
||||
url: string;
|
||||
title: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface PageMenuComponents {
|
||||
Icon: React.ComponentType<IconProps>;
|
||||
}
|
||||
|
||||
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
|
||||
getItems() {
|
||||
return super.getItems().map(item => {
|
||||
item.url = item.extension.getPageUrl(item.url)
|
||||
return item
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const globalPageMenuRegistry = new PageMenuRegistry<Omit<PageMenuRegistration, "subMenus">>();
|
||||
export const clusterPageMenuRegistry = new PageMenuRegistry();
|
||||
@@ -1,31 +1,33 @@
|
||||
// Extensions-api -> Custom page registration
|
||||
|
||||
import type React from "react";
|
||||
import type { RouteProps } from "react-router";
|
||||
import type { IconProps } from "../../renderer/components/icon";
|
||||
import type { IClassName } from "../../renderer/utils";
|
||||
import type { TabRoute } from "../../renderer/components/layout/tab-layout";
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
import React from "react";
|
||||
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
|
||||
|
||||
export interface PageRegistration extends RouteProps {
|
||||
className?: IClassName;
|
||||
url?: string; // initial url to be used for building menus and tabs, otherwise "path" applied by default
|
||||
title?: React.ReactNode; // used in sidebar's & tabs-layout if provided
|
||||
hideInMenu?: boolean; // hide element within app's navigation menu
|
||||
subPages?: (PageRegistration & TabRoute)[];
|
||||
export interface PageRegistration extends BaseRegistryItem {
|
||||
routePath?: string; // additional (suffix) route path to base extension's route: "/extension/:name"
|
||||
exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool
|
||||
components: PageComponents;
|
||||
subPages?: SubPageRegistration[];
|
||||
}
|
||||
|
||||
export interface SubPageRegistration {
|
||||
routePath: string; // required for sub-pages
|
||||
exact?: boolean;
|
||||
components: PageComponents;
|
||||
}
|
||||
|
||||
export interface PageComponents {
|
||||
Page: React.ComponentType<any>;
|
||||
MenuIcon?: React.ComponentType<IconProps>;
|
||||
}
|
||||
|
||||
export class GlobalPageRegistry extends BaseRegistry<PageRegistration> {
|
||||
export class PageRegistry<T extends PageRegistration> extends BaseRegistry<T> {
|
||||
getItems() {
|
||||
return super.getItems().map(item => {
|
||||
item.routePath = item.extension.getPageRoute(item.routePath)
|
||||
return item
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ClusterPageRegistry extends BaseRegistry<PageRegistration> {
|
||||
}
|
||||
|
||||
export const globalPageRegistry = new GlobalPageRegistry();
|
||||
export const clusterPageRegistry = new ClusterPageRegistry();
|
||||
export const globalPageRegistry = new PageRegistry<Omit<PageRegistration, "subPages">>();
|
||||
export const clusterPageRegistry = new PageRegistry();
|
||||
@@ -1,9 +1,9 @@
|
||||
// Extensions API -> Status bar customizations
|
||||
|
||||
import React from "react";
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
import { BaseRegistry, BaseRegistryItem } from "./base-registry";
|
||||
|
||||
export interface StatusBarRegistration {
|
||||
export interface StatusBarRegistration extends BaseRegistryItem {
|
||||
item?: React.ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { navigate } from "../../renderer/navigation";
|
||||
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/navigation"
|
||||
export { RouteProps } from "react-router"
|
||||
export { IURLParams } from "../../common/utils/buildUrl";
|
||||
Reference in new issue
Block a user