Support}>
Community Slack Channel
Ask a question, see what's being discussed, join the conversation here
diff --git a/integration/__tests__/app.tests.ts b/integration/__tests__/app.tests.ts
index 74081a2061..8845ae91a8 100644
--- a/integration/__tests__/app.tests.ts
+++ b/integration/__tests__/app.tests.ts
@@ -13,6 +13,7 @@ const itif = (condition: boolean) => condition ? it : it.skip
jest.setTimeout(60000)
+// FIXME (!): improve / simplify all css-selectors + use [data-test-id="some-id"] (already used in some tests below)
describe("Lens integration tests", () => {
const TEST_NAMESPACE = "integration-tests"
@@ -394,7 +395,7 @@ describe("Lens integration tests", () => {
if (drawer !== "") {
it(`shows ${drawer} drawer`, async () => {
expect(clusterAdded).toBe(true)
- await app.client.click(`.sidebar-nav #${drawerId} span.link-text`)
+ await app.client.click(`.sidebar-nav [data-test-id="${drawerId}"] span.link-text`)
await app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name)
})
}
@@ -409,7 +410,7 @@ describe("Lens integration tests", () => {
// hide the drawer
it(`hides ${drawer} drawer`, async () => {
expect(clusterAdded).toBe(true)
- await app.client.click(`.sidebar-nav #${drawerId} span.link-text`)
+ await app.client.click(`.sidebar-nav [data-test-id="${drawerId}"] span.link-text`)
await expect(app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name, 100)).rejects.toThrow()
})
}
@@ -428,7 +429,7 @@ describe("Lens integration tests", () => {
it(`shows a logs for a pod`, async () => {
expect(clusterAdded).toBe(true)
// Go to Pods page
- await app.client.click(".sidebar-nav #workloads span.link-text")
+ await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text")
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
await app.client.click('a[href^="/pods"]')
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
@@ -479,7 +480,7 @@ describe("Lens integration tests", () => {
it(`creates a pod in ${TEST_NAMESPACE} namespace`, async () => {
expect(clusterAdded).toBe(true)
- await app.client.click(".sidebar-nav #workloads span.link-text")
+ await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text")
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
await app.client.click('a[href^="/pods"]')
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
diff --git a/src/extensions/dynamic-page.tsx b/src/extensions/dynamic-page.tsx
deleted file mode 100644
index 854e0f27fd..0000000000
--- a/src/extensions/dynamic-page.tsx
+++ /dev/null
@@ -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 (
-
-
-
- )
- }
-}
diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts
index 78ff1fd3ee..b75fd5147d 100644
--- a/src/extensions/extension-loader.ts
+++ b/src/extensions/extension-loader.ts
@@ -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[]) {
diff --git a/src/extensions/interfaces/registrations.ts b/src/extensions/interfaces/registrations.ts
index 1d875127d0..14c9f66c22 100644
--- a/src/extensions/interfaces/registrations.ts
+++ b/src/extensions/interfaces/registrations.ts
@@ -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"
\ No newline at end of file
diff --git a/src/extensions/lens-extension.ts b/src/extensions/lens-extension.ts
index f1ffb9184b..ca135a0b39 100644
--- a/src/extensions/lens-extension.ts
+++ b/src/extensions/lens-extension.ts
@@ -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;
diff --git a/src/extensions/lens-main-extension.ts b/src/extensions/lens-main-extension.ts
index 5e5aa073f3..0055344a66 100644
--- a/src/extensions/lens-main-extension.ts
+++ b/src/extensions/lens-main-extension.ts
@@ -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().navigate(location, frameId)
+ async navigate(location?: string, frameId?: number) {
+ const windowManager = WindowManager.getInstance();
+ const url = this.getPageUrl(location); // get full path to extension's page
+ await windowManager.navigate(url, frameId);
}
}
diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts
index d26bc4764e..87ca214805 100644
--- a/src/extensions/lens-renderer-extension.ts
+++ b/src/extensions/lens-renderer-extension.ts
@@ -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));
}
}
diff --git a/src/extensions/registries/app-preference-registry.ts b/src/extensions/registries/app-preference-registry.ts
index 6c54911f82..dc15ec3e20 100644
--- a/src/extensions/registries/app-preference-registry.ts
+++ b/src/extensions/registries/app-preference-registry.ts
@@ -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;
Input: React.ComponentType;
}
-export interface AppPreferenceRegistration {
+export interface AppPreferenceRegistration extends BaseRegistryItem {
title: string;
components: AppPreferenceComponents;
}
diff --git a/src/extensions/registries/base-registry.ts b/src/extensions/registries/base-registry.ts
index ff23e36cad..76678791f9 100644
--- a/src/extensions/registries/base-registry.ts
+++ b/src/extensions/registries/base-registry.ts
@@ -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 {
- protected items = observable([], { 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 {
+ private items = observable.map([], { 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);
+ }
}
}
diff --git a/src/extensions/registries/cluster-feature-registry.ts b/src/extensions/registries/cluster-feature-registry.ts
index f17ee0ff92..17a4ef1eef 100644
--- a/src/extensions/registries/cluster-feature-registry.ts
+++ b/src/extensions/registries/cluster-feature-registry.ts
@@ -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;
}
-export interface ClusterFeatureRegistration {
+export interface ClusterFeatureRegistration extends BaseRegistryItem {
title: string;
components: ClusterFeatureComponents
feature: ClusterFeature
}
-export class ClusterFeatureRegistry extends BaseRegistry {}
+export class ClusterFeatureRegistry extends BaseRegistry {
+}
export const clusterFeatureRegistry = new ClusterFeatureRegistry()
diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts
index fcb9ad03f2..cdcfb7124b 100644
--- a/src/extensions/registries/index.ts
+++ b/src/extensions/registries/index.ts
@@ -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"
diff --git a/src/extensions/registries/kube-object-detail-registry.ts b/src/extensions/registries/kube-object-detail-registry.ts
index 1869424e92..63d9cfa30d 100644
--- a/src/extensions/registries/kube-object-detail-registry.ts
+++ b/src/extensions/registries/kube-object-detail-registry.ts
@@ -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;
}
-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 {
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) {
diff --git a/src/extensions/registries/kube-object-menu-registry.ts b/src/extensions/registries/kube-object-menu-registry.ts
index 335b1b7441..3e0cfb0251 100644
--- a/src/extensions/registries/kube-object-menu-registry.ts
+++ b/src/extensions/registries/kube-object-menu-registry.ts
@@ -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;
}
-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 {
getItemsForKind(kind: string, apiVersion: string) {
- return this.items.filter((item) => {
+ return this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
}
diff --git a/src/extensions/registries/kube-object-status-registry.ts b/src/extensions/registries/kube-object-status-registry.ts
index bd3a6e0225..3a516a82e1 100644
--- a/src/extensions/registries/kube-object-status-registry.ts
+++ b/src/extensions/registries/kube-object-status-registry.ts
@@ -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 {
getItemsForKind(kind: string, apiVersion: string) {
- return this.items.filter((item) => {
+ return this.getItems().filter((item) => {
return item.kind === kind && item.apiVersions.includes(apiVersion)
})
}
diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts
new file mode 100644
index 0000000000..6bd161ccb2
--- /dev/null
+++ b/src/extensions/registries/page-menu-registry.ts
@@ -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;
+}
+
+export class PageMenuRegistry extends BaseRegistry {
+ getItems() {
+ return super.getItems().map(item => {
+ item.url = item.extension.getPageUrl(item.url)
+ return item
+ });
+ }
+}
+
+export const globalPageMenuRegistry = new PageMenuRegistry>();
+export const clusterPageMenuRegistry = new PageMenuRegistry();
diff --git a/src/extensions/registries/page-registry.ts b/src/extensions/registries/page-registry.ts
index 0f448e1284..886ab8a584 100644
--- a/src/extensions/registries/page-registry.ts
+++ b/src/extensions/registries/page-registry.ts
@@ -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;
- MenuIcon?: React.ComponentType;
}
-export class GlobalPageRegistry extends BaseRegistry {
+export class PageRegistry extends BaseRegistry {
+ getItems() {
+ return super.getItems().map(item => {
+ item.routePath = item.extension.getPageRoute(item.routePath)
+ return item
+ });
+ }
}
-export class ClusterPageRegistry extends BaseRegistry {
-}
-
-export const globalPageRegistry = new GlobalPageRegistry();
-export const clusterPageRegistry = new ClusterPageRegistry();
+export const globalPageRegistry = new PageRegistry>();
+export const clusterPageRegistry = new PageRegistry();
diff --git a/src/extensions/registries/status-bar-registry.ts b/src/extensions/registries/status-bar-registry.ts
index 88c4132d30..c1029e6d68 100644
--- a/src/extensions/registries/status-bar-registry.ts
+++ b/src/extensions/registries/status-bar-registry.ts
@@ -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;
}
diff --git a/src/extensions/renderer-api/navigation.ts b/src/extensions/renderer-api/navigation.ts
index 2c82fd4db5..f923f6e152 100644
--- a/src/extensions/renderer-api/navigation.ts
+++ b/src/extensions/renderer-api/navigation.ts
@@ -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";
diff --git a/src/renderer/components/+apps/apps.tsx b/src/renderer/components/+apps/apps.tsx
index 5ee297a1c0..469765aa68 100644
--- a/src/renderer/components/+apps/apps.tsx
+++ b/src/renderer/components/+apps/apps.tsx
@@ -1,41 +1,34 @@
import React from "react";
import { observer } from "mobx-react";
-import { Redirect, Route, Switch } from "react-router";
import { Trans } from "@lingui/macro";
-import { TabLayout, TabRoute } from "../layout/tab-layout";
+import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { HelmCharts, helmChartsRoute, helmChartsURL } from "../+apps-helm-charts";
import { HelmReleases, releaseRoute, releaseURL } from "../+apps-releases";
import { namespaceStore } from "../+namespaces/namespace.store";
@observer
export class Apps extends React.Component {
- static get tabRoutes(): TabRoute[] {
+ static get tabRoutes(): TabLayoutRoute[] {
const query = namespaceStore.getContextParams();
return [
{
title: Charts,
component: HelmCharts,
url: helmChartsURL(),
- path: helmChartsRoute.path,
+ routePath: helmChartsRoute.path.toString(),
},
{
title: Releases,
component: HelmReleases,
url: releaseURL({ query }),
- path: releaseRoute.path,
+ routePath: releaseRoute.path.toString(),
},
]
}
render() {
- const tabRoutes = Apps.tabRoutes;
return (
-
-
- {tabRoutes.map((route, index) => )}
-
-
-
+
)
}
}
diff --git a/src/renderer/components/+config/config.route.ts b/src/renderer/components/+config/config.route.ts
index f480e84952..d747f49efd 100644
--- a/src/renderer/components/+config/config.route.ts
+++ b/src/renderer/components/+config/config.route.ts
@@ -5,7 +5,7 @@ import { configMapsURL } from "../+config-maps/config-maps.route";
export const configRoute: RouteProps = {
get path() {
- return Config.tabRoutes.map(({ path }) => path).flat()
+ return Config.tabRoutes.map(({ routePath }) => routePath).flat()
}
}
diff --git a/src/renderer/components/+config/config.tsx b/src/renderer/components/+config/config.tsx
index 7e42c7fcc8..f738cb273e 100644
--- a/src/renderer/components/+config/config.tsx
+++ b/src/renderer/components/+config/config.tsx
@@ -1,33 +1,26 @@
import React from "react";
import { observer } from "mobx-react";
-import { Redirect, Route, Switch } from "react-router";
import { Trans } from "@lingui/macro";
-import { TabLayout, TabRoute } from "../layout/tab-layout";
+import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { ConfigMaps, configMapsRoute, configMapsURL } from "../+config-maps";
import { Secrets, secretsRoute, secretsURL } from "../+config-secrets";
import { namespaceStore } from "../+namespaces/namespace.store";
import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config-resource-quotas";
-import { PodDisruptionBudgets, pdbRoute, pdbURL } from "../+config-pod-disruption-budgets";
-import { configURL } from "./config.route";
+import { pdbRoute, pdbURL, PodDisruptionBudgets } from "../+config-pod-disruption-budgets";
import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers";
import { isAllowedResource } from "../../../common/rbac"
-import { buildURL } from "../../../common/utils/buildUrl";
-
-export const certificatesURL = buildURL("/certificates");
-export const issuersURL = buildURL("/issuers");
-export const clusterIssuersURL = buildURL("/clusterissuers");
@observer
export class Config extends React.Component {
- static get tabRoutes(): TabRoute[] {
+ static get tabRoutes(): TabLayoutRoute[] {
const query = namespaceStore.getContextParams()
- const routes: TabRoute[] = []
+ const routes: TabLayoutRoute[] = []
if (isAllowedResource("configmaps")) {
routes.push({
title: ConfigMaps,
component: ConfigMaps,
url: configMapsURL({ query }),
- path: configMapsRoute.path,
+ routePath: configMapsRoute.path.toString(),
})
}
if (isAllowedResource("secrets")) {
@@ -35,7 +28,7 @@ export class Config extends React.Component {
title: Secrets,
component: Secrets,
url: secretsURL({ query }),
- path: secretsRoute.path,
+ routePath: secretsRoute.path.toString(),
})
}
if (isAllowedResource("resourcequotas")) {
@@ -43,7 +36,7 @@ export class Config extends React.Component {
title: Resource Quotas,
component: ResourceQuotas,
url: resourceQuotaURL({ query }),
- path: resourceQuotaRoute.path,
+ routePath: resourceQuotaRoute.path.toString(),
})
}
if (isAllowedResource("horizontalpodautoscalers")) {
@@ -51,7 +44,7 @@ export class Config extends React.Component {
title: HPA,
component: HorizontalPodAutoscalers,
url: hpaURL({ query }),
- path: hpaRoute.path,
+ routePath: hpaRoute.path.toString(),
})
}
if (isAllowedResource("poddisruptionbudgets")) {
@@ -59,21 +52,15 @@ export class Config extends React.Component {
title: Pod Disruption Budgets,
component: PodDisruptionBudgets,
url: pdbURL({ query }),
- path: pdbRoute.path,
+ routePath: pdbRoute.path.toString(),
})
}
return routes;
}
render() {
- const tabRoutes = Config.tabRoutes;
return (
-
-
- {tabRoutes.map((route, index) => )}
-
-
-
+
)
}
}
diff --git a/src/renderer/components/+custom-resources/custom-resources.tsx b/src/renderer/components/+custom-resources/custom-resources.tsx
index 9dbd882bd1..ee34809013 100644
--- a/src/renderer/components/+custom-resources/custom-resources.tsx
+++ b/src/renderer/components/+custom-resources/custom-resources.tsx
@@ -2,23 +2,20 @@ import React from "react";
import { observer } from "mobx-react";
import { Redirect, Route, Switch } from "react-router";
import { Trans } from "@lingui/macro";
-import { TabLayout, TabRoute } from "../layout/tab-layout";
+import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { crdResourcesRoute, crdRoute, crdURL, crdDefinitionsRoute } from "./crd.route";
import { CrdList } from "./crd-list";
import { CrdResources } from "./crd-resources";
-// todo: next steps - customization via plugins
-// todo: list views (rows content), full details view and if possible chart/prometheus hooks
-
@observer
export class CustomResources extends React.Component {
- static get tabRoutes(): TabRoute[] {
+ static get tabRoutes(): TabLayoutRoute[] {
return [
{
title: Definitions,
component: CustomResources,
url: crdURL(),
- path: crdRoute.path,
+ routePath: crdRoute.path.toString(),
}
]
}
diff --git a/src/renderer/components/+network/network.route.ts b/src/renderer/components/+network/network.route.ts
index 3029b85fa2..c13f7cedf7 100644
--- a/src/renderer/components/+network/network.route.ts
+++ b/src/renderer/components/+network/network.route.ts
@@ -5,7 +5,7 @@ import { IURLParams } from "../../../common/utils/buildUrl";
export const networkRoute: RouteProps = {
get path() {
- return Network.tabRoutes.map(({ path }) => path).flat()
+ return Network.tabRoutes.map(({ routePath }) => routePath).flat()
}
}
diff --git a/src/renderer/components/+network/network.tsx b/src/renderer/components/+network/network.tsx
index 2932f5860c..9e0b2d92c6 100644
--- a/src/renderer/components/+network/network.tsx
+++ b/src/renderer/components/+network/network.tsx
@@ -2,32 +2,26 @@ import "./network.scss"
import React from "react";
import { observer } from "mobx-react";
-import { Redirect, Route, Switch } from "react-router";
-import { RouteComponentProps } from "react-router-dom";
import { Trans } from "@lingui/macro";
-import { TabLayout, TabRoute } from "../layout/tab-layout";
+import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { Services, servicesRoute, servicesURL } from "../+network-services";
-import { Endpoints, endpointRoute, endpointURL } from "../+network-endpoints";
+import { endpointRoute, Endpoints, endpointURL } from "../+network-endpoints";
import { Ingresses, ingressRoute, ingressURL } from "../+network-ingresses";
import { NetworkPolicies, networkPoliciesRoute, networkPoliciesURL } from "../+network-policies";
import { namespaceStore } from "../+namespaces/namespace.store";
-import { networkURL } from "./network.route";
import { isAllowedResource } from "../../../common/rbac";
-interface Props extends RouteComponentProps<{}> {
-}
-
@observer
-export class Network extends React.Component {
- static get tabRoutes(): TabRoute[] {
+export class Network extends React.Component {
+ static get tabRoutes(): TabLayoutRoute[] {
const query = namespaceStore.getContextParams()
- const routes: TabRoute[] = [];
+ const routes: TabLayoutRoute[] = [];
if (isAllowedResource("services")) {
routes.push({
title: Services,
component: Services,
url: servicesURL({ query }),
- path: servicesRoute.path,
+ routePath: servicesRoute.path.toString(),
})
}
if (isAllowedResource("endpoints")) {
@@ -35,7 +29,7 @@ export class Network extends React.Component {
title: Endpoints,
component: Endpoints,
url: endpointURL({ query }),
- path: endpointRoute.path,
+ routePath: endpointRoute.path.toString(),
})
}
if (isAllowedResource("ingresses")) {
@@ -43,7 +37,7 @@ export class Network extends React.Component {
title: Ingresses,
component: Ingresses,
url: ingressURL({ query }),
- path: ingressRoute.path,
+ routePath: ingressRoute.path.toString(),
})
}
if (isAllowedResource("networkpolicies")) {
@@ -51,21 +45,15 @@ export class Network extends React.Component {
title: Network Policies,
component: NetworkPolicies,
url: networkPoliciesURL({ query }),
- path: networkPoliciesRoute.path,
+ routePath: networkPoliciesRoute.path.toString(),
})
}
return routes
}
render() {
- const tabRoutes = Network.tabRoutes;
return (
-
-
- {tabRoutes.map((route, index) => )}
-
-
-
+
)
}
}
diff --git a/src/renderer/components/+storage/storage.route.ts b/src/renderer/components/+storage/storage.route.ts
index 6a7ab065f9..6fe3bb6a0a 100644
--- a/src/renderer/components/+storage/storage.route.ts
+++ b/src/renderer/components/+storage/storage.route.ts
@@ -5,7 +5,7 @@ import { IURLParams } from "../../../common/utils/buildUrl";
export const storageRoute: RouteProps = {
get path() {
- return Storage.tabRoutes.map(({ path }) => path).flat()
+ return Storage.tabRoutes.map(({ routePath }) => routePath).flat()
}
}
diff --git a/src/renderer/components/+storage/storage.tsx b/src/renderer/components/+storage/storage.tsx
index c86afad2e0..e8a105944b 100644
--- a/src/renderer/components/+storage/storage.tsx
+++ b/src/renderer/components/+storage/storage.tsx
@@ -2,31 +2,25 @@ import "./storage.scss"
import React from "react";
import { observer } from "mobx-react";
-import { Redirect, Route, Switch } from "react-router";
-import { RouteComponentProps } from "react-router-dom";
import { Trans } from "@lingui/macro";
-import { TabLayout, TabRoute } from "../layout/tab-layout";
+import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { PersistentVolumes, volumesRoute, volumesURL } from "../+storage-volumes";
import { StorageClasses, storageClassesRoute, storageClassesURL } from "../+storage-classes";
import { PersistentVolumeClaims, volumeClaimsRoute, volumeClaimsURL } from "../+storage-volume-claims";
import { namespaceStore } from "../+namespaces/namespace.store";
-import { storageURL } from "./storage.route";
import { isAllowedResource } from "../../../common/rbac";
-interface Props extends RouteComponentProps<{}> {
-}
-
@observer
-export class Storage extends React.Component {
+export class Storage extends React.Component {
static get tabRoutes() {
- const tabRoutes: TabRoute[] = [];
+ const tabRoutes: TabLayoutRoute[] = [];
const query = namespaceStore.getContextParams()
tabRoutes.push({
title: Persistent Volume Claims,
component: PersistentVolumeClaims,
url: volumeClaimsURL({ query }),
- path: volumeClaimsRoute.path,
+ routePath: volumeClaimsRoute.path.toString(),
})
if (isAllowedResource('persistentvolumes')) {
@@ -34,7 +28,7 @@ export class Storage extends React.Component {
title: Persistent Volumes,
component: PersistentVolumes,
url: volumesURL(),
- path: volumesRoute.path,
+ routePath: volumesRoute.path.toString(),
});
}
@@ -43,21 +37,15 @@ export class Storage extends React.Component {
title: Storage Classes,
component: StorageClasses,
url: storageClassesURL(),
- path: storageClassesRoute.path,
+ routePath: storageClassesRoute.path.toString(),
})
}
return tabRoutes;
}
render() {
- const tabRoutes = Storage.tabRoutes;
return (
-
-
- {tabRoutes.map((route, index) => )}
-
-
-
+
)
}
}
diff --git a/src/renderer/components/+user-management/user-management.route.ts b/src/renderer/components/+user-management/user-management.route.ts
index 04de465e3f..aa520fb128 100644
--- a/src/renderer/components/+user-management/user-management.route.ts
+++ b/src/renderer/components/+user-management/user-management.route.ts
@@ -4,7 +4,7 @@ import { UserManagement } from "./user-management"
export const usersManagementRoute: RouteProps = {
get path() {
- return UserManagement.tabRoutes.map(({ path }) => path).flat()
+ return UserManagement.tabRoutes.map(({ routePath }) => routePath).flat()
}
}
diff --git a/src/renderer/components/+user-management/user-management.tsx b/src/renderer/components/+user-management/user-management.tsx
index 97cf71140e..e7985fae6a 100644
--- a/src/renderer/components/+user-management/user-management.tsx
+++ b/src/renderer/components/+user-management/user-management.tsx
@@ -1,44 +1,39 @@
import "./user-management.scss"
import React from "react";
import { observer } from "mobx-react";
-import { Redirect, Route, Switch } from "react-router";
-import { RouteComponentProps } from "react-router-dom";
import { Trans } from "@lingui/macro";
-import { TabLayout, TabRoute } from "../layout/tab-layout";
+import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { Roles } from "../+user-management-roles";
import { RoleBindings } from "../+user-management-roles-bindings";
import { ServiceAccounts } from "../+user-management-service-accounts";
-import { roleBindingsRoute, roleBindingsURL, rolesRoute, rolesURL, serviceAccountsRoute, serviceAccountsURL, usersManagementURL } from "./user-management.route";
+import { roleBindingsRoute, roleBindingsURL, rolesRoute, rolesURL, serviceAccountsRoute, serviceAccountsURL } from "./user-management.route";
import { namespaceStore } from "../+namespaces/namespace.store";
import { PodSecurityPolicies, podSecurityPoliciesRoute, podSecurityPoliciesURL } from "../+pod-security-policies";
import { isAllowedResource } from "../../../common/rbac";
-interface Props extends RouteComponentProps<{}> {
-}
-
@observer
-export class UserManagement extends React.Component {
+export class UserManagement extends React.Component {
static get tabRoutes() {
- const tabRoutes: TabRoute[] = [];
+ const tabRoutes: TabLayoutRoute[] = [];
const query = namespaceStore.getContextParams()
tabRoutes.push(
{
title: Service Accounts,
component: ServiceAccounts,
url: serviceAccountsURL({ query }),
- path: serviceAccountsRoute.path,
+ routePath: serviceAccountsRoute.path.toString(),
},
{
title: Role Bindings,
component: RoleBindings,
url: roleBindingsURL({ query }),
- path: roleBindingsRoute.path,
+ routePath: roleBindingsRoute.path.toString(),
},
{
title: Roles,
component: Roles,
url: rolesURL({ query }),
- path: rolesRoute.path,
+ routePath: rolesRoute.path.toString(),
},
)
if (isAllowedResource("podsecuritypolicies")) {
@@ -46,21 +41,15 @@ export class UserManagement extends React.Component {
title: Pod Security Policies,
component: PodSecurityPolicies,
url: podSecurityPoliciesURL(),
- path: podSecurityPoliciesRoute.path,
+ routePath: podSecurityPoliciesRoute.path.toString(),
})
}
return tabRoutes;
}
render() {
- const tabRoutes = UserManagement.tabRoutes;
return (
-
-
- {tabRoutes.map((route, index) => )}
-
-
-
+
)
}
}
diff --git a/src/renderer/components/+workloads/workloads.route.ts b/src/renderer/components/+workloads/workloads.route.ts
index 47044b0d18..03e1f384a1 100644
--- a/src/renderer/components/+workloads/workloads.route.ts
+++ b/src/renderer/components/+workloads/workloads.route.ts
@@ -5,7 +5,7 @@ import { Workloads } from "./workloads";
export const workloadsRoute: RouteProps = {
get path() {
- return Workloads.tabRoutes.map(({ path }) => path).flat()
+ return Workloads.tabRoutes.map(({ routePath }) => routePath).flat()
}
}
diff --git a/src/renderer/components/+workloads/workloads.tsx b/src/renderer/components/+workloads/workloads.tsx
index fe6e1606b5..8c75e0b203 100644
--- a/src/renderer/components/+workloads/workloads.tsx
+++ b/src/renderer/components/+workloads/workloads.tsx
@@ -2,12 +2,10 @@ import "./workloads.scss"
import React from "react";
import { observer } from "mobx-react";
-import { Redirect, Route, Switch } from "react-router";
-import { RouteComponentProps } from "react-router-dom";
import { Trans } from "@lingui/macro";
-import { TabLayout, TabRoute } from "../layout/tab-layout";
+import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { WorkloadsOverview } from "../+workloads-overview/overview";
-import { cronJobsRoute, cronJobsURL, daemonSetsRoute, daemonSetsURL, deploymentsRoute, deploymentsURL, jobsRoute, jobsURL, overviewRoute, overviewURL, podsRoute, podsURL, statefulSetsRoute, statefulSetsURL, workloadsURL } from "./workloads.route";
+import { cronJobsRoute, cronJobsURL, daemonSetsRoute, daemonSetsURL, deploymentsRoute, deploymentsURL, jobsRoute, jobsURL, overviewRoute, overviewURL, podsRoute, podsURL, statefulSetsRoute, statefulSetsURL } from "./workloads.route";
import { namespaceStore } from "../+namespaces/namespace.store";
import { Pods } from "../+workloads-pods";
import { Deployments } from "../+workloads-deployments";
@@ -17,19 +15,16 @@ import { Jobs } from "../+workloads-jobs";
import { CronJobs } from "../+workloads-cronjobs";
import { isAllowedResource } from "../../../common/rbac"
-interface Props extends RouteComponentProps {
-}
-
@observer
-export class Workloads extends React.Component {
- static get tabRoutes(): TabRoute[] {
+export class Workloads extends React.Component {
+ static get tabRoutes(): TabLayoutRoute[] {
const query = namespaceStore.getContextParams();
- const routes: TabRoute[] = [
+ const routes: TabLayoutRoute[] = [
{
title: Overview,
component: WorkloadsOverview,
url: overviewURL({ query }),
- path: overviewRoute.path
+ routePath: overviewRoute.path.toString()
}
]
if (isAllowedResource("pods")) {
@@ -37,7 +32,7 @@ export class Workloads extends React.Component {
title: Pods,
component: Pods,
url: podsURL({ query }),
- path: podsRoute.path
+ routePath: podsRoute.path.toString()
})
}
if (isAllowedResource("deployments")) {
@@ -45,7 +40,7 @@ export class Workloads extends React.Component {
title: Deployments,
component: Deployments,
url: deploymentsURL({ query }),
- path: deploymentsRoute.path,
+ routePath: deploymentsRoute.path.toString(),
})
}
if (isAllowedResource("daemonsets")) {
@@ -53,7 +48,7 @@ export class Workloads extends React.Component {
title: DaemonSets,
component: DaemonSets,
url: daemonSetsURL({ query }),
- path: daemonSetsRoute.path,
+ routePath: daemonSetsRoute.path.toString(),
})
}
if (isAllowedResource("statefulsets")) {
@@ -61,7 +56,7 @@ export class Workloads extends React.Component {
title: StatefulSets,
component: StatefulSets,
url: statefulSetsURL({ query }),
- path: statefulSetsRoute.path,
+ routePath: statefulSetsRoute.path.toString(),
})
}
if (isAllowedResource("jobs")) {
@@ -69,7 +64,7 @@ export class Workloads extends React.Component {
title: Jobs,
component: Jobs,
url: jobsURL({ query }),
- path: jobsRoute.path,
+ routePath: jobsRoute.path.toString(),
})
}
if (isAllowedResource("cronjobs")) {
@@ -77,21 +72,15 @@ export class Workloads extends React.Component {
title: CronJobs,
component: CronJobs,
url: cronJobsURL({ query }),
- path: cronJobsRoute.path,
+ routePath: cronJobsRoute.path.toString(),
})
}
return routes;
}
render() {
- const tabRoutes = Workloads.tabRoutes;
return (
-
-
- {tabRoutes.map((route, index) => )}
-
-
-
+
)
}
}
diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx
index a5ebe554d1..45b7d565f2 100755
--- a/src/renderer/components/app.tsx
+++ b/src/renderer/components/app.tsx
@@ -29,6 +29,7 @@ import { CustomResources } from "./+custom-resources/custom-resources";
import { crdRoute } from "./+custom-resources";
import { isAllowedResource } from "../../common/rbac";
import { MainLayout } from "./layout/main-layout";
+import { TabLayout, TabLayoutRoute } from "./layout/tab-layout";
import { ErrorBoundary } from "./error-boundary";
import { Terminal } from "./dock/terminal";
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
@@ -36,7 +37,7 @@ import logger from "../../main/logger";
import { clusterIpc } from "../../common/cluster-ipc";
import { webFrame } from "electron";
import { clusterPageRegistry } from "../../extensions/registries/page-registry";
-import { DynamicPage } from "../../extensions/dynamic-page";
+import { clusterPageMenuRegistry } from "../../extensions/registries";
import { extensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus";
import whatInput from 'what-input';
@@ -52,9 +53,13 @@ export class App extends React.Component {
await clusterIpc.setFrameId.invokeFromRenderer(clusterId, frameId);
await getHostedCluster().whenReady; // cluster.activate() is done at this point
extensionLoader.loadOnClusterRenderer();
- appEventBus.emit({name: "cluster", action: "open", params: {
- clusterId: clusterId
- }})
+ appEventBus.emit({
+ name: "cluster",
+ action: "open",
+ params: {
+ clusterId: clusterId
+ }
+ })
window.addEventListener("online", () => {
window.location.reload()
})
@@ -68,6 +73,34 @@ export class App extends React.Component {
return workloadsURL();
}
+ renderExtensionRoutes() {
+ return clusterPageRegistry.getItems().map(({ id: pageId, components: { Page }, exact, routePath, subPages }) => {
+ const Component = () => {
+ if (subPages) {
+ const tabs: TabLayoutRoute[] = subPages.map(({ exact, routePath, components: { Page } }) => {
+ const menuItem = clusterPageMenuRegistry.getById(pageId);
+ if (!menuItem) return;
+ return {
+ routePath, exact,
+ component: Page,
+ url: menuItem.url,
+ title: menuItem.title,
+ }
+ }).filter(Boolean);
+ if (tabs.length > 0) {
+ return (
+
+
+
+ )
+ }
+ }
+ return
+ };
+ return
+ })
+ }
+
render() {
return (
@@ -86,12 +119,11 @@ export class App extends React.Component {
- {clusterPageRegistry.getItems().map(page => {
- return }/>
- })}
+ {this.renderExtensionRoutes()}
-
+
+
diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx
index 4a42a0419d..1522376c3e 100644
--- a/src/renderer/components/cluster-manager/cluster-manager.tsx
+++ b/src/renderer/components/cluster-manager/cluster-manager.tsx
@@ -69,8 +69,8 @@ export class ClusterManager extends React.Component {
- {globalPageRegistry.getItems().map(({ path, url = String(path), components: { Page } }) => {
- return
+ {globalPageRegistry.getItems().map(({ routePath, exact, components: { Page } }) => {
+ return
})}
diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx
index 3d1e6debaf..7844399227 100644
--- a/src/renderer/components/cluster-manager/clusters-menu.tsx
+++ b/src/renderer/components/cluster-manager/clusters-menu.tsx
@@ -5,6 +5,7 @@ import { remote } from "electron"
import type { Cluster } from "../../../main/cluster";
import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
import { observer } from "mobx-react";
+import { matchPath } from "react-router";
import { _i18n } from "../../i18n";
import { t, Trans } from "@lingui/macro";
import { userStore } from "../../../common/user-store";
@@ -14,7 +15,7 @@ import { ClusterIcon } from "../cluster-icon";
import { Icon } from "../icon";
import { autobind, cssNames, IClassName } from "../../utils";
import { Badge } from "../badge";
-import { navigate } from "../../navigation";
+import { navigate, navigation } from "../../navigation";
import { addClusterURL } from "../+add-cluster";
import { clusterSettingsURL } from "../+cluster-settings";
import { landingURL } from "../+landing-page";
@@ -22,7 +23,7 @@ import { Tooltip } from "../tooltip";
import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc";
import { clusterViewURL } from "./cluster-view.route";
-import { globalPageRegistry } from "../../../extensions/registries/page-registry";
+import { globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
interface Props {
className?: IClassName;
@@ -138,7 +139,7 @@ export class ClustersMenu extends React.Component {