From 7e8cc2122cd99435bfeff8ceb336e054ff2b075c Mon Sep 17 00:00:00 2001 From: Panu Horsmalahti Date: Mon, 28 Jun 2021 16:48:02 +0300 Subject: [PATCH 1/5] Fix extension enabled status after installation (#3199) Signed-off-by: Panu Horsmalahti --- src/extensions/extension-loader.ts | 4 ++++ src/renderer/components/+extensions/extensions.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index be7c9e5c17..af69a6bef8 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -192,6 +192,10 @@ export class ExtensionLoader extends Singleton { } } + setIsEnabled(lensExtensionId: LensExtensionId, isEnabled: boolean) { + this.extensions.get(lensExtensionId).isEnabled = isEnabled; + } + protected async initMain() { this.isLoaded = true; this.loadOnMain(); diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 20707eac18..b135487343 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -278,7 +278,7 @@ async function unpackExtension(request: InstallRequestValidated, disposeDownload await when(() => ExtensionLoader.getInstance().userExtensions.has(id)); // Enable installed extensions by default. - ExtensionLoader.getInstance().userExtensions.get(id).isEnabled = true; + ExtensionLoader.getInstance().setIsEnabled(id, true); Notifications.ok(

Extension {displayName} successfully installed!

From 2788025feacf99bb16110ddb6241efbccc18f5b5 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 28 Jun 2021 11:14:55 -0400 Subject: [PATCH 2/5] Switch to deterministic cluster IDs (#3201) --- package.json | 2 +- src/common/cluster-store.ts | 2 +- src/migrations/cluster-store/5.0.0-beta.13.ts | 120 ++++++++++++++++++ src/migrations/cluster-store/index.ts | 2 + src/migrations/hotbar-store/5.0.0-beta.10.ts | 4 +- src/migrations/utils.ts | 26 ++++ 6 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 src/migrations/cluster-store/5.0.0-beta.13.ts create mode 100644 src/migrations/utils.ts diff --git a/package.json b/package.json index ebe48438c4..1ad83a1e35 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.0.0-beta.12", + "version": "5.0.0-beta.13", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index baa7fc1409..b637bddac3 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -72,7 +72,7 @@ export interface ClusterModel { workspace?: string; /** User context in kubeconfig */ - contextName?: string; + contextName: string; /** Preferences */ preferences?: ClusterPreferences; diff --git a/src/migrations/cluster-store/5.0.0-beta.13.ts b/src/migrations/cluster-store/5.0.0-beta.13.ts new file mode 100644 index 0000000000..09628ed234 --- /dev/null +++ b/src/migrations/cluster-store/5.0.0-beta.13.ts @@ -0,0 +1,120 @@ +/** + * 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 { ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../../common/cluster-store"; +import { MigrationDeclaration, migrationLog } from "../helpers"; +import { generateNewIdFor } from "../utils"; +import path from "path"; +import { app } from "electron"; +import { moveSync, removeSync } from "fs-extra"; + +function mergePrometheusPreferences(left: ClusterPrometheusPreferences, right: ClusterPrometheusPreferences): ClusterPrometheusPreferences { + if (left.prometheus && left.prometheusProvider) { + return { + prometheus: left.prometheus, + prometheusProvider: left.prometheusProvider, + }; + } + + if (right.prometheus && right.prometheusProvider) { + return { + prometheus: right.prometheus, + prometheusProvider: right.prometheusProvider, + }; + } + + return {}; +} + +function mergePreferences(left: ClusterPreferences, right: ClusterPreferences): ClusterPreferences { + return { + terminalCWD: left.terminalCWD || right.terminalCWD || undefined, + clusterName: left.clusterName || right.clusterName || undefined, + iconOrder: left.iconOrder || right.iconOrder || undefined, + icon: left.icon || right.icon || undefined, + httpsProxy: left.httpsProxy || right.httpsProxy || undefined, + hiddenMetrics: mergeSet(left.hiddenMetrics ?? [], right.hiddenMetrics ?? []), + ...mergePrometheusPreferences(left, right), + }; +} + +function mergeLabels(left: Record, right: Record): Record { + return { + ...right, + ...left + }; +} + +function mergeSet(left: Iterable, right: Iterable): string[] { + return [...new Set([...left, ...right])]; +} + +function mergeClusterModel(prev: ClusterModel, right: Omit): ClusterModel { + return { + id: prev.id, + kubeConfigPath: prev.id, + contextName: prev.contextName, + preferences: mergePreferences(prev.preferences ?? {}, right.preferences ?? {}), + metadata: prev.metadata, + labels: mergeLabels(prev.labels ?? {}, right.labels ?? {}), + accessibleNamespaces: mergeSet(prev.accessibleNamespaces ?? [], right.accessibleNamespaces ?? []), + }; +} + +function moveStorageFolder({ folder, newId, oldId }: { folder: string, newId: string, oldId: string }): void { + const oldPath = path.resolve(folder, `${oldId}.json`); + const newPath = path.resolve(folder, `${newId}.json`); + + try { + moveSync(oldPath, newPath); + } catch (error) { + if (String(error).includes("dest already exists")) { + migrationLog(`Multiple old lens-local-storage files for newId=${newId}. Removing ${oldId}.json`); + removeSync(oldPath); + } + } +} + +export default { + version: "5.0.0-beta.13", + run(store) { + const folder = path.resolve(app.getPath("userData"), "lens-local-storage"); + + const oldClusters: ClusterModel[] = store.get("clusters"); + const clusters = new Map(); + + for (const { id: oldId, ...cluster } of oldClusters) { + const newId = generateNewIdFor(cluster); + + if (clusters.has(newId)) { + clusters.set(newId, mergeClusterModel(clusters.get(newId), cluster)); + } else { + clusters.set(newId, { + ...cluster, + id: newId, + }); + moveStorageFolder({ folder, newId, oldId }); + } + } + + store.set("clusters", [...clusters.values()]); + } +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/index.ts b/src/migrations/cluster-store/index.ts index 9bf870825c..b68e098a16 100644 --- a/src/migrations/cluster-store/index.ts +++ b/src/migrations/cluster-store/index.ts @@ -31,6 +31,7 @@ import version270Beta0 from "./2.7.0-beta.0"; import version270Beta1 from "./2.7.0-beta.1"; import version360Beta1 from "./3.6.0-beta.1"; import version500Beta10 from "./5.0.0-beta.10"; +import version500Beta13 from "./5.0.0-beta.13"; import snap from "./snap"; export default joinMigrations( @@ -42,5 +43,6 @@ export default joinMigrations( version270Beta1, version360Beta1, version500Beta10, + version500Beta13, snap, ); diff --git a/src/migrations/hotbar-store/5.0.0-beta.10.ts b/src/migrations/hotbar-store/5.0.0-beta.10.ts index 1586f5c700..34c3aa34f9 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.10.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.10.ts @@ -19,7 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { createHash } from "crypto"; import { app } from "electron"; import fse from "fs-extra"; import { isNull } from "lodash"; @@ -29,6 +28,7 @@ import type { ClusterStoreModel } from "../../common/cluster-store"; import { defaultHotbarCells, Hotbar, HotbarStore } from "../../common/hotbar-store"; import { catalogEntity } from "../../main/catalog-sources/general"; import type { MigrationDeclaration } from "../helpers"; +import { generateNewIdFor } from "../utils"; interface Pre500WorkspaceStoreModel { workspaces: { @@ -74,7 +74,7 @@ export default { if (workspaceHotbar?.items.length < defaultHotbarCells) { workspaceHotbar.items.push({ entity: { - uid: createHash("md5").update(`${cluster.kubeConfigPath}:${cluster.contextName}`).digest("hex"), + uid: generateNewIdFor(cluster), name: cluster.preferences.clusterName || cluster.contextName, } }); diff --git a/src/migrations/utils.ts b/src/migrations/utils.ts new file mode 100644 index 0000000000..f9f9985e38 --- /dev/null +++ b/src/migrations/utils.ts @@ -0,0 +1,26 @@ +/** + * 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 { createHash } from "crypto"; + +export function generateNewIdFor(cluster: { kubeConfigPath: string, contextName: string }): string { + return createHash("md5").update(`${cluster.kubeConfigPath}:${cluster.contextName}`).digest("hex"); +} From 0b88df9b369e0b1f08c8a0f4d5431a1e2b534c55 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 28 Jun 2021 11:28:18 -0400 Subject: [PATCH 3/5] HotbarIcon's refering to unknown entities should be removalable (#3190) * HotbarIcon's refering to unknown entities should be removalable * Allow menu to open when icon is disabled * Add cursor context-menu Signed-off-by: Sebastian Malton --- .../components/hotbar/hotbar-icon.scss | 4 ++++ src/renderer/components/hotbar/hotbar-icon.tsx | 18 ++++++++---------- src/renderer/components/hotbar/hotbar-menu.tsx | 15 +++++++-------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/renderer/components/hotbar/hotbar-icon.scss b/src/renderer/components/hotbar/hotbar-icon.scss index 57c5ed8928..225a054011 100644 --- a/src/renderer/components/hotbar/hotbar-icon.scss +++ b/src/renderer/components/hotbar/hotbar-icon.scss @@ -49,6 +49,10 @@ cursor: default; filter: grayscale(0.7); + &.contextMenuAvailable { + cursor: context-menu; + } + &:hover { &:not(.active) { box-shadow: none; diff --git a/src/renderer/components/hotbar/hotbar-icon.tsx b/src/renderer/components/hotbar/hotbar-icon.tsx index a24e9bb237..b739fa2b4c 100644 --- a/src/renderer/components/hotbar/hotbar-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-icon.tsx @@ -95,7 +95,7 @@ export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: Hotba }; return ( -
+
0 })}>
{renderIcon()} @@ -110,19 +110,17 @@ export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: Hotba toggleEvent="contextmenu" position={{right: true, bottom: true }} // FIXME: position does not work open={() => { - if (!disabled) { - onMenuOpen?.(); - toggleMenu(); - } + onMenuOpen?.(); + toggleMenu(); }} close={() => toggleMenu()}> - { menuItems.map((menuItem) => { - return ( - onMenuItemClick(menuItem) }> + { + menuItems.map((menuItem) => ( + onMenuItemClick(menuItem)}> {menuItem.title} - ); - })} + )) + }
); diff --git a/src/renderer/components/hotbar/hotbar-menu.tsx b/src/renderer/components/hotbar/hotbar-menu.tsx index e9be18990f..dd7267f213 100644 --- a/src/renderer/components/hotbar/hotbar-menu.tsx +++ b/src/renderer/components/hotbar/hotbar-menu.tsx @@ -27,7 +27,7 @@ import { HotbarEntityIcon } from "./hotbar-entity-icon"; import { cssNames, IClassName } from "../../utils"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { defaultHotbarCells, HotbarItem, HotbarStore } from "../../../common/hotbar-store"; -import { CatalogEntity, CatalogEntityContextMenu, catalogEntityRunContext } from "../../api/catalog-entity"; +import { CatalogEntity, catalogEntityRunContext } from "../../api/catalog-entity"; import { DragDropContext, Draggable, Droppable, DropResult } from "react-beautiful-dnd"; import { HotbarSelector } from "./hotbar-selector"; import { HotbarCell } from "./hotbar-cell"; @@ -110,12 +110,6 @@ export class HotbarMenu extends React.Component { renderGrid() { return this.items.map((item, index) => { const entity = this.getEntity(item); - const disabledMenuItems: CatalogEntityContextMenu[] = [ - { - title: "Unpin from Hotbar", - onClick: () => this.removeItem(item.entity.uid) - } - ]; return ( @@ -163,7 +157,12 @@ export class HotbarMenu extends React.Component { uid={`hotbar-icon-${item.entity.uid}`} title={item.entity.name} source={item.entity.source} - menuItems={disabledMenuItems} + menuItems={[ + { + title: "Unpin from Hotbar", + onClick: () => this.removeItem(item.entity.uid) + } + ]} disabled size={40} /> From c0dbd5878d3114639aa1ce506a1f5d68c32a6d28 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 28 Jun 2021 18:28:54 +0300 Subject: [PATCH 4/5] Filtering out clusters by metadata.id (#3200) Signed-off-by: Alex Andreev --- src/migrations/hotbar-store/5.0.0-beta.10.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/migrations/hotbar-store/5.0.0-beta.10.ts b/src/migrations/hotbar-store/5.0.0-beta.10.ts index 34c3aa34f9..41b4aeb081 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.10.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.10.ts @@ -21,7 +21,7 @@ import { app } from "electron"; import fse from "fs-extra"; -import { isNull } from "lodash"; +import { isNull, uniqBy } from "lodash"; import path from "path"; import * as uuid from "uuid"; import type { ClusterStoreModel } from "../../common/cluster-store"; @@ -47,6 +47,7 @@ export default { const workspaceStoreData: Pre500WorkspaceStoreModel = fse.readJsonSync(path.join(userDataPath, "lens-workspace-store.json")); const { clusters }: ClusterStoreModel = fse.readJSONSync(path.join(userDataPath, "lens-cluster-store.json")); const workspaceHotbars = new Map(); // mapping from WorkspaceId to HotBar + const uniqueClusters = uniqBy(clusters, "metadata.id"); // Filtering out duplicated clusters for (const { id, name } of workspaceStoreData.workspaces) { workspaceHotbars.set(id, { @@ -68,7 +69,7 @@ export default { }); } - for (const cluster of clusters) { + for (const cluster of uniqueClusters) { const workspaceHotbar = workspaceHotbars.get(cluster.workspace); if (workspaceHotbar?.items.length < defaultHotbarCells) { From 10e1e69137e71833ee595742b1f60107d87d56f1 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 28 Jun 2021 14:56:06 -0400 Subject: [PATCH 5/5] release v5.0.0-beta.13 (#3203) Signed-off-by: Sebastian Malton