diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts
index 15f11139e6..53131f507b 100644
--- a/src/extensions/registries/page-menu-registry.ts
+++ b/src/extensions/registries/page-menu-registry.ts
@@ -1,9 +1,10 @@
// Extensions-api -> Register page menu items
import type { IconProps } from "../../renderer/components/icon";
import type React from "react";
-import { action } from "mobx";
+import { action, computed } from "mobx";
import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension";
+import { RegisteredPage } from "./page-registry";
export interface PageMenuTarget
{
extensionId?: string;
@@ -17,11 +18,16 @@ export interface PageMenuRegistration {
components: PageMenuComponents;
}
+export interface ClusterPageMenuRegistration extends PageMenuRegistration {
+ id?: string;
+ parentId?: string;
+}
+
export interface PageMenuComponents {
Icon: React.ComponentType;
}
-export class PageMenuRegistry extends BaseRegistry> {
+export class GlobalPageMenuRegistry extends BaseRegistry {
@action
add(items: PageMenuRegistration[], ext: LensExtension) {
const normalizedItems = items.map(menuItem => {
@@ -35,5 +41,31 @@ export class PageMenuRegistry extends BaseRegistry {
+ @action
+ add(items: PageMenuRegistration[], ext: LensExtension) {
+ const normalizedItems = items.map(menuItem => {
+ menuItem.target = {
+ extensionId: ext.name,
+ ...(menuItem.target || {}),
+ };
+ return menuItem;
+ });
+ return super.add(normalizedItems);
+ }
+
+ getRootItems() {
+ return this.getItems().filter((item) => !item.parentId);
+ }
+
+ getSubItems(parent: ClusterPageMenuRegistration) {
+ return this.getItems().filter((item) => item.parentId === parent.id && item.target.extensionId === parent.target.extensionId);
+ }
+
+ getByPage(page: RegisteredPage) {
+ return this.getItems().find((item) => item.target?.pageId == page.id && item.target?.extensionId === page.extensionId);
+ }
+}
+
+export const globalPageMenuRegistry = new GlobalPageMenuRegistry();
+export const clusterPageMenuRegistry = new ClusterPageMenuRegistry();
diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx
index 185a9650fa..7aea1125ad 100755
--- a/src/renderer/components/app.tsx
+++ b/src/renderer/components/app.tsx
@@ -34,12 +34,15 @@ import { Terminal } from "./dock/terminal";
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
import logger from "../../main/logger";
import { webFrame } from "electron";
-import { clusterPageRegistry } from "../../extensions/registries/page-registry";
+import { clusterPageRegistry, getExtensionPageUrl, PageRegistration, RegisteredPage } from "../../extensions/registries/page-registry";
import { extensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus";
import { requestMain } from "../../common/ipc";
import whatInput from 'what-input';
import { clusterSetFrameIdHandler } from "../../common/cluster-ipc";
+import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../extensions/registries";
+import { TabLayoutRoute, TabLayout } from "./layout/tab-layout";
+import { Trans } from "@lingui/macro";
@observer
export class App extends React.Component {
@@ -72,9 +75,48 @@ export class App extends React.Component {
return workloadsURL();
}
+ getTabLayoutRoutes(menuItem: ClusterPageMenuRegistration) {
+ const routes: TabLayoutRoute[] = [];
+ if (!menuItem.id) {
+ return routes;
+ }
+ clusterPageMenuRegistry.getSubItems(menuItem).forEach((item) => {
+ const page = clusterPageRegistry.getByPageMenuTarget(item.target);
+ if (page) {
+ routes.push({
+ routePath: page.routePath,
+ url: getExtensionPageUrl({ extensionId: page.extensionId, pageId: page.id, params: item.target.params }),
+ title: item.title,
+ component: page.components.Page,
+ exact: page.exact
+ });
+ }
+ });
+ return routes;
+ }
+
+ renderExtensionTabLayoutRoutes() {
+ return clusterPageMenuRegistry.getRootItems().map((menu, index) => {
+ const tabRoutes = this.getTabLayoutRoutes(menu);
+ if (tabRoutes.length > 0) {
+ const pageComponent = () => ;
+ return ;
+ } else {
+ const page = clusterPageRegistry.getByPageMenuTarget(menu.target);
+ if (page) {
+ const pageComponent = () => ;
+ return ;
+ }
+ }
+ });
+ }
+
renderExtensionRoutes() {
- return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath }) => {
- return ;
+ return clusterPageRegistry.getItems().map((page, index) => {
+ const menu = clusterPageMenuRegistry.getByPage(page);
+ if (!menu) {
+ return ;
+ }
});
}
@@ -96,6 +138,7 @@ export class App extends React.Component {
+ {this.renderExtensionTabLayoutRoutes()}
{this.renderExtensionRoutes()}
diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx
index 428794764f..0c56923c28 100644
--- a/src/renderer/components/layout/sidebar.tsx
+++ b/src/renderer/components/layout/sidebar.tsx
@@ -29,7 +29,7 @@ import { CustomResources } from "../+custom-resources/custom-resources";
import { isActiveRoute, navigation } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac";
import { Spinner } from "../spinner";
-import { clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries";
+import { ClusterPageMenuRegistration, clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl, RegisteredPage } from "../../../extensions/registries";
const SidebarContext = React.createContext({ pinned: false });
type SidebarContextValue = {
@@ -76,6 +76,52 @@ export class Sidebar extends React.Component {
});
}
+ getTabLayoutRoutes(menu: ClusterPageMenuRegistration): TabLayoutRoute[] {
+ if (!menu.id) {
+ return [];
+ }
+ const routes: TabLayoutRoute[] = [];
+
+ clusterPageMenuRegistry.getSubItems(menu).forEach((subItem) => {
+ const subPage = clusterPageRegistry.getByPageMenuTarget(subItem.target);
+ if (subPage) {
+ routes.push({
+ routePath: subPage.routePath,
+ url: getExtensionPageUrl({ extensionId: subPage.extensionId, pageId: subPage.id, params: subItem.target.params }),
+ title: subItem.title,
+ component: subPage.components.Page,
+ exact: subPage.exact
+ });
+ }
+ });
+ return routes;
+ }
+
+ renderRegisteredMenus() {
+ return clusterPageMenuRegistry.getRootItems().map((menuItem) => {
+ const registeredPage = clusterPageRegistry.getByPageMenuTarget(menuItem.target);
+ let pageUrl: string;
+ let isActive = false;
+ if (registeredPage) {
+ const { extensionId, id: pageId } = registeredPage;
+ pageUrl = getExtensionPageUrl({ extensionId, pageId, params: menuItem.target.params });
+ isActive = pageUrl === navigation.location.pathname;
+ }
+ const tabRoutes = this.getTabLayoutRoutes(menuItem);
+ if (!registeredPage && tabRoutes.length == 0) {
+ return;
+ }
+ return (
+ }
+ isActive={isActive}
+ subMenus={tabRoutes}
+ />
+ );
+ });
+ }
+
render() {
const { toggle, isPinned, className } = this.props;
const query = namespaceStore.getContextParams();
@@ -191,20 +237,7 @@ export class Sidebar extends React.Component {
>
{this.renderCustomResources()}
- {clusterPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => {
- const registeredPage = clusterPageRegistry.getByPageMenuTarget(target);
- if (!registeredPage) return;
- const { extensionId, id: pageId } = registeredPage;
- const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: target.params });
- const isActive = pageUrl === navigation.location.pathname;
- return (
- }
- isActive={isActive}
- />
- );
- })}
+ {this.renderRegisteredMenus()}