diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts
index da2669550f..d344e8ebc3 100644
--- a/src/extensions/extension-loader.ts
+++ b/src/extensions/extension-loader.ts
@@ -251,6 +251,7 @@ export class ExtensionLoader extends Singleton {
registries.commandRegistry.add(extension.commands),
registries.welcomeMenuRegistry.add(extension.welcomeMenus),
registries.catalogEntityDetailRegistry.add(extension.catalogEntityDetailItems),
+ registries.topBarRegistry.add(extension.topBarItems),
];
this.events.on("remove", (removedExtension: LensRendererExtension) => {
diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts
index fb26d383b0..7119b0f7ac 100644
--- a/src/extensions/lens-renderer-extension.ts
+++ b/src/extensions/lens-renderer-extension.ts
@@ -28,6 +28,7 @@ import { LensExtension } from "./lens-extension";
import { getExtensionPageUrl } from "./registries/page-registry";
import type { CommandRegistration } from "./registries/command-registry";
import type { EntitySettingRegistration } from "./registries/entity-setting-registry";
+import type { TopBarRegistration } from "./registries/topbar-registry";
export class LensRendererExtension extends LensExtension {
globalPages: PageRegistration[] = [];
@@ -44,6 +45,7 @@ export class LensRendererExtension extends LensExtension {
commands: CommandRegistration[] = [];
welcomeMenus: WelcomeMenuRegistration[] = [];
catalogEntityDetailItems: CatalogEntityDetailRegistration[] = [];
+ topBarItems: TopBarRegistration[] = [];
async navigate
(pageId?: string, params?: P) {
const { navigate } = await import("../renderer/navigation");
diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts
index b9ba986521..70c1c5ecba 100644
--- a/src/extensions/registries/index.ts
+++ b/src/extensions/registries/index.ts
@@ -35,3 +35,4 @@ export * from "./welcome-menu-registry";
export * from "./protocol-handler-registry";
export * from "./catalog-entity-detail-registry";
export * from "./workloads-overview-detail-registry";
+export * from "./topbar-registry";
diff --git a/src/extensions/registries/topbar-registry.ts b/src/extensions/registries/topbar-registry.ts
new file mode 100644
index 0000000000..d0c3f03133
--- /dev/null
+++ b/src/extensions/registries/topbar-registry.ts
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2021 OpenLens Authors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import type React from "react";
+import { BaseRegistry } from "./base-registry";
+
+interface TopBarComponents {
+ Item?: React.ComponentType;
+}
+
+export interface TopBarRegistration {
+ components: TopBarComponents;
+}
+
+export class TopBarRegistry extends BaseRegistry {
+}
+
+export const topBarRegistry = new TopBarRegistry();
diff --git a/src/renderer/components/cluster-manager/topbar.test.tsx b/src/renderer/components/cluster-manager/topbar.test.tsx
new file mode 100644
index 0000000000..ce716581db
--- /dev/null
+++ b/src/renderer/components/cluster-manager/topbar.test.tsx
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2021 OpenLens Authors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import React from "react";
+import { render } from "@testing-library/react";
+import "@testing-library/jest-dom/extend-expect";
+
+jest.mock("electron", () => ({
+ app: {
+ getPath: () => "/foo",
+ },
+}));
+
+jest.mock("../../../extensions/registries");
+
+import { topBarRegistry } from "../../../extensions/registries";
+import { TopBar } from "../layout/topbar";
+
+describe("", () => {
+ it("renders w/o errors", () => {
+ const { container } = render();
+
+ expect(container).toBeInstanceOf(HTMLElement);
+ });
+
+ it("renders title", async () => {
+ const { getByTestId } = render();
+
+ expect(await getByTestId("topbarLabel")).toHaveTextContent("topbar");
+ });
+
+ it("renders items", async () => {
+ const testId = "testId";
+ const text = "an item";
+
+ topBarRegistry.getItems = jest.fn().mockImplementationOnce(() => [
+ {
+ components: {
+ Item: {text}
+ }
+ }
+ ]);
+
+ const { getByTestId } = render();
+
+ expect(await getByTestId(testId)).toHaveTextContent(text);
+ });
+});
diff --git a/src/renderer/components/layout/topbar.module.css b/src/renderer/components/layout/topbar.module.css
index 4738d53339..2c3741024b 100644
--- a/src/renderer/components/layout/topbar.module.css
+++ b/src/renderer/components/layout/topbar.module.css
@@ -43,4 +43,7 @@
align-items: center;
display: flex;
height: 100%;
+}
+
+.extensionItem {
}
\ No newline at end of file
diff --git a/src/renderer/components/layout/topbar.tsx b/src/renderer/components/layout/topbar.tsx
index 2fe302ce55..a29c47a5c3 100644
--- a/src/renderer/components/layout/topbar.tsx
+++ b/src/renderer/components/layout/topbar.tsx
@@ -22,16 +22,44 @@
import styles from "./topbar.module.css";
import React from "react";
import { observer } from "mobx-react";
+import { topBarRegistry } from "../../../extensions/registries";
interface Props extends React.HTMLAttributes {
label: React.ReactNode;
}
export const TopBar = observer(({ label, children, ...rest }: Props) => {
+ const renderRegisteredItems = () => {
+ const items = topBarRegistry.getItems();
+
+ if (!Array.isArray(items)) {
+ return null;
+ }
+
+ return (
+
+ {items.map((registration, index) => {
+ if (!registration?.components?.Item) {
+ return null;
+ }
+
+ return (
+
+ {registration.components.Item}
+
+ );
+ })}
+
+ );
+ };
+
return (
-
{label}
-
{children}
+
{label}
+
+ {renderRegisteredItems()}
+ {children}
+
);
});