diff --git a/package.json b/package.json index c76cdc2835..9a939ae48b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "4.1.0", + "version": "4.1.1", "main": "static/build/main.js", "copyright": "© 2020, Mirantis, Inc.", "license": "MIT", @@ -359,7 +359,7 @@ "webpack-dev-server": "^3.11.0", "webpack-node-externals": "^1.7.2", "what-input": "^5.2.10", - "xterm": "^4.6.0", + "xterm": "^4.10.0", "xterm-addon-fit": "^0.4.0" } } diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index fcbd5ebd6b..6ee34388a2 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -2,7 +2,7 @@ import fs from "fs"; import mockFs from "mock-fs"; import yaml from "js-yaml"; import { Cluster } from "../../main/cluster"; -import { ClusterStore } from "../cluster-store"; +import { ClusterStore, getClusterIdFromHost } from "../cluster-store"; import { workspaceStore } from "../workspace-store"; const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png"); @@ -446,3 +446,27 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => { expect(icon.startsWith("data:;base64,")).toBe(true); }); }); + +describe("getClusterIdFromHost", () => { + const clusterFakeId = "fe540901-0bd6-4f6c-b472-bce1559d7c4a"; + + it("should return undefined for non cluster frame hosts", () => { + expect(getClusterIdFromHost("localhost:45345")).toBeUndefined(); + }); + + it("should return ClusterId for cluster frame hosts", () => { + expect(getClusterIdFromHost(`${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + }); + + it("should return ClusterId for cluster frame hosts with additional subdomains", () => { + expect(getClusterIdFromHost(`abc.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + expect(getClusterIdFromHost(`abc.def.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + expect(getClusterIdFromHost(`abc.def.ghi.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + expect(getClusterIdFromHost(`abc.def.ghi.jkl.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.vwx.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.vwx.yz.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); + }); +}); diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index d8bd28f1e8..4000684d16 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -354,10 +354,11 @@ export class ClusterStore extends BaseStore { export const clusterStore = ClusterStore.getInstance(); -export function getClusterIdFromHost(hostname: string): ClusterId { - const subDomains = hostname.split(":")[0].split("."); +export function getClusterIdFromHost(host: string): ClusterId | undefined { + // e.g host == "%clusterId.localhost:45345" + const subDomains = host.split(":")[0].split("."); - return subDomains.slice(-2)[0]; // e.g host == "%clusterId.localhost:45345" + return subDomains.slice(-2, -1)[0]; // ClusterId or undefined } export function getClusterFrameUrl(clusterId: ClusterId) { @@ -365,7 +366,7 @@ export function getClusterFrameUrl(clusterId: ClusterId) { } export function getHostedClusterId() { - return getClusterIdFromHost(location.hostname); + return getClusterIdFromHost(location.host); } export function getHostedCluster(): Cluster { diff --git a/src/main/shell-session.ts b/src/main/shell-session.ts index be04649a31..9e5af371f7 100644 --- a/src/main/shell-session.ts +++ b/src/main/shell-session.ts @@ -108,7 +108,7 @@ export class ShellSession extends EventEmitter { if(isWindows) { env["SystemRoot"] = process.env.SystemRoot; - env["PTYSHELL"] = "powershell.exe"; + env["PTYSHELL"] = process.env.SHELL || "powershell.exe"; env["PATH"] = pathStr; } else if(typeof(process.env.SHELL) != "undefined") { env["PTYSHELL"] = process.env.SHELL; diff --git a/src/renderer/components/kube-object/kube-object-details.tsx b/src/renderer/components/kube-object/kube-object-details.tsx index 40a247c2d8..b07b0753fb 100644 --- a/src/renderer/components/kube-object/kube-object-details.tsx +++ b/src/renderer/components/kube-object/kube-object-details.tsx @@ -13,11 +13,21 @@ import { CrdResourceDetails } from "../+custom-resources"; import { KubeObjectMenu } from "./kube-object-menu"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; +/** + * Used to store `object.selfLink` to show more info about resource in the details panel. + */ export const kubeDetailsUrlParam = createPageParam({ name: "kube-details", isSystem: true, }); +/** + * Used to highlight last active/selected table row with the resource. + * + * @example + * If we go to "Nodes (page) -> Node (details) -> Pod (details)", + * last clicked Node should be "active" while Pod details are shown). + */ export const kubeSelectedUrlParam = createPageParam({ name: "kube-selected", isSystem: true, @@ -26,8 +36,8 @@ export const kubeSelectedUrlParam = createPageParam({ } }); -export function showDetails(details = "", resetSelected = true) { - const detailsUrl = getDetailsUrl(details, resetSelected); +export function showDetails(selfLink = "", resetSelected = true) { + const detailsUrl = getDetailsUrl(selfLink, resetSelected); navigation.merge({ search: detailsUrl }); } @@ -36,18 +46,18 @@ export function hideDetails() { showDetails(); } -export function getDetailsUrl(details: string, resetSelected = false) { - const detailsUrl = kubeDetailsUrlParam.toSearchString({ value: details }); +export function getDetailsUrl(selfLink: string, resetSelected = false, mergeGlobals = true) { + const params = new URLSearchParams(mergeGlobals ? navigation.searchParams : ""); + + params.set(kubeDetailsUrlParam.urlName, selfLink); if (resetSelected) { - const params = new URLSearchParams(detailsUrl); - - params.delete(kubeSelectedUrlParam.name); - - return `?${params.toString()}`; + params.delete(kubeSelectedUrlParam.urlName); + } else { + params.set(kubeSelectedUrlParam.urlName, kubeSelectedUrlParam.get()); } - return detailsUrl; + return `?${params}`; } export interface KubeObjectDetailsProps { diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index 023048cb73..987112a25c 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -109,9 +109,11 @@ export abstract class KubeObjectStore extends ItemSt return api.list({}, this.query); } - const isLoadingAll = this.context.allNamespaces.every(ns => namespaces.includes(ns)); + const isLoadingAll = this.context.allNamespaces?.length > 1 + && this.context.cluster.accessibleNamespaces.length === 0 + && this.context.allNamespaces.every(ns => namespaces.includes(ns)); - if (isLoadingAll && this.context.cluster.accessibleNamespaces.length === 0) { + if (isLoadingAll) { this.loadedNamespaces = []; return api.list({}, this.query); diff --git a/src/renderer/navigation/page-param.ts b/src/renderer/navigation/page-param.ts index e73da03c44..50f6008738 100644 --- a/src/renderer/navigation/page-param.ts +++ b/src/renderer/navigation/page-param.ts @@ -20,7 +20,7 @@ export class PageParam { static SYSTEM_PREFIX = "lens-"; readonly name: string; - protected urlName: string; + readonly urlName: string; constructor(readonly init: PageParamInit | PageSystemParamInit, protected history: IObservableHistory) { const { isSystem, name } = init as PageSystemParamInit; diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md index 8c43fcda18..add0d44ab3 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,10 +2,16 @@ Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights! -## 4.1.0 (current version) +## 4.1.1 (current version) **Upgrade note:** Where have all my pods gone? Namespaced Kubernetes resources are now initially shown only for the "default" namespace. Use the namespaces selector to add more. +- Fix an issue where users with rights to a single namespace were seeing an empty dashboard +- Windows: use SHELL for terminal if set +- Keep highlighted table row during navigation in the details panel + +## 4.1.0 + - Change: list views default to a namespace (instead of listing resources from all namespaces) - Command palette - Generic logs view with Pod selector diff --git a/yarn.lock b/yarn.lock index 16ac2ca4f8..2cc97b2383 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14384,10 +14384,10 @@ xterm-addon-fit@^0.4.0: resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz#06e0c5d0a6aaacfb009ef565efa1c81e93d90193" integrity sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w== -xterm@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0.tgz#1b49b32e546409c110fbe8ece0b4a388504a937d" - integrity sha512-98211RIDrAECqpsxs6gbilwMcxLtxSDIvtzZUIqP1xIByXtuccJ4pmMhHGJATZeEGe/reARPMqwPINK8T7jGZg== +xterm@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.10.0.tgz#fc4f554e3e718aff9b83622e858e64b0953067bb" + integrity sha512-Wn66I8YpSVkgP3R95GjABC6Eb21pFfnCSnyIqKIIoUI13ohvwd0KGVzUDfyEFfSAzKbPJfrT2+vt7SfUXBZQKQ== y18n@^3.2.1: version "3.2.1"