From 48e278c71b58f8207a36f5fe206ffac3fa4e0cb9 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Mon, 21 Jun 2021 10:50:40 +0300 Subject: [PATCH 001/120] Rename PageLayout to SettingLayout (#3123) * rename PageLayout to SettingLayout Signed-off-by: Jari Kolehmainen * expose to extensions Signed-off-by: Jari Kolehmainen * fix Signed-off-by: Jari Kolehmainen * fix Signed-off-by: Jari Kolehmainen --- src/extensions/renderer-api/components.ts | 1 + .../components/+add-cluster/add-cluster.tsx | 6 +- .../+entity-settings/entity-settings.tsx | 7 +- .../components/+extensions/extensions.tsx | 8 +- .../components/+preferences/preferences.tsx | 7 +- .../components/layout/page-layout.tsx | 99 ++------------- .../{page-layout.scss => setting-layout.scss} | 4 +- .../components/layout/setting-layout.tsx | 116 ++++++++++++++++++ 8 files changed, 138 insertions(+), 110 deletions(-) rename src/renderer/components/layout/{page-layout.scss => setting-layout.scss} (99%) create mode 100644 src/renderer/components/layout/setting-layout.tsx diff --git a/src/extensions/renderer-api/components.ts b/src/extensions/renderer-api/components.ts index 49b3ef933c..96eaeb6db4 100644 --- a/src/extensions/renderer-api/components.ts +++ b/src/extensions/renderer-api/components.ts @@ -20,6 +20,7 @@ */ // layouts +export * from "../../renderer/components/layout/setting-layout"; export * from "../../renderer/components/layout/page-layout"; export * from "../../renderer/components/layout/wizard-layout"; export * from "../../renderer/components/layout/tab-layout"; diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index b04c9a2989..164106b517 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -38,8 +38,8 @@ import { navigate } from "../../navigation"; import { iter } from "../../utils"; import { AceEditor } from "../ace-editor"; import { Button } from "../button"; -import { PageLayout } from "../layout/page-layout"; import { Notifications } from "../notifications"; +import { SettingLayout } from "../layout/setting-layout"; interface Option { config: KubeConfig; @@ -108,7 +108,7 @@ export class AddCluster extends React.Component { render() { return ( - +

Add Clusters from Kubeconfig

Clusters added here are not merged into the ~/.kube/config file. @@ -144,7 +144,7 @@ export class AddCluster extends React.Component { tooltipOverrideDisabled /> - + ); } } diff --git a/src/renderer/components/+entity-settings/entity-settings.tsx b/src/renderer/components/+entity-settings/entity-settings.tsx index 16c79412cf..de38e6df2a 100644 --- a/src/renderer/components/+entity-settings/entity-settings.tsx +++ b/src/renderer/components/+entity-settings/entity-settings.tsx @@ -25,7 +25,6 @@ import React from "react"; import { observable, makeObservable } from "mobx"; import type { RouteComponentProps } from "react-router"; import { observer } from "mobx-react"; -import { PageLayout } from "../layout/page-layout"; import { navigation } from "../../navigation"; import { Tabs, Tab } from "../tabs"; import type { CatalogEntity } from "../../api/catalog-entity"; @@ -33,6 +32,7 @@ import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { EntitySettingRegistry } from "../../../extensions/registries"; import type { EntitySettingsRouteParams } from "../../../common/routes"; import { groupBy } from "lodash"; +import { SettingLayout } from "../layout/setting-layout"; interface Props extends RouteComponentProps { } @@ -120,10 +120,9 @@ export class EntitySettings extends React.Component { const activeSetting = this.menuItems.find((setting) => setting.id === this.activeTab); return ( -

@@ -132,7 +131,7 @@ export class EntitySettings extends React.Component {
-
+ ); } } diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 831ec6c1cd..6a2524da31 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -39,12 +39,12 @@ import logger from "../../../main/logger"; import { Button } from "../button"; import { ConfirmDialog } from "../confirm-dialog"; import { DropFileInput, InputValidators } from "../input"; -import { PageLayout } from "../layout/page-layout"; import { Notifications } from "../notifications"; import { ExtensionInstallationState, ExtensionInstallationStateStore } from "./extension-install.store"; import { Install } from "./install"; import { InstalledExtensions } from "./installed-extensions"; import { Notice } from "./notice"; +import { SettingLayout } from "../layout/setting-layout"; function getMessageFromError(error: any): string { if (!error || typeof error !== "object") { @@ -317,7 +317,7 @@ export async function attemptInstallByInfo({ name, version, requireConfirmation version = json["dist-tags"][version]; } else { Notifications.error(

The {name} extension does not have a version or tag {version}.

); - + return disposer(); } } @@ -510,7 +510,7 @@ export class Extensions extends React.Component { return ( - +

Extensions

@@ -533,7 +533,7 @@ export class Extensions extends React.Component { uninstall={confirmUninstallExtension} />
-
+
); } diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx index 0a85c6369d..9bf82f9e4f 100644 --- a/src/renderer/components/+preferences/preferences.tsx +++ b/src/renderer/components/+preferences/preferences.tsx @@ -31,7 +31,6 @@ import { AppPreferenceRegistry, RegisteredAppPreference } from "../../../extensi import { UserStore } from "../../../common/user-store"; import { ThemeStore } from "../../theme.store"; import { Input } from "../input"; -import { PageLayout } from "../layout/page-layout"; import { SubTitle } from "../layout/sub-title"; import { Select, SelectOption } from "../select"; import { HelmCharts } from "./helm-charts"; @@ -40,6 +39,7 @@ import { navigation } from "../../navigation"; import { Tab, Tabs } from "../tabs"; import { FormSwitch, Switcher } from "../switch"; import { KubeconfigSyncs } from "./kubeconfig-syncs"; +import { SettingLayout } from "../layout/setting-layout"; enum Pages { Application = "application", @@ -136,8 +136,7 @@ export class Preferences extends React.Component { ); return ( - !e.showInPreferencesTab).map(this.renderExtension)} )} - + ); } } diff --git a/src/renderer/components/layout/page-layout.tsx b/src/renderer/components/layout/page-layout.tsx index 6978cdc218..a393603885 100644 --- a/src/renderer/components/layout/page-layout.tsx +++ b/src/renderer/components/layout/page-layout.tsx @@ -19,96 +19,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./page-layout.scss"; +import { SettingLayout } from "./setting-layout"; -import React from "react"; -import { observer } from "mobx-react"; -import { boundMethod, cssNames, IClassName } from "../../utils"; -import { navigation } from "../../navigation"; -import { Icon } from "../icon"; - -export interface PageLayoutProps extends React.DOMAttributes { - className?: IClassName; - contentClass?: IClassName; - provideBackButtonNavigation?: boolean; - contentGaps?: boolean; - showOnTop?: boolean; // covers whole app view - navigation?: React.ReactNode; - back?: (evt: React.MouseEvent | KeyboardEvent) => void; -} - -const defaultProps: Partial = { - provideBackButtonNavigation: true, - contentGaps: true, -}; - -@observer -export class PageLayout extends React.Component { - static defaultProps = defaultProps as object; - - @boundMethod - back(evt?: React.MouseEvent | KeyboardEvent) { - if (this.props.back) { - this.props.back(evt); - } else { - navigation.goBack(); - } - } - - async componentDidMount() { - window.addEventListener("keydown", this.onEscapeKey); - } - - componentWillUnmount() { - window.removeEventListener("keydown", this.onEscapeKey); - } - - onEscapeKey = (evt: KeyboardEvent) => { - if (!this.props.provideBackButtonNavigation) { - return; - } - - if (evt.code === "Escape") { - evt.stopPropagation(); - this.back(evt); - } - }; - - render() { - const { - contentClass, provideBackButtonNavigation, - contentGaps, showOnTop, navigation, children, ...elemProps - } = this.props; - const className = cssNames("PageLayout", { showOnTop, showNavigation: navigation }, this.props.className); - - return ( -
- { navigation && ( - - )} -
-
- {children} -
-
- { this.props.provideBackButtonNavigation && ( -
-
- -
- - -
- )} -
-
-
- ); - } -} +/** + * PageLayout is deprecated. See MainLayout & SettingLayout + * + * @deprecated + */ +export class PageLayout extends SettingLayout {} diff --git a/src/renderer/components/layout/page-layout.scss b/src/renderer/components/layout/setting-layout.scss similarity index 99% rename from src/renderer/components/layout/page-layout.scss rename to src/renderer/components/layout/setting-layout.scss index 8b2c30494e..198e7c9cb6 100644 --- a/src/renderer/components/layout/page-layout.scss +++ b/src/renderer/components/layout/setting-layout.scss @@ -19,12 +19,10 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.PageLayout { +.SettingLayout { --width: 75%; --nav-width: 180px; --nav-column-width: 30vw; - - position: relative; width: 100%; height: 100%; display: grid !important; diff --git a/src/renderer/components/layout/setting-layout.tsx b/src/renderer/components/layout/setting-layout.tsx new file mode 100644 index 0000000000..45912f262c --- /dev/null +++ b/src/renderer/components/layout/setting-layout.tsx @@ -0,0 +1,116 @@ +/** + * 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 "./setting-layout.scss"; + +import React from "react"; +import { observer } from "mobx-react"; +import { boundMethod, cssNames, IClassName } from "../../utils"; +import { navigation } from "../../navigation"; +import { Icon } from "../icon"; + +export interface SettingLayoutProps extends React.DOMAttributes { + className?: IClassName; + contentClass?: IClassName; + provideBackButtonNavigation?: boolean; + contentGaps?: boolean; + navigation?: React.ReactNode; + back?: (evt: React.MouseEvent | KeyboardEvent) => void; +} + +const defaultProps: Partial = { + provideBackButtonNavigation: true, + contentGaps: true, +}; + +/** + * Layout for settings like pages with navigation + */ +@observer +export class SettingLayout extends React.Component { + static defaultProps = defaultProps as object; + + @boundMethod + back(evt?: React.MouseEvent | KeyboardEvent) { + if (this.props.back) { + this.props.back(evt); + } else { + navigation.goBack(); + } + } + + async componentDidMount() { + window.addEventListener("keydown", this.onEscapeKey); + } + + componentWillUnmount() { + window.removeEventListener("keydown", this.onEscapeKey); + } + + onEscapeKey = (evt: KeyboardEvent) => { + if (!this.props.provideBackButtonNavigation) { + return; + } + + if (evt.code === "Escape") { + evt.stopPropagation(); + this.back(evt); + } + }; + + render() { + const { + contentClass, provideBackButtonNavigation, + contentGaps, navigation, children, ...elemProps + } = this.props; + const className = cssNames("SettingLayout", "showOnTop", { showNavigation: navigation }, this.props.className); + + return ( +
+ { navigation && ( + + )} +
+
+ {children} +
+
+ { this.props.provideBackButtonNavigation && ( +
+
+ +
+ + +
+ )} +
+
+
+ ); + } +} From d9ceb8fa082395336463ba9981cd85735176f444 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 21 Jun 2021 10:59:54 +0300 Subject: [PATCH 002/120] General catalog category (#3106) * Adding General Entities and General Category Signed-off-by: Alex Andreev * Usign material icons for general entities Signed-off-by: Alex Andreev * Setting background for hotbar icon explicitly Signed-off-by: Alex Andreev * Adding Catalog initially to first hotbar Signed-off-by: Alex Andreev * Tuning hotbar store tests Signed-off-by: Alex Andreev * Changing type from GeneralEntity to General Signed-off-by: Alex Andreev * Search for catalog hotbar item in tests Signed-off-by: Alex Andreev * Removing Catalog link from bottom bar Signed-off-by: Alex Andreev * Waiting for hotbar catalog entity Signed-off-by: Alex Andreev * Opening cluster list by data-testId Signed-off-by: Alex Andreev * Replacing types on interfaces Signed-off-by: Alex Andreev * Fixing integration tests Signed-off-by: Alex Andreev * Adding general entities throught initializers Signed-off-by: Alex Andreev * Removing icon refs from CatalogEntitySpec Signed-off-by: Alex Andreev --- integration/__tests__/cluster-pages.tests.ts | 1 - integration/helpers/minikube.ts | 1 + integration/helpers/utils.ts | 4 +- src/common/__tests__/hotbar-store.test.ts | 30 +++++--- src/common/catalog-entities/general.ts | 76 +++++++++++++++++++ src/common/catalog-entities/index.ts | 1 + .../catalog-entities/kubernetes-cluster.ts | 13 ++-- src/common/catalog/catalog-entity.ts | 1 + src/common/hotbar-store.ts | 13 +++- .../registries/welcome-menu-registry.ts | 1 + src/main/catalog-sources/general.ts | 72 ++++++++++++++++++ src/main/catalog-sources/index.ts | 1 + src/main/cluster-manager.ts | 5 +- src/main/index.ts | 3 +- src/main/initializers/general-entities.ts | 22 ++++++ src/main/initializers/index.ts | 1 + .../+catalog/catalog-entity-details.tsx | 4 +- src/renderer/components/+catalog/catalog.tsx | 7 +- src/renderer/components/+welcome/welcome.tsx | 2 +- src/renderer/components/avatar/avatar.tsx | 7 +- .../cluster-manager/bottom-bar.scss | 12 --- .../components/cluster-manager/bottom-bar.tsx | 7 -- .../components/cluster-icon-settings.tsx | 2 +- .../components/hotbar/hotbar-entity-icon.tsx | 4 +- .../components/hotbar/hotbar-icon.scss | 8 +- .../components/hotbar/hotbar-icon.tsx | 33 ++++---- .../initializers/welcome-menu-registry.ts | 3 +- 27 files changed, 261 insertions(+), 73 deletions(-) create mode 100644 src/common/catalog-entities/general.ts create mode 100644 src/main/catalog-sources/general.ts create mode 100644 src/main/initializers/general-entities.ts diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index 24ad38dcfc..9fa04500b8 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -45,7 +45,6 @@ describe("Lens cluster pages", () => { utils.describeIf(ready)("test common pages", () => { let clusterAdded = false; const addCluster = async () => { - await app.client.waitUntilTextExists("div", "Catalog"); await waitForMinikubeDashboard(app); await app.client.click('a[href="/nodes"]'); await app.client.waitUntilTextExists("div.TableCell", "Ready"); diff --git a/integration/helpers/minikube.ts b/integration/helpers/minikube.ts index bbe411047b..8698bd6be2 100644 --- a/integration/helpers/minikube.ts +++ b/integration/helpers/minikube.ts @@ -65,6 +65,7 @@ export async function waitForMinikubeDashboard(app: Application) { await app.client.waitUntilTextExists("div.TableCell", "minikube"); await app.client.click("div.TableRow"); await app.client.waitUntilTextExists("div.drawer-title-text", "KubernetesCluster: minikube"); + await app.client.waitForExist("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root"); await app.client.click("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root"); await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started"); await app.client.waitForExist(`iframe[name="minikube"]`); diff --git a/integration/helpers/utils.ts b/integration/helpers/utils.ts index 9ce33b21c3..b509b3d342 100644 --- a/integration/helpers/utils.ts +++ b/integration/helpers/utils.ts @@ -99,8 +99,8 @@ export async function appStart() { } export async function showCatalog(app: Application) { - await app.client.waitUntilTextExists("[data-test-id=catalog-link]", "Catalog"); - await app.client.click("[data-test-id=catalog-link]"); + await app.client.waitForExist("#hotbarIcon-catalog-entity .Icon"); + await app.client.click("#hotbarIcon-catalog-entity .Icon"); } type AsyncPidGetter = () => Promise; diff --git a/src/common/__tests__/hotbar-store.test.ts b/src/common/__tests__/hotbar-store.test.ts index 51002fee23..9f54e8731d 100644 --- a/src/common/__tests__/hotbar-store.test.ts +++ b/src/common/__tests__/hotbar-store.test.ts @@ -156,6 +156,13 @@ describe("HotbarStore", () => { expect(hotbarStore.getActive().items.length).toEqual(12); }); + it("initially adds catalog entity as first item", () => { + const hotbarStore = HotbarStore.createInstance(); + + hotbarStore.load(); + expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog"); + }); + it("adds items", () => { const hotbarStore = HotbarStore.createInstance(); @@ -163,7 +170,7 @@ describe("HotbarStore", () => { hotbarStore.addToHotbar(testCluster); const items = hotbarStore.getActive().items.filter(Boolean); - expect(items.length).toEqual(1); + expect(items.length).toEqual(2); }); it("removes items", () => { @@ -172,6 +179,7 @@ describe("HotbarStore", () => { hotbarStore.load(); hotbarStore.addToHotbar(testCluster); hotbarStore.removeFromHotbar("test"); + hotbarStore.removeFromHotbar("catalog-entity"); const items = hotbarStore.getActive().items.filter(Boolean); expect(items.length).toEqual(0); @@ -185,7 +193,7 @@ describe("HotbarStore", () => { hotbarStore.removeFromHotbar("invalid uid"); const items = hotbarStore.getActive().items.filter(Boolean); - expect(items.length).toEqual(1); + expect(items.length).toEqual(2); }); it("moves item to empty cell", () => { @@ -196,12 +204,12 @@ describe("HotbarStore", () => { hotbarStore.addToHotbar(minikubeCluster); hotbarStore.addToHotbar(awsCluster); - expect(hotbarStore.getActive().items[5]).toBeNull(); + expect(hotbarStore.getActive().items[6]).toBeNull(); hotbarStore.restackItems(1, 5); expect(hotbarStore.getActive().items[5]).toBeTruthy(); - expect(hotbarStore.getActive().items[5].entity.uid).toEqual("minikube"); + expect(hotbarStore.getActive().items[5].entity.uid).toEqual("test"); }); it("moves items down", () => { @@ -212,12 +220,12 @@ describe("HotbarStore", () => { hotbarStore.addToHotbar(minikubeCluster); hotbarStore.addToHotbar(awsCluster); - // aws -> test - hotbarStore.restackItems(2, 0); + // aws -> catalog + hotbarStore.restackItems(3, 0); const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null); - expect(items.slice(0, 4)).toEqual(["aws", "test", "minikube", null]); + expect(items.slice(0, 4)).toEqual(["aws", "catalog-entity", "test", "minikube"]); }); it("moves items up", () => { @@ -229,11 +237,11 @@ describe("HotbarStore", () => { hotbarStore.addToHotbar(awsCluster); // test -> aws - hotbarStore.restackItems(0, 2); + hotbarStore.restackItems(1, 3); const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null); - expect(items.slice(0, 4)).toEqual(["minikube", "aws", "test", null]); + expect(items.slice(0, 4)).toEqual(["catalog-entity", "minikube", "aws", "test"]); }); it("does nothing when item moved to same cell", () => { @@ -241,9 +249,9 @@ describe("HotbarStore", () => { hotbarStore.load(); hotbarStore.addToHotbar(testCluster); - hotbarStore.restackItems(0, 0); + hotbarStore.restackItems(1, 1); - expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test"); + expect(hotbarStore.getActive().items[1].entity.uid).toEqual("test"); }); it("new items takes first empty cell", () => { diff --git a/src/common/catalog-entities/general.ts b/src/common/catalog-entities/general.ts new file mode 100644 index 0000000000..5d13eeabee --- /dev/null +++ b/src/common/catalog-entities/general.ts @@ -0,0 +1,76 @@ +/** + * 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 { navigate } from "../../renderer/navigation"; +import { CatalogCategory, CatalogEntity, CatalogEntityMetadata, CatalogEntitySpec, CatalogEntityStatus } from "../catalog"; +import { catalogCategoryRegistry } from "../catalog/catalog-category-registry"; + +interface GeneralEntitySpec extends CatalogEntitySpec { + path: string; + icon?: { + material?: string; + background?: string; + }; +} + +export class GeneralEntity extends CatalogEntity { + public readonly apiVersion = "entity.k8slens.dev/v1alpha1"; + public readonly kind = "General"; + + async onRun() { + navigate(this.spec.path); + } + + public onSettingsOpen(): void { + return; + } + + public onDetailsOpen(): void { + return; + } + + public onContextMenuOpen(): void { + return; + } +} + +export class GeneralCategory extends CatalogCategory { + public readonly apiVersion = "catalog.k8slens.dev/v1alpha1"; + public readonly kind = "CatalogCategory"; + public metadata = { + name: "General", + icon: "settings" + }; + public spec = { + group: "entity.k8slens.dev", + versions: [ + { + name: "v1alpha1", + entityClass: GeneralEntity + } + ], + names: { + kind: "General" + } + }; +} + +catalogCategoryRegistry.add(new GeneralCategory()); diff --git a/src/common/catalog-entities/index.ts b/src/common/catalog-entities/index.ts index 621383c6ec..ee8a41b552 100644 --- a/src/common/catalog-entities/index.ts +++ b/src/common/catalog-entities/index.ts @@ -19,5 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +export * from "./general"; export * from "./kubernetes-cluster"; export * from "./web-link"; diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 1f28f3453f..322675b23a 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -27,9 +27,10 @@ import { requestMain } from "../ipc"; import { CatalogCategory, CatalogCategorySpec } from "../catalog"; import { addClusterURL } from "../routes"; import { app } from "electron"; +import type { CatalogEntitySpec } from "../catalog/catalog-entity"; import { HotbarStore } from "../hotbar-store"; -export type KubernetesClusterPrometheusMetrics = { +export interface KubernetesClusterPrometheusMetrics { address?: { namespace: string; service: string; @@ -37,17 +38,19 @@ export type KubernetesClusterPrometheusMetrics = { prefix: string; }; type?: string; -}; + icon?: { + src?: string; + }; +} -export type KubernetesClusterSpec = { +export interface KubernetesClusterSpec extends CatalogEntitySpec { kubeconfigPath: string; kubeconfigContext: string; - iconData?: string; metrics?: { source: string; prometheus?: KubernetesClusterPrometheusMetrics; } -}; +} export interface KubernetesClusterStatus extends CatalogEntityStatus { phase: "connected" | "disconnected" | "deleting"; diff --git a/src/common/catalog/catalog-entity.ts b/src/common/catalog/catalog-entity.ts index 8b8d04f6cb..bc3e360a7b 100644 --- a/src/common/catalog/catalog-entity.ts +++ b/src/common/catalog/catalog-entity.ts @@ -149,6 +149,7 @@ export interface CatalogEntityAddMenuContext { export type CatalogEntitySpec = Record; + export interface CatalogEntityData< Metadata extends CatalogEntityMetadata = CatalogEntityMetadata, Status extends CatalogEntityStatus = CatalogEntityStatus, diff --git a/src/common/hotbar-store.ts b/src/common/hotbar-store.ts index 6d15ace11e..ab41583b0a 100644 --- a/src/common/hotbar-store.ts +++ b/src/common/hotbar-store.ts @@ -26,6 +26,7 @@ import * as uuid from "uuid"; import isNull from "lodash/isNull"; import { toJS } from "./utils"; import { CatalogEntity } from "./catalog"; +import { catalogEntity } from "../main/catalog-sources/general"; export interface HotbarItem { entity: { @@ -91,6 +92,16 @@ export class HotbarStore extends BaseStore { return this.hotbarIndex(this.activeHotbarId); } + get defaultHotbarInitialItems() { + const { metadata: { uid, name, source } } = catalogEntity; + const initialItem = { entity: { uid, name, source }}; + + return [ + initialItem, + ...Array.from(Array(defaultHotbarCells - 1).fill(null)) + ]; + } + get initialItems() { return [...Array.from(Array(defaultHotbarCells).fill(null))]; } @@ -100,7 +111,7 @@ export class HotbarStore extends BaseStore { this.hotbars = [{ id: uuid.v4(), name: "Default", - items: this.initialItems, + items: this.defaultHotbarInitialItems, }]; } else { this.hotbars = data.hotbars; diff --git a/src/extensions/registries/welcome-menu-registry.ts b/src/extensions/registries/welcome-menu-registry.ts index 7092028459..681de6c8f3 100644 --- a/src/extensions/registries/welcome-menu-registry.ts +++ b/src/extensions/registries/welcome-menu-registry.ts @@ -25,6 +25,7 @@ export interface WelcomeMenuRegistration { title: string | (() => string); icon: string; click: () => void | Promise; + testId?: string; } export class WelcomeMenuRegistry extends BaseRegistry {} diff --git a/src/main/catalog-sources/general.ts b/src/main/catalog-sources/general.ts new file mode 100644 index 0000000000..9f64f46b6d --- /dev/null +++ b/src/main/catalog-sources/general.ts @@ -0,0 +1,72 @@ +/** + * 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 { observable } from "mobx"; +import { GeneralEntity } from "../../common/catalog-entities/general"; +import { catalogURL, preferencesURL } from "../../common/routes"; +import { catalogEntityRegistry } from "../catalog"; + +export const catalogEntity = new GeneralEntity({ + metadata: { + uid: "catalog-entity", + name: "Catalog", + source: "app", + labels: {} + }, + spec: { + path: catalogURL(), + icon: { + material: "view_list", + background: "#3d90ce" + } + }, + status: { + phase: "active", + } +}); + +const preferencesEntity = new GeneralEntity({ + metadata: { + uid: "preferences-entity", + name: "Preferences", + source: "app", + labels: {} + }, + spec: { + path: preferencesURL(), + icon: { + material: "settings", + background: "#3d90ce" + } + }, + status: { + phase: "active", + } +}); + +const generalEntities = observable([ + catalogEntity, + preferencesEntity +]); + +export function initializeGeneralEntities() { + catalogEntityRegistry.addObservableSource("lens:general", generalEntities); +} diff --git a/src/main/catalog-sources/index.ts b/src/main/catalog-sources/index.ts index 81f0182ca7..17f383185b 100644 --- a/src/main/catalog-sources/index.ts +++ b/src/main/catalog-sources/index.ts @@ -21,3 +21,4 @@ export { initializeWeblinks } from "./weblinks"; export { KubeconfigSyncManager } from "./kubeconfig-sync"; +export { initializeGeneralEntities } from "./general"; diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index 5f2ef9899d..66c9bda601 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -119,7 +119,7 @@ export class ClusterManager extends Singleton { entity.spec.metrics.prometheus = prometheus; } - entity.spec.iconData = cluster.preferences.icon; + entity.spec.icon.src = cluster.preferences.icon; catalogEntityRegistry.items.splice(index, 1, entity); } @@ -220,7 +220,8 @@ export function catalogEntityFromCluster(cluster: Cluster) { }, spec: { kubeconfigPath: cluster.kubeConfigPath, - kubeconfigContext: cluster.contextName + kubeconfigContext: cluster.contextName, + icon: {} }, status: { phase: cluster.disconnected ? "disconnected" : "connected", diff --git a/src/main/index.ts b/src/main/index.ts index dc36de80dc..d9013cf1ba 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -132,7 +132,7 @@ app.on("ready", async () => { handleWsUpgrade, req => ClusterManager.getInstance().getClusterForRequest(req), ); - + ClusterManager.createInstance().init(); KubeconfigSyncManager.createInstance(); @@ -182,6 +182,7 @@ app.on("ready", async () => { ipcMainOn(IpcRendererNavigationEvents.LOADED, () => { cleanup.push(pushCatalogToRenderer(catalogEntityRegistry)); KubeconfigSyncManager.getInstance().startSync(); + initializers.initializeGeneralEntities(); startUpdateChecking(); LensProtocolRouterMain.getInstance().rendererLoaded = true; }); diff --git a/src/main/initializers/general-entities.ts b/src/main/initializers/general-entities.ts new file mode 100644 index 0000000000..b2e39d8a39 --- /dev/null +++ b/src/main/initializers/general-entities.ts @@ -0,0 +1,22 @@ +/** + * 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. + */ + +export { initializeGeneralEntities } from "../catalog-sources"; diff --git a/src/main/initializers/index.ts b/src/main/initializers/index.ts index 37ff00bd77..6cd9330bac 100644 --- a/src/main/initializers/index.ts +++ b/src/main/initializers/index.ts @@ -24,3 +24,4 @@ export * from "./metrics-providers"; export * from "./ipc"; export * from "./weblinks"; export * from "./stores"; +export * from "./general-entities"; diff --git a/src/renderer/components/+catalog/catalog-entity-details.tsx b/src/renderer/components/+catalog/catalog-entity-details.tsx index b0df7371a6..1a6f0ab03a 100644 --- a/src/renderer/components/+catalog/catalog-entity-details.tsx +++ b/src/renderer/components/+catalog/catalog-entity-details.tsx @@ -63,7 +63,9 @@ export class CatalogEntityDetails extends Component item.onRun(catalogEntityRunContext)} size={128} /> diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index a55b2ae0af..747adc97c7 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -169,9 +169,12 @@ export class Catalog extends React.Component { uid={item.getId()} title={item.getName()} source={item.source} - icon={item.entity.spec.iconData} + src={item.entity.spec.icon?.src} + material={item.entity.spec.icon?.material} + background={item.entity.spec.icon?.background} onClick={() => this.onDetails(item)} - size={24} /> + size={24} + /> ); } diff --git a/src/renderer/components/+welcome/welcome.tsx b/src/renderer/components/+welcome/welcome.tsx index 703520fa17..d6f24b076b 100644 --- a/src/renderer/components/+welcome/welcome.tsx +++ b/src/renderer/components/+welcome/welcome.tsx @@ -44,7 +44,7 @@ export class Welcome extends React.Component {