From 125a0730074b0fc4637280b7f47f4705ff666f61 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 19 Oct 2021 09:19:25 -0400 Subject: [PATCH 01/37] Add context menu into sidebar (#4044) --- integration/__tests__/cluster-pages.tests.ts | 384 +++++++++--------- .../catalog-entities/kubernetes-cluster.ts | 8 +- src/common/catalog/catalog-entity.ts | 5 +- src/common/routes/cluster.ts | 2 +- src/renderer/components/app.tsx | 2 + .../components/hotbar/hotbar-entity-icon.tsx | 12 +- src/renderer/components/layout/sidebar.tsx | 26 ++ src/renderer/navigation/events.ts | 1 + 8 files changed, 243 insertions(+), 197 deletions(-) diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index be032afab3..bd31a80102 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -36,7 +36,7 @@ function getSidebarSelectors(itemId: string) { return { expandSubMenu: `${root} .nav-item`, - subMenuLink: (href: string) => `[data-testid=cluster-sidebar] .sub-menu a[href^="/${href}"]`, + subMenuLink: (href: string) => `[data-testid=cluster-sidebar] .sub-menu a[href^="${href}"]`, }; } @@ -73,7 +73,7 @@ function isTopPageTest(test: CommonPageTest): test is TopPageTest { const commonPageTests: CommonPageTest[] = [{ page: { name: "Cluster", - href: "cluster", + href: "/overview", expectedSelector: "div.ClusterOverview div.label", expectedText: "CPU" } @@ -81,153 +81,161 @@ const commonPageTests: CommonPageTest[] = [{ { page: { name: "Nodes", - href: "nodes", + href: "/nodes", expectedSelector: "h5.title", expectedText: "Nodes" } }, { drawerId: "workloads", - pages: [{ - name: "Overview", - href: "workloads", - expectedSelector: "h5.box", - expectedText: "Overview" - }, - { - name: "Pods", - href: "pods", - expectedSelector: "h5.title", - expectedText: "Pods" - }, - { - name: "Deployments", - href: "deployments", - expectedSelector: "h5.title", - expectedText: "Deployments" - }, - { - name: "DaemonSets", - href: "daemonsets", - expectedSelector: "h5.title", - expectedText: "Daemon Sets" - }, - { - name: "StatefulSets", - href: "statefulsets", - expectedSelector: "h5.title", - expectedText: "Stateful Sets" - }, - { - name: "ReplicaSets", - href: "replicasets", - expectedSelector: "h5.title", - expectedText: "Replica Sets" - }, - { - name: "Jobs", - href: "jobs", - expectedSelector: "h5.title", - expectedText: "Jobs" - }, - { - name: "CronJobs", - href: "cronjobs", - expectedSelector: "h5.title", - expectedText: "Cron Jobs" - }] + pages: [ + { + name: "Overview", + href: "/workloads", + expectedSelector: "h5.box", + expectedText: "Overview" + }, + { + name: "Pods", + href: "/pods", + expectedSelector: "h5.title", + expectedText: "Pods" + }, + { + name: "Deployments", + href: "/deployments", + expectedSelector: "h5.title", + expectedText: "Deployments" + }, + { + name: "DaemonSets", + href: "/daemonsets", + expectedSelector: "h5.title", + expectedText: "Daemon Sets" + }, + { + name: "StatefulSets", + href: "/statefulsets", + expectedSelector: "h5.title", + expectedText: "Stateful Sets" + }, + { + name: "ReplicaSets", + href: "/replicasets", + expectedSelector: "h5.title", + expectedText: "Replica Sets" + }, + { + name: "Jobs", + href: "/jobs", + expectedSelector: "h5.title", + expectedText: "Jobs" + }, + { + name: "CronJobs", + href: "/cronjobs", + expectedSelector: "h5.title", + expectedText: "Cron Jobs" + }, + ] }, { drawerId: "config", - pages: [{ - name: "ConfigMaps", - href: "configmaps", - expectedSelector: "h5.title", - expectedText: "Config Maps" - }, - { - name: "Secrets", - href: "secrets", - expectedSelector: "h5.title", - expectedText: "Secrets" - }, - { - name: "Resource Quotas", - href: "resourcequotas", - expectedSelector: "h5.title", - expectedText: "Resource Quotas" - }, - { - name: "Limit Ranges", - href: "limitranges", - expectedSelector: "h5.title", - expectedText: "Limit Ranges" - }, - { - name: "HPA", - href: "hpa", - expectedSelector: "h5.title", - expectedText: "Horizontal Pod Autoscalers" - }, - { - name: "Pod Disruption Budgets", - href: "poddisruptionbudgets", - expectedSelector: "h5.title", - expectedText: "Pod Disruption Budgets" - }] + pages: [ + { + name: "ConfigMaps", + href: "/configmaps", + expectedSelector: "h5.title", + expectedText: "Config Maps" + }, + { + name: "Secrets", + href: "/secrets", + expectedSelector: "h5.title", + expectedText: "Secrets" + }, + { + name: "Resource Quotas", + href: "/resourcequotas", + expectedSelector: "h5.title", + expectedText: "Resource Quotas" + }, + { + name: "Limit Ranges", + href: "/limitranges", + expectedSelector: "h5.title", + expectedText: "Limit Ranges" + }, + { + name: "HPA", + href: "/hpa", + expectedSelector: "h5.title", + expectedText: "Horizontal Pod Autoscalers" + }, + { + name: "Pod Disruption Budgets", + href: "/poddisruptionbudgets", + expectedSelector: "h5.title", + expectedText: "Pod Disruption Budgets" + }, + ] }, { drawerId: "networks", - pages: [{ - name: "Services", - href: "services", - expectedSelector: "h5.title", - expectedText: "Services" - }, - { - name: "Endpoints", - href: "endpoints", - expectedSelector: "h5.title", - expectedText: "Endpoints" - }, - { - name: "Ingresses", - href: "ingresses", - expectedSelector: "h5.title", - expectedText: "Ingresses" - }, - { - name: "Network Policies", - href: "network-policies", - expectedSelector: "h5.title", - expectedText: "Network Policies" - }] + pages: [ + { + name: "Services", + href: "/services", + expectedSelector: "h5.title", + expectedText: "Services" + }, + { + name: "Endpoints", + href: "/endpoints", + expectedSelector: "h5.title", + expectedText: "Endpoints" + }, + { + name: "Ingresses", + href: "/ingresses", + expectedSelector: "h5.title", + expectedText: "Ingresses" + }, + { + name: "Network Policies", + href: "/network-policies", + expectedSelector: "h5.title", + expectedText: "Network Policies" + }, + ] }, { drawerId: "storage", - pages: [{ - name: "Persistent Volume Claims", - href: "persistent-volume-claims", - expectedSelector: "h5.title", - expectedText: "Persistent Volume Claims" - }, - { - name: "Persistent Volumes", - href: "persistent-volumes", - expectedSelector: "h5.title", - expectedText: "Persistent Volumes" - }, - { - name: "Storage Classes", - href: "storage-classes", - expectedSelector: "h5.title", - expectedText: "Storage Classes" - }] + pages: [ + { + name: "Persistent Volume Claims", + href: "/persistent-volume-claims", + expectedSelector: "h5.title", + expectedText: "Persistent Volume Claims" + }, + { + name: "Persistent Volumes", + href: "/persistent-volumes", + expectedSelector: "h5.title", + expectedText: "Persistent Volumes" + }, + { + name: "Storage Classes", + href: "/storage-classes", + expectedSelector: "h5.title", + expectedText: "Storage Classes" + }, + ] }, { page: { name: "Namespaces", - href: "namespaces", + href: "/namespaces", expectedSelector: "h5.title", expectedText: "Namespaces" } @@ -235,72 +243,78 @@ const commonPageTests: CommonPageTest[] = [{ { page: { name: "Events", - href: "events", + href: "/events", expectedSelector: "h5.title", expectedText: "Events" } }, { drawerId: "apps", - pages: [{ - name: "Charts", - href: "apps/charts", - expectedSelector: "div.HelmCharts input", - }, - { - name: "Releases", - href: "apps/releases", - expectedSelector: "h5.title", - expectedText: "Releases" - }] + pages: [ + { + name: "Charts", + href: "/apps/charts", + expectedSelector: "div.HelmCharts input", + }, + { + name: "Releases", + href: "/apps/releases", + expectedSelector: "h5.title", + expectedText: "Releases" + }, + ] }, { drawerId: "users", - pages: [{ - name: "Service Accounts", - href: "service-accounts", - expectedSelector: "h5.title", - expectedText: "Service Accounts" - }, - { - name: "Roles", - href: "roles", - expectedSelector: "h5.title", - expectedText: "Roles" - }, - { - name: "Cluster Roles", - href: "cluster-roles", - expectedSelector: "h5.title", - expectedText: "Cluster Roles" - }, - { - name: "Role Bindings", - href: "role-bindings", - expectedSelector: "h5.title", - expectedText: "Role Bindings" - }, - { - name: "Cluster Role Bindings", - href: "cluster-role-bindings", - expectedSelector: "h5.title", - expectedText: "Cluster Role Bindings" - }, - { - name: "Pod Security Policies", - href: "pod-security-policies", - expectedSelector: "h5.title", - expectedText: "Pod Security Policies" - }] + pages: [ + { + name: "Service Accounts", + href: "/service-accounts", + expectedSelector: "h5.title", + expectedText: "Service Accounts" + }, + { + name: "Roles", + href: "/roles", + expectedSelector: "h5.title", + expectedText: "Roles" + }, + { + name: "Cluster Roles", + href: "/cluster-roles", + expectedSelector: "h5.title", + expectedText: "Cluster Roles" + }, + { + name: "Role Bindings", + href: "/role-bindings", + expectedSelector: "h5.title", + expectedText: "Role Bindings" + }, + { + name: "Cluster Role Bindings", + href: "/cluster-role-bindings", + expectedSelector: "h5.title", + expectedText: "Cluster Role Bindings" + }, + { + name: "Pod Security Policies", + href: "/pod-security-policies", + expectedSelector: "h5.title", + expectedText: "Pod Security Policies" + }, + ] }, { drawerId: "custom-resources", - pages: [{ - name: "Definitions", - href: "crd/definitions", - expectedSelector: "h5.title", - expectedText: "Custom Resources" - }] + pages: [ + { + name: "Definitions", + href: "/crd/definitions", + expectedSelector: "h5.title", + expectedText: "Custom Resources" + }, + ] }]; utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { @@ -321,7 +335,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { for (const test of commonPageTests) { if (isTopPageTest(test)) { const { href, expectedText, expectedSelector } = test.page; - const menuButton = await frame.waitForSelector(`a[href^="/${href}"]`); + const menuButton = await frame.waitForSelector(`a[href^="${href}"]`); await menuButton.click(); await frame.waitForSelector(`${expectedSelector} >> text='${expectedText}'`); diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 31e0251966..8a3c3e0858 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -23,10 +23,11 @@ import { catalogCategoryRegistry } from "../catalog/catalog-category-registry"; import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; import { clusterActivateHandler, clusterDisconnectHandler } from "../cluster-ipc"; import { ClusterStore } from "../cluster-store"; -import { requestMain } from "../ipc"; +import { broadcastMessage, requestMain } from "../ipc"; import { CatalogCategory, CatalogCategorySpec } from "../catalog"; import { app } from "electron"; import type { CatalogEntitySpec } from "../catalog/catalog-entity"; +import { IpcRendererNavigationEvents } from "../../renderer/navigation/events"; export interface KubernetesClusterPrometheusMetrics { address?: { @@ -114,7 +115,10 @@ export class KubernetesCluster extends CatalogEntity context.navigate(`/entity/${this.metadata.uid}/settings`) + onClick: () => broadcastMessage( + IpcRendererNavigationEvents.NAVIGATE_IN_APP, + `/entity/${this.metadata.uid}/settings`, + ), }); } diff --git a/src/common/catalog/catalog-entity.ts b/src/common/catalog/catalog-entity.ts index cbe59bae57..9d549f89de 100644 --- a/src/common/catalog/catalog-entity.ts +++ b/src/common/catalog/catalog-entity.ts @@ -172,7 +172,10 @@ export interface CatalogEntitySettingsMenu { } export interface CatalogEntityContextMenuContext { - navigate: (url: string) => void; + /** + * Navigate to the specified pathname + */ + navigate: (pathname: string) => void; menuItems: CatalogEntityContextMenu[]; } diff --git a/src/common/routes/cluster.ts b/src/common/routes/cluster.ts index 3a39ccd22a..2ba39ae53f 100644 --- a/src/common/routes/cluster.ts +++ b/src/common/routes/cluster.ts @@ -23,7 +23,7 @@ import type { RouteProps } from "react-router"; import { buildURL } from "../utils/buildUrl"; export const clusterRoute: RouteProps = { - path: "/cluster" + path: "/overview" }; export const clusterURL = buildURL(clusterRoute.path); diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 59bf2ea065..d126e5260f 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -72,6 +72,7 @@ import type { ClusterId } from "../../common/cluster-types"; import { watchHistoryState } from "../remote-helpers/history-updater"; import { unmountComponentAtNode } from "react-dom"; import { PortForwardDialog } from "../port-forward"; +import { DeleteClusterDialog } from "./delete-cluster-dialog"; @observer export class App extends React.Component { @@ -230,6 +231,7 @@ export class App extends React.Component { + diff --git a/src/renderer/components/hotbar/hotbar-entity-icon.tsx b/src/renderer/components/hotbar/hotbar-entity-icon.tsx index 24405da556..b3ed9df942 100644 --- a/src/renderer/components/hotbar/hotbar-entity-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-entity-icon.tsx @@ -44,20 +44,16 @@ interface Props extends DOMAttributes { @observer export class HotbarEntityIcon extends React.Component { - @observable private contextMenu: CatalogEntityContextMenuContext; + @observable private contextMenu: CatalogEntityContextMenuContext = { + menuItems: [], + navigate: (url: string) => navigate(url), + }; constructor(props: Props) { super(props); makeObservable(this); } - componentDidMount() { - this.contextMenu = { - menuItems: [], - navigate: (url: string) => navigate(url), - }; - } - get kindIcon() { const className = "badge"; const category = catalogCategoryRegistry.getCategoryForEntity(this.props.entity); diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index ed9b76a8d1..0cde9b3d62 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -42,6 +42,9 @@ import * as routes from "../../../common/routes"; import { Config } from "../+config"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { HotbarIcon } from "../hotbar/hotbar-icon"; +import { makeObservable, observable } from "mobx"; +import type { CatalogEntityContextMenuContext } from "../../../common/catalog"; +import { HotbarStore } from "../../../common/hotbar-store"; interface Props { className?: string; @@ -50,6 +53,15 @@ interface Props { @observer export class Sidebar extends React.Component { static displayName = "Sidebar"; + @observable private contextMenu: CatalogEntityContextMenuContext = { + menuItems: [], + navigate, + }; + + constructor(props: Props) { + super(props); + makeObservable(this); + } async componentDidMount() { crdStore.reloadAll(); @@ -194,6 +206,20 @@ export class Sidebar extends React.Component { src={spec.icon?.src} className="mr-5" onClick={() => navigate("/")} + menuItems={this.contextMenu.menuItems} + onMenuOpen={() => { + const hotbarStore = HotbarStore.getInstance(); + const isAddedToActive = HotbarStore.getInstance().isAddedToActive(this.clusterEntity); + const title = isAddedToActive + ? "Remove from Hotbar" + : "Add to Hotbar"; + const onClick = isAddedToActive + ? () => hotbarStore.removeFromHotbar(metadata.uid) + : () => hotbarStore.addToHotbar(this.clusterEntity); + + this.contextMenu.menuItems = [{ title, onClick }]; + this.clusterEntity.onContextMenuOpen(this.contextMenu); + }} />
{metadata.name} diff --git a/src/renderer/navigation/events.ts b/src/renderer/navigation/events.ts index eaf3741b34..6435bfde1f 100644 --- a/src/renderer/navigation/events.ts +++ b/src/renderer/navigation/events.ts @@ -63,6 +63,7 @@ function bindClusterManagerRouteEvents() { ipcRendererOn(IpcRendererNavigationEvents.NAVIGATE_IN_APP, (event, url: string) => { logger.info(`[IPC]: navigate to ${url}`, { currentLocation: location.href }); navigate(url); + window.focus(); // make sure that the main frame is focused }); } From cbffb856503b4ff01fa04d9b451eb970e607611c Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 19 Oct 2021 09:22:06 -0400 Subject: [PATCH 02/37] Disable kubectl download mirror options when not available (#3956) --- src/common/user-store/preferences-helpers.ts | 24 +++++++++++++++++-- src/main/kubectl.ts | 14 ++++------- .../+preferences/kubectl-binaries.tsx | 11 +++++---- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/common/user-store/preferences-helpers.ts b/src/common/user-store/preferences-helpers.ts index 807bf8ca4c..72e08faa42 100644 --- a/src/common/user-store/preferences-helpers.ts +++ b/src/common/user-store/preferences-helpers.ts @@ -136,12 +136,32 @@ const allowErrorReporting: PreferenceDescription = { }, }; +export interface DownloadMirror { + url: string; + label: string; + platforms: Set; +} + +export const defaultPackageMirror = "default"; +export const packageMirrors = new Map([ + [defaultPackageMirror, { + url: "https://storage.googleapis.com/kubernetes-release/release", + label: "Default (Google)", + platforms: new Set(["darwin", "win32", "linux"]), + }], + ["china", { + url: "https://mirror.azure.cn/kubernetes/kubectl", + label: "China (Azure)", + platforms: new Set(["win32", "linux"]), + }], +]); + const downloadMirror: PreferenceDescription = { fromStore(val) { - return val ?? "default"; + return packageMirrors.has(val) ? val : defaultPackageMirror; }, toStore(val) { - if (!val || val === "default") { + if (!val || val === defaultPackageMirror) { return undefined; } diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 4695e8272e..846133d828 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -32,6 +32,7 @@ import { getBundledKubectlVersion } from "../common/utils/app-version"; import { isDevelopment, isWindows, isTestEnv } from "../common/vars"; import { SemVer } from "semver"; import { getPath } from "../common/utils/getPath"; +import { defaultPackageMirror, packageMirrors } from "../common/user-store/preferences-helpers"; const bundledVersion = getBundledKubectlVersion(); const kubectlMap: Map = new Map([ @@ -51,10 +52,6 @@ const kubectlMap: Map = new Map([ ["1.20", "1.20.8"], ["1.21", bundledVersion] ]); -const packageMirrors: Map = new Map([ - ["default", "https://storage.googleapis.com/kubernetes-release/release"], - ["china", "https://mirror.azure.cn/kubernetes/kubectl"] -]); let bundledPath: string; const initScriptVersionString = "# lens-initscript v3\n"; @@ -389,12 +386,9 @@ export class Kubectl { } protected getDownloadMirror() { - const mirror = packageMirrors.get(UserStore.getInstance().downloadMirror); + // MacOS packages are only available from default - if (mirror) { - return mirror; - } - - return packageMirrors.get("default"); // MacOS packages are only available from default + return packageMirrors.get(UserStore.getInstance().downloadMirror) + ?? packageMirrors.get(defaultPackageMirror); } } diff --git a/src/renderer/components/+preferences/kubectl-binaries.tsx b/src/renderer/components/+preferences/kubectl-binaries.tsx index 226ef23c1e..85b699a840 100644 --- a/src/renderer/components/+preferences/kubectl-binaries.tsx +++ b/src/renderer/components/+preferences/kubectl-binaries.tsx @@ -27,17 +27,17 @@ import { observer } from "mobx-react"; import { bundledKubectlPath } from "../../../main/kubectl"; import { SelectOption, Select } from "../select"; import { FormSwitch, Switcher } from "../switch"; +import { packageMirrors } from "../../../common/user-store/preferences-helpers"; export const KubectlBinaries = observer(() => { const userStore = UserStore.getInstance(); const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || ""); const [binariesPath, setBinariesPath] = useState(userStore.kubectlBinariesPath || ""); const pathValidator = downloadPath ? InputValidators.isPath : undefined; - - const downloadMirrorOptions: SelectOption[] = [ - { value: "default", label: "Default (Google)" }, - { value: "china", label: "China (Azure)" }, - ]; + const downloadMirrorOptions: SelectOption[] = Array.from( + packageMirrors.entries(), + ([value, { label, platforms }]) => ({ value, label, platforms }) + ); const save = () => { userStore.downloadBinariesPath = downloadPath; @@ -68,6 +68,7 @@ export const KubectlBinaries = observer(() => { value={userStore.downloadMirror} onChange={({ value }: SelectOption) => userStore.downloadMirror = value} disabled={!userStore.downloadKubectlBinaries} + isOptionDisabled={({ platforms }) => !platforms.has(process.platform)} themeName="lens" /> From fa21b63c7747c3f9d6afbd90ef7cfdda4daa29cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 09:26:39 -0400 Subject: [PATCH 03/37] Bump esbuild from 0.12.29 to 0.13.8 (#4070) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9d15c8d669..57ccf68318 100644 --- a/package.json +++ b/package.json @@ -330,7 +330,7 @@ "electron": "^13.5.1", "electron-builder": "^22.11.11", "electron-notarize": "^0.3.0", - "esbuild": "^0.12.24", + "esbuild": "^0.13.8", "esbuild-loader": "^2.15.1", "eslint": "^7.32.0", "eslint-plugin-header": "^3.1.1", diff --git a/yarn.lock b/yarn.lock index 39409f0ca8..a4139bccc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5465,6 +5465,61 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +esbuild-android-arm64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.8.tgz#c20e875c3c98164b1ffba9b28637bdf96f5e9e7c" + integrity sha512-AilbChndywpk7CdKkNSZ9klxl+9MboLctXd9LwLo3b0dawmOF/i/t2U5d8LM6SbT1Xw36F8yngSUPrd8yPs2RA== + +esbuild-darwin-64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.8.tgz#f46e6b471ddbf62265234808a6a1aa91df18a417" + integrity sha512-b6sdiT84zV5LVaoF+UoMVGJzR/iE2vNUfUDfFQGrm4LBwM/PWXweKpuu6RD9mcyCq18cLxkP6w/LD/w9DtX3ng== + +esbuild-darwin-arm64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.8.tgz#a991157a6013facd4f2e14159b7da52626c90154" + integrity sha512-R8YuPiiJayuJJRUBG4H0VwkEKo6AvhJs2m7Tl0JaIer3u1FHHXwGhMxjJDmK+kXwTFPriSysPvcobXC/UrrZCQ== + +esbuild-freebsd-64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.8.tgz#301601d2e443ad458960e359b402a17d9500be9d" + integrity sha512-zBn6urrn8FnKC+YSgDxdof9jhPCeU8kR/qaamlV4gI8R3KUaUK162WYM7UyFVAlj9N0MyD3AtB+hltzu4cysTw== + +esbuild-freebsd-arm64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.8.tgz#039a63acc12ec0892006c147ea221e55f9125a9f" + integrity sha512-pWW2slN7lGlkx0MOEBoUGwRX5UgSCLq3dy2c8RIOpiHtA87xAUpDBvZK10MykbT+aMfXc0NI2lu1X+6kI34xng== + +esbuild-linux-32@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.8.tgz#c537b67d7e694b60bfa2786581412838c6ba0284" + integrity sha512-T0I0ueeKVO/Is0CAeSEOG9s2jeNNb8jrrMwG9QBIm3UU18MRB60ERgkS2uV3fZ1vP2F8i3Z2e3Zju4lg9dhVmw== + +esbuild-linux-64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.8.tgz#0092fc8a064001a777bfa0e3b425bb8be8f96e6a" + integrity sha512-Bm8SYmFtvfDCIu9sjKppFXzRXn2BVpuCinU1ChTuMtdKI/7aPpXIrkqBNOgPTOQO9AylJJc1Zw6EvtKORhn64w== + +esbuild-linux-arm64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.8.tgz#5cd3f2bb924212971482e8dbc25c4afd09b28110" + integrity sha512-X4pWZ+SL+FJ09chWFgRNO3F+YtvAQRcWh0uxKqZSWKiWodAB20flsW/OWFYLXBKiVCTeoGMvENZS/GeVac7+tQ== + +esbuild-linux-arm@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.8.tgz#ad634f96bf2975536907aeb9fdb75a3194f4ddce" + integrity sha512-4/HfcC40LJ4GPyboHA+db0jpFarTB628D1ifU+/5bunIgY+t6mHkJWyxWxAAE8wl/ZIuRYB9RJFdYpu1AXGPdg== + +esbuild-linux-mips64le@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.8.tgz#57857edfebf9bf65766dc8be1637f2179c990572" + integrity sha512-o7e0D+sqHKT31v+mwFircJFjwSKVd2nbkHEn4l9xQ1hLR+Bv8rnt3HqlblY3+sBdlrOTGSwz0ReROlKUMJyldA== + +esbuild-linux-ppc64le@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.8.tgz#fdb82a059a5b86bb10fb42091b4ebcf488b9cd46" + integrity sha512-eZSQ0ERsWkukJp2px/UWJHVNuy0lMoz/HZcRWAbB6reoaBw7S9vMzYNUnflfL3XA6WDs+dZn3ekHE4Y2uWLGig== + esbuild-loader@^2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.15.1.tgz#5a3940f5d20317f1a35720efa33e933f97c923e9" @@ -5478,11 +5533,64 @@ esbuild-loader@^2.15.1: type-fest "^1.4.0" webpack-sources "^2.2.0" -esbuild@^0.12.21, esbuild@^0.12.24: +esbuild-netbsd-64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.8.tgz#d7879e7123d3b2c04754ece8bd061aa6866deeff" + integrity sha512-gZX4kP7gVvOrvX0ZwgHmbuHczQUwqYppxqtoyC7VNd80t5nBHOFXVhWo2Ad/Lms0E8b+wwgI/WjZFTCpUHOg9Q== + +esbuild-openbsd-64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.8.tgz#88b280b6cb0a3f6adb60abf27fc506c506a35cf0" + integrity sha512-afzza308X4WmcebexbTzAgfEWt9MUkdTvwIa8xOu4CM2qGbl2LanqEl8/LUs8jh6Gqw6WsicEK52GPrS9wvkcw== + +esbuild-sunos-64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.8.tgz#229ae7c7703196a58acd0f0291ad9bebda815d63" + integrity sha512-mWPZibmBbuMKD+LDN23LGcOZ2EawMYBONMXXHmbuxeT0XxCNwadbCVwUQ/2p5Dp5Kvf6mhrlIffcnWOiCBpiVw== + +esbuild-windows-32@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.8.tgz#892d093e32a21c0c9135e5a0ffdc380aeb70e763" + integrity sha512-QsZ1HnWIcnIEApETZWw8HlOhDSWqdZX2SylU7IzGxOYyVcX7QI06ety/aDcn437mwyO7Ph4RrbhB+2ntM8kX8A== + +esbuild-windows-64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.8.tgz#7defd8d79ae3bb7e6f53b65a7190be7daf901686" + integrity sha512-76Fb57B9eE/JmJi1QmUW0tRLQZfGo0it+JeYoCDTSlbTn7LV44ecOHIMJSSgZADUtRMWT9z0Kz186bnaB3amSg== + +esbuild-windows-arm64@0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.8.tgz#e59ae004496fd8a5ab67bfc7945a2e47480d6fb9" + integrity sha512-HW6Mtq5eTudllxY2YgT62MrVcn7oq2o8TAoAvDUhyiEmRmDY8tPwAhb1vxw5/cdkbukM3KdMYtksnUhF/ekWeg== + +esbuild@^0.12.21: version "0.12.29" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.29.tgz#be602db7c4dc78944a9dbde0d1ea19d36c1f882d" integrity sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g== +esbuild@^0.13.8: + version "0.13.8" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.8.tgz#bd7cc51b881ab067789f88e17baca74724c1ec4f" + integrity sha512-A4af7G7YZLfG5OnARJRMtlpEsCkq/zHZQXewgPA864l9D6VjjbH1SuFYK/OSV6BtHwDGkdwyRrX0qQFLnMfUcw== + optionalDependencies: + esbuild-android-arm64 "0.13.8" + esbuild-darwin-64 "0.13.8" + esbuild-darwin-arm64 "0.13.8" + esbuild-freebsd-64 "0.13.8" + esbuild-freebsd-arm64 "0.13.8" + esbuild-linux-32 "0.13.8" + esbuild-linux-64 "0.13.8" + esbuild-linux-arm "0.13.8" + esbuild-linux-arm64 "0.13.8" + esbuild-linux-mips64le "0.13.8" + esbuild-linux-ppc64le "0.13.8" + esbuild-netbsd-64 "0.13.8" + esbuild-openbsd-64 "0.13.8" + esbuild-sunos-64 "0.13.8" + esbuild-windows-32 "0.13.8" + esbuild-windows-64 "0.13.8" + esbuild-windows-arm64 "0.13.8" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" From 283408cd43a9b966f50dfde1f78ab2b20c7b5435 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 09:32:39 -0400 Subject: [PATCH 04/37] Bump marked from 2.0.3 to 2.1.3 (#4059) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 57ccf68318..21ffd9d353 100644 --- a/package.json +++ b/package.json @@ -212,7 +212,7 @@ "jsonpath": "^1.1.1", "lodash": "^4.17.15", "mac-ca": "^1.0.6", - "marked": "^2.0.3", + "marked": "^2.1.3", "md5-file": "^5.0.0", "mobx": "^6.3.0", "mobx-observable-history": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index a4139bccc9..04341be3d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9446,10 +9446,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.3.tgz#3551c4958c4da36897bda2a16812ef1399c8d6b0" - integrity sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA== +marked@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753" + integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA== marked@^3.0.4: version "3.0.6" From 55eceb76b291cbcaab284de8f5a23fe53a40b79b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 09:49:32 -0400 Subject: [PATCH 05/37] Bump @types/webpack-env from 1.16.2 to 1.16.3 (#4076) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 21ffd9d353..a6d50c48bf 100644 --- a/package.json +++ b/package.json @@ -315,7 +315,7 @@ "@types/webdriverio": "^4.13.0", "@types/webpack": "^4.41.31", "@types/webpack-dev-server": "^3.11.6", - "@types/webpack-env": "^1.16.2", + "@types/webpack-env": "^1.16.3", "@types/webpack-node-externals": "^1.7.1", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.29.1", diff --git a/yarn.lock b/yarn.lock index 04341be3d9..7eb4a48a79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2106,10 +2106,10 @@ "@types/webpack" "^4" http-proxy-middleware "^1.0.0" -"@types/webpack-env@^1.16.2": - version "1.16.2" - resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.2.tgz#8db514b059c1b2ae14ce9d7bb325296de6a9a0fa" - integrity sha512-vKx7WNQNZDyJveYcHAm9ZxhqSGLYwoyLhrHjLBOkw3a7cT76sTdjgtwyijhk1MaHyRIuSztcVwrUOO/NEu68Dw== +"@types/webpack-env@^1.16.3": + version "1.16.3" + resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a" + integrity sha512-9gtOPPkfyNoEqCQgx4qJKkuNm/x0R2hKR7fdl7zvTJyHnIisuE/LfvXOsYWL0o3qq6uiBnKZNNNzi3l0y/X+xw== "@types/webpack-node-externals@^1.7.1": version "1.7.1" From e3e7c620a1ae48c3ec6e59a12306b36abd9a0b98 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Tue, 19 Oct 2021 09:50:23 -0400 Subject: [PATCH 06/37] Added Status to the Port Forwarding list items (#4065) Signed-off-by: Jim Ehrismann --- .../+network-port-forwards/port-forwards.scss | 5 +++++ .../+network-port-forwards/port-forwards.tsx | 4 ++++ src/renderer/components/+network/network-mixins.scss | 12 ++++++++++++ src/renderer/port-forward/port-forward-item.ts | 4 ++++ 4 files changed, 25 insertions(+) diff --git a/src/renderer/components/+network-port-forwards/port-forwards.scss b/src/renderer/components/+network-port-forwards/port-forwards.scss index 0c048fc94d..50a3fd3f83 100644 --- a/src/renderer/components/+network-port-forwards/port-forwards.scss +++ b/src/renderer/components/+network-port-forwards/port-forwards.scss @@ -24,5 +24,10 @@ &.warning { @include table-cell-warning; } + + &.status { + @include port-forward-status-colors; + flex: 0.6; + } } } \ No newline at end of file diff --git a/src/renderer/components/+network-port-forwards/port-forwards.tsx b/src/renderer/components/+network-port-forwards/port-forwards.tsx index c056dab454..fb5a1e89ac 100644 --- a/src/renderer/components/+network-port-forwards/port-forwards.tsx +++ b/src/renderer/components/+network-port-forwards/port-forwards.tsx @@ -33,6 +33,7 @@ enum columnId { kind = "kind", port = "port", forwardPort = "forwardPort", + status = "status", } @observer @@ -68,6 +69,7 @@ export class PortForwards extends React.Component { [columnId.kind]: item => item.getKind(), [columnId.port]: item => item.getPort(), [columnId.forwardPort]: item => item.getForwardPort(), + [columnId.status]: item => item.getStatus(), }} searchFilters={[ item => item.getSearchFields(), @@ -79,6 +81,7 @@ export class PortForwards extends React.Component { { title: "Kind", className: "kind", sortBy: columnId.kind, id: columnId.kind }, { title: "Pod Port", className: "port", sortBy: columnId.port, id: columnId.port }, { title: "Local Port", className: "forwardPort", sortBy: columnId.forwardPort, id: columnId.forwardPort }, + { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, ]} renderTableContents={item => [ item.getName(), @@ -86,6 +89,7 @@ export class PortForwards extends React.Component { item.getKind(), item.getPort(), item.getForwardPort(), + { title: item.getStatus(), className: item.getStatus().toLowerCase() }, ]} renderItemMenu={pf => ( Date: Tue, 19 Oct 2021 10:36:49 -0400 Subject: [PATCH 07/37] Sort pod names by convering sub-parts (#3314) --- src/common/utils/array.ts | 44 +++++ src/common/utils/index.ts | 3 +- src/common/utils/iter.ts | 16 ++ src/common/utils/sort-compare.ts | 42 ++++- .../deployment-replicasets.tsx | 2 +- .../components/+workloads-pods/pods.tsx | 4 +- .../table/__tests__/getSorted.test.ts | 155 ++++++++++++++++++ src/renderer/components/table/sorting.ts | 68 ++++++++ src/renderer/components/table/table.tsx | 42 ++--- .../utils/__tests__/converted-name.test.ts | 36 ++++ src/renderer/utils/index.ts | 23 ++- src/renderer/utils/name-parts.ts | 36 ++++ 12 files changed, 427 insertions(+), 44 deletions(-) create mode 100644 src/common/utils/array.ts create mode 100644 src/renderer/components/table/__tests__/getSorted.test.ts create mode 100644 src/renderer/components/table/sorting.ts create mode 100644 src/renderer/utils/__tests__/converted-name.test.ts create mode 100644 src/renderer/utils/name-parts.ts diff --git a/src/common/utils/array.ts b/src/common/utils/array.ts new file mode 100644 index 0000000000..b0561e3eaf --- /dev/null +++ b/src/common/utils/array.ts @@ -0,0 +1,44 @@ +/** + * 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 type Tuple = N extends N ? number extends N ? T[] : _TupleOf : never; +type _TupleOf = R["length"] extends N ? R : _TupleOf; + +/** + * + * @param sources The source arrays + * @yields A tuple of the next element from each of the sources + * @returns The tuple of all the sources as soon as at least one of the sources is exausted + */ +export function* zipStrict(...sources: Tuple): Iterator, Tuple> { + const maxSafeLength = sources.reduce((prev, cur) => Math.min(prev, cur.length), Number.POSITIVE_INFINITY); + + if (!isFinite(maxSafeLength)) { + // There are no sources, thus just return + return [] as Tuple; + } + + for (let i = 0; i < maxSafeLength; i += 1) { + yield sources.map(source => source[i]) as Tuple; + } + + return sources.map(source => source.slice(maxSafeLength)) as Tuple; +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 6035b9f408..fdf91d39c1 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -60,5 +60,6 @@ export * from "./convertMemory"; export * from "./convertCpu"; import * as iter from "./iter"; +import * as array from "./array"; -export { iter }; +export { iter, array }; diff --git a/src/common/utils/iter.ts b/src/common/utils/iter.ts index dd97c8feef..6271a05969 100644 --- a/src/common/utils/iter.ts +++ b/src/common/utils/iter.ts @@ -185,3 +185,19 @@ export function reduce(src: Iterable, reducer: (acc: R, cur: T) => export function join(src: Iterable, connector = ","): string { return reduce(src, (acc, cur) => `${acc}${connector}${cur}`, ""); } + +/** + * Iterate through `src` and return `true` if `fn` returns a thruthy value for every yielded value. + * Otherwise, return `false`. This function short circuits. + * @param src The type to be iterated over + * @param fn A function to check each iteration + */ +export function every(src: Iterable, fn: (val: T) => any): boolean { + for (const val of src) { + if (!fn(val)) { + return false; + } + } + + return true; +} diff --git a/src/common/utils/sort-compare.ts b/src/common/utils/sort-compare.ts index 041a239bb5..799c47f957 100644 --- a/src/common/utils/sort-compare.ts +++ b/src/common/utils/sort-compare.ts @@ -24,16 +24,44 @@ import * as iter from "./iter"; import type { RawHelmChart } from "../k8s-api/endpoints/helm-charts.api"; import logger from "../logger"; -export function sortCompare(left: T, right: T): -1 | 0 | 1 { +export enum Ordering { + LESS = -1, + EQUAL = 0, + GREATER = 1, +} + +/** + * This function switches the direction of `ordering` if `direction` is `"desc"` + * @param ordering The original ordering (assumed to be an "asc" ordering) + * @param direction The new desired direction + */ +export function rectifyOrdering(ordering: Ordering, direction: "asc" | "desc"): Ordering { + if (direction === "desc") { + return -ordering; + } + + return ordering; +} + +/** + * An ascending sorting function + * @param left An item from an array + * @param right An item from an array + * @returns The relative ordering in an ascending manner. + * - Less if left < right + * - Equal if left == right + * - Greater if left > right + */ +export function sortCompare(left: T, right: T): Ordering { if (left < right) { - return -1; + return Ordering.LESS; } if (left === right) { - return 0; + return Ordering.EQUAL; } - return 1; + return Ordering.GREATER; } interface ChartVersion { @@ -41,17 +69,17 @@ interface ChartVersion { __version?: SemVer; } -export function sortCompareChartVersions(left: ChartVersion, right: ChartVersion): -1 | 0 | 1 { +export function sortCompareChartVersions(left: ChartVersion, right: ChartVersion): Ordering { if (left.__version && right.__version) { return semver.compare(right.__version, left.__version); } if (!left.__version && right.__version) { - return 1; + return Ordering.GREATER; } if (left.__version && !right.__version) { - return -1; + return Ordering.LESS; } return sortCompare(left.version, right.version); diff --git a/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx b/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx index 13770b0abf..03c8c47890 100644 --- a/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx @@ -71,11 +71,11 @@ export class DeploymentReplicaSets extends React.Component { diff --git a/src/renderer/components/+workloads-pods/pods.tsx b/src/renderer/components/+workloads-pods/pods.tsx index 448bfa75c8..7724e16bf7 100644 --- a/src/renderer/components/+workloads-pods/pods.tsx +++ b/src/renderer/components/+workloads-pods/pods.tsx @@ -31,7 +31,7 @@ import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { nodesApi, Pod } from "../../../common/k8s-api/endpoints"; import { StatusBrick } from "../status-brick"; -import { cssNames, stopPropagation } from "../../utils"; +import { cssNames, getConvertedParts, stopPropagation } from "../../utils"; import toPairs from "lodash/toPairs"; import startCase from "lodash/startCase"; import kebabCase from "lodash/kebabCase"; @@ -98,7 +98,7 @@ export class Pods extends React.Component { tableId = "workloads_pods" isConfigurable sortingCallbacks={{ - [columnId.name]: pod => pod.getName(), + [columnId.name]: pod => getConvertedParts(pod.getName()), [columnId.namespace]: pod => pod.getNs(), [columnId.containers]: pod => pod.getContainers().length, [columnId.restarts]: pod => pod.getRestartsCount(), diff --git a/src/renderer/components/table/__tests__/getSorted.test.ts b/src/renderer/components/table/__tests__/getSorted.test.ts new file mode 100644 index 0000000000..7480727ce1 --- /dev/null +++ b/src/renderer/components/table/__tests__/getSorted.test.ts @@ -0,0 +1,155 @@ +/** + * 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 { cloneDeep } from "lodash"; +import { getSorted } from "../sorting"; + +describe("Table tests", () => { + describe("getSorted", () => { + it.each([undefined, 5, "", true, {}, []])("should not sort since %j is not a function", () => { + expect(getSorted([1, 2, 4, 3], undefined, "asc")).toStrictEqual([1, 2, 4, 3]); + }); + + it("should sort numerically asc and not touch the original list", () => { + const i = [1, 2, 4, 3]; + + expect(getSorted(i, v => v, "asc")).toStrictEqual([1, 2, 3, 4]); + expect(i).toStrictEqual([1, 2, 4, 3]); + }); + + it("should sort numerically desc and not touch the original list", () => { + const i = [1, 2, 4, 3]; + + expect(getSorted(i, v => v, "desc")).toStrictEqual([4, 3, 2, 1]); + expect(i).toStrictEqual([1, 2, 4, 3]); + }); + + it("should sort numerically asc (by defaul) and not touch the original list", () => { + const i = [1, 2, 4, 3]; + + expect(getSorted(i, v => v, "foobar")).toStrictEqual([1, 2, 3, 4]); + expect(i).toStrictEqual([1, 2, 4, 3]); + }); + + describe("multi-part", () => { + it("should sort each part by its order", () => { + const i = ["a", "c", "b.1", "b.2", "d"]; + + expect(getSorted(i, v => v.split("."), "desc")).toStrictEqual(["d", "c", "b.2", "b.1", "a"]); + expect(i).toStrictEqual(["a", "c", "b.1", "b.2", "d"]); + }); + + it("should be a stable sort", () => { + const i = [{ + val: "a", + k: 1, + }, { + val: "c", + k: 2 + }, { + val: "b.1", + k: 3 + }, { + val: "b.2", + k: 4 + }, { + val: "d", + k: 5 + }, { + val: "b.2", + k: -10 + }]; + const dup = cloneDeep(i); + const expected = [ + { + val: "a", + k: 1, + }, { + val: "b.1", + k: 3 + }, { + val: "b.2", + k: 4 + }, { + val: "b.2", + k: -10 + }, { + val: "c", + k: 2 + }, { + val: "d", + k: 5 + }, + ]; + + expect(getSorted(i, ({ val }) => val.split("."), "asc")).toStrictEqual(expected); + expect(i).toStrictEqual(dup); + }); + + it("should be a stable sort #2", () => { + const i = [{ + val: "a", + k: 1, + }, { + val: "b.2", + k: -10 + }, { + val: "c", + k: 2 + }, { + val: "b.1", + k: 3 + }, { + val: "b.2", + k: 4 + }, { + val: "d", + k: 5 + }]; + const dup = cloneDeep(i); + const expected = [ + { + val: "a", + k: 1, + }, { + val: "b.1", + k: 3 + }, { + val: "b.2", + k: -10 + }, { + val: "b.2", + k: 4 + }, { + val: "c", + k: 2 + }, { + val: "d", + k: 5 + }, + ]; + + expect(getSorted(i, ({ val }) => val.split("."), "asc")).toStrictEqual(expected); + expect(i).toStrictEqual(dup); + }); + }); + }); +}); diff --git a/src/renderer/components/table/sorting.ts b/src/renderer/components/table/sorting.ts new file mode 100644 index 0000000000..ca9ea959fc --- /dev/null +++ b/src/renderer/components/table/sorting.ts @@ -0,0 +1,68 @@ +/** + * 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 { TableSortCallback } from "./table"; +import { array, Ordering, rectifyOrdering, sortCompare } from "../../utils"; + +export function getSorted(rawItems: T[], sortingCallback: TableSortCallback | undefined, orderByRaw: string): T[] { + if (typeof sortingCallback !== "function") { + return rawItems; + } + + const orderBy = orderByRaw === "asc" || orderByRaw === "desc" ? orderByRaw : "asc"; + const sortData = rawItems.map((item, index) => ({ + index, + sortBy: sortingCallback(item), + })); + + sortData.sort((left, right) => { + if (!Array.isArray(left.sortBy) && !Array.isArray(right.sortBy)) { + return rectifyOrdering(sortCompare(left.sortBy, right.sortBy), orderBy); + } + + const leftSortBy = [left.sortBy].flat(); + const rightSortBy = [right.sortBy].flat(); + const zipIter = array.zipStrict(leftSortBy, rightSortBy); + let r = zipIter.next(); + + for (; r.done === false; r = zipIter.next()) { + const [nextL, nextR] = r.value; + + const sortOrder = rectifyOrdering(sortCompare(nextL, nextR), orderBy); + + if (sortOrder !== Ordering.EQUAL) { + return sortOrder; + } + } + + const [leftRest, rightRest] = r.value; + + return leftRest.length - rightRest.length; + }); + + const res = []; + + for (const { index } of sortData) { + res.push(rawItems[index]); + } + + return res; +} diff --git a/src/renderer/components/table/table.tsx b/src/renderer/components/table/table.tsx index bb5e63f570..1d428c62be 100644 --- a/src/renderer/components/table/table.tsx +++ b/src/renderer/components/table/table.tsx @@ -22,9 +22,8 @@ import "./table.scss"; import React from "react"; -import { orderBy } from "lodash"; import { observer } from "mobx-react"; -import { boundMethod, cssNames, noop } from "../../utils"; +import { boundMethod, cssNames } from "../../utils"; import { TableRow, TableRowElem, TableRowProps } from "./table-row"; import { TableHead, TableHeadElem, TableHeadProps } from "./table-head"; import type { TableCellElem } from "./table-cell"; @@ -32,6 +31,7 @@ import { VirtualList } from "../virtual-list"; import { createPageParam } from "../../navigation"; import { getSortParams, setSortParams } from "./table.storage"; import { computed, makeObservable } from "mobx"; +import { getSorted } from "./sorting"; export type TableSortBy = string; export type TableOrderBy = "asc" | "desc" | string; @@ -92,16 +92,22 @@ export class Table extends React.Component> { const { sortable, tableId } = this.props; if (sortable && !tableId) { - console.error("[Table]: sorted table requires props.tableId to be specified"); + console.error("Table must have props.tableId if props.sortable is specified"); } } + @computed get isSortable() { + const { sortable, tableId } = this.props; + + return Boolean(sortable && tableId); + } + @computed get sortParams() { return Object.assign({}, this.props.sortByDefault, getSortParams(this.props.tableId)); } renderHead() { - const { sortable, children } = this.props; + const { children } = this.props; const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[]; const headElem: React.ReactElement = content.find(elem => elem.type === TableHead); @@ -109,7 +115,7 @@ export class Table extends React.Component> { return null; } - if (sortable) { + if (this.isSortable) { const columns = React.Children.toArray(headElem.props.children) as TableCellElem[]; return React.cloneElement(headElem, { @@ -136,14 +142,12 @@ export class Table extends React.Component> { return headElem; } - getSorted(items: any[]) { - const { sortBy, orderBy: order } = this.sortParams; - const sortingCallback = this.props.sortable[sortBy] || noop; + getSorted(rawItems: Item[]) { + const { sortBy, orderBy: orderByRaw } = this.sortParams; - return orderBy(items, sortingCallback, order as any); + return getSorted(rawItems, this.props.sortable[sortBy], orderByRaw); } - @boundMethod protected onSort({ sortBy, orderBy }: TableSortParams) { setSortParams(this.props.tableId, { sortBy, orderBy }); const { sortSyncWithUrl, onSort } = this.props; @@ -153,9 +157,7 @@ export class Table extends React.Component> { orderByUrlParam.set(orderBy); } - if (onSort) { - onSort({ sortBy, orderBy }); - } + onSort?.({ sortBy, orderBy }); } @boundMethod @@ -183,12 +185,12 @@ export class Table extends React.Component> { } renderRows() { - const { sortable, noItems, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props; + const { noItems, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props; const content = this.getContent(); let rows: React.ReactElement[] = content.filter(elem => elem.type === TableRow); let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items]; - if (sortable) { + if (this.isSortable) { sortedItems = this.getSorted(sortedItems); if (rows.length) { @@ -228,15 +230,13 @@ export class Table extends React.Component> { } render() { - const { selectable, scrollable, sortable, autoSize, virtual } = this.props; - let { className } = this.props; - - className = cssNames("Table flex column", className, { - selectable, scrollable, sortable, autoSize, virtual, + const { selectable, scrollable, autoSize, virtual, className } = this.props; + const classNames = cssNames("Table flex column", className, { + selectable, scrollable, sortable: this.isSortable, autoSize, virtual, }); return ( -
+
{this.renderHead()} {this.renderRows()}
diff --git a/src/renderer/utils/__tests__/converted-name.test.ts b/src/renderer/utils/__tests__/converted-name.test.ts new file mode 100644 index 0000000000..35ac991b4b --- /dev/null +++ b/src/renderer/utils/__tests__/converted-name.test.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 { getConvertedParts } from "../name-parts"; + +describe("getConvertedParts", () => { + it.each([ + ["hello", ["hello"]], + ["hello.goodbye", ["hello", "goodbye"]], + ["hello.1", ["hello", 1]], + ["3-hello.1", [3, "hello", 1]], + ["3_hello.1", [3, "hello", 1]], + ["3_hello.1/foobar", [3, "hello", 1, "foobar"]], + ["3_hello.1/foobar\\new", [3, "hello", 1, "foobar", "new"]], + ])("Splits '%s' as into %j", (input, output) => { + expect(getConvertedParts(input)).toEqual(output); + }); +}); diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts index 537aab13ba..5c765d4c8a 100755 --- a/src/renderer/utils/index.ts +++ b/src/renderer/utils/index.ts @@ -22,19 +22,18 @@ // Common usage utils & helpers export * from "../../common/utils"; - -export * from "./cssVar"; -export * from "./cssNames"; export * from "../../common/event-emitter"; -export * from "./saveFile"; -export * from "./prevDefault"; -export * from "./storageHelper"; -export * from "./createStorage"; -export * from "./interval"; + export * from "./copyToClipboard"; -export * from "./isReactNode"; -export * from "../../common/utils/convertMemory"; -export * from "../../common/utils/convertCpu"; -export * from "./metricUnitsToNumber"; +export * from "./createStorage"; +export * from "./cssNames"; +export * from "./cssVar"; export * from "./display-booleans"; +export * from "./interval"; export * from "./isMiddleClick"; +export * from "./isReactNode"; +export * from "./metricUnitsToNumber"; +export * from "./name-parts"; +export * from "./prevDefault"; +export * from "./saveFile"; +export * from "./storageHelper"; diff --git a/src/renderer/utils/name-parts.ts b/src/renderer/utils/name-parts.ts new file mode 100644 index 0000000000..e60948bc0f --- /dev/null +++ b/src/renderer/utils/name-parts.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. + */ + +/** + * Split `name` into the parts seperated by one or more of (-, _, or .) and + * the sections can be converted to numbers will be converted + * @param name A kube object name + * @returns The converted parts of the name + */ +export function getConvertedParts(name: string): (string | number)[] { + return name + .split(/[-_./\\]+/) + .map(part => { + const converted = +part; + + return isNaN(converted) ? part : converted; + }); +} From 5ffa1de7e6634ea111160c1e88c4e5a94392e26c Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Tue, 19 Oct 2021 11:06:02 -0400 Subject: [PATCH 08/37] added checkbox for optionally opening browser in the port-forward dialog (#4068) Signed-off-by: Jim Ehrismann --- src/renderer/port-forward/port-forward-dialog.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/renderer/port-forward/port-forward-dialog.tsx b/src/renderer/port-forward/port-forward-dialog.tsx index 6ba05ec1c4..100fdf5b94 100644 --- a/src/renderer/port-forward/port-forward-dialog.tsx +++ b/src/renderer/port-forward/port-forward-dialog.tsx @@ -32,6 +32,7 @@ import { cssNames } from "../utils"; import { addPortForward, modifyPortForward } from "./port-forward.store"; import type { ForwardedPort } from "./port-forward-item"; import { openPortForward } from "."; +import { Checkbox } from "../components/checkbox"; interface Props extends Partial { } @@ -57,7 +58,7 @@ export class PortForwardDialog extends Component { makeObservable(this); } - static open(portForward: ForwardedPort, options : PortForwardDialogOpenOptions = { openInBrowser: false }) { + static open(portForward: ForwardedPort, options: PortForwardDialogOpenOptions = { openInBrowser: false }) { dialogState.isOpen = true; dialogState.data = portForward; dialogState.openInBrowser = options.openInBrowser; @@ -105,7 +106,7 @@ export class PortForwardDialog extends Component { portForward.forwardPort = desiredPort; port = await addPortForward(portForward); } - + if (dialogState.openInBrowser) { portForward.forwardPort = port; openPortForward(portForward); @@ -120,7 +121,7 @@ export class PortForwardDialog extends Component { renderContents() { return ( <> -
+
Local port to forward from: @@ -134,6 +135,13 @@ export class PortForwardDialog extends Component { onChange={this.changePort} />
+ dialogState.openInBrowser = value} + />
); From 991ff252a5c790ca9794445e57d8ff5b1e99472e Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Tue, 19 Oct 2021 11:48:25 -0400 Subject: [PATCH 09/37] Further tweaks to release guide (#4066) --- Makefile | 4 ---- RELEASE_GUIDE.md | 12 +++++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index b22527daec..9fdb588215 100644 --- a/Makefile +++ b/Makefile @@ -39,10 +39,6 @@ dev: binaries/client build-extensions static/build/LensDev.html lint: yarn lint -.PHONY: release-version -release-version: - npm version $(CMD_ARGS) --git-tag-version false - .PHONY: tag-release tag-release: scripts/tag-release.sh $(CMD_ARGS) diff --git a/RELEASE_GUIDE.md b/RELEASE_GUIDE.md index b11a47c134..a2f7a6db32 100644 --- a/RELEASE_GUIDE.md +++ b/RELEASE_GUIDE.md @@ -2,15 +2,17 @@ Lens releases are built by CICD automatically on git tags. The typical release process flow is the following: +1. It is recommended to perform the release process from a folder used solely meant for creating releases (i.e. not your dev folder), with the Lens repo initialized (origin set to https://github.com/lensapp/lens). + 1. If doing a patch release checkout the `release/vMAJOR.MINOR` branch for the appropriate `MAJOR`/`MINOR` version and manually `cherry-pick` the PRs required for the patch that were commited to master. If there are any conflicts they must be resolved manually. If necessary, get assistance from the PR authors. -1. From a clean and up to date `master` (or `release/vMAJOR.MINOR` if doing a patch release) run `make release-version ` where `` is one of the following: +1. From a clean and up to date `master` (or `release/vMAJOR.MINOR` if doing a patch release) run `npm version --git-tag-version false` where `` is one of the following: - `major` - `minor` - `patch` - - `premajor` - - `preminor` - - `prepatch` + - `premajor` [--preid=] + - `preminor` [--preid=] + - `prepatch` [--preid=] - `prerelease [--preid=]` where `` is generally one of: @@ -18,7 +20,7 @@ Lens releases are built by CICD automatically on git tags. The typical release p - `beta` - `rc` - This assumes origin is set to https://github.com/lensapp/lens.git. If not then set GIT_REMOTE to the remote that is set to https://github.com/lensapp/lens.git. For example run `GIT_REMOTE=upstream make release-version ...` + This assumes origin is set to https://github.com/lensapp/lens.git. If not then set GIT_REMOTE to the remote that is set to https://github.com/lensapp/lens.git. For example run `GIT_REMOTE=upstream npm version ...` 1. Open the PR (git should have printed a link to GitHub in the console) with the contents of all the accepted PRs since the last release. The PR description needs to be filled with the draft release description. From https://github.com/lensapp/lens click on Releases, the draft release should be first in the list, click `Edit` and copy/paste the markdown to the PR description. Add the `skip-changelog` label and click `Create Pull Request`. If this is a patch release be sure to set the PR base branch to `release/vMAJOR.MINOR` instead of `master`. 1. After the PR is accepted and passes CI (and before merging), go to the same branch and run `make tag-release` (set GIT_REMOTE if necessary). This additionally triggers the azure jobs to build the binaries and put them on S3. 1. If the CI fails at this stage the problem needs to be fixed. Sometimes an azure job fails due to outside service issues (e.g. Apple signing occasionally fails), in which case the specific azure job can be rerun from https://dev.azure.com/lensapp/lensapp/_build. Otherwise changes to the codebase may need to be done and committed to the release branch and pushed to https://github.com/lensapp/lens. CI will run again. As well the release tag needs to be manually set to this new commit. You can do something like: From b0ddb5eb77f70dbe3823a1e271235611cf7aafa5 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 20 Oct 2021 15:04:02 +0300 Subject: [PATCH 10/37] Moving Catalog Details panel under TopBar (#4087) * Moving Catalog Details panel under TopBar Signed-off-by: Alex Andreev * Fine-tuning drawer height css rule Signed-off-by: Alex Andreev --- ...scss => catalog-entity-details.module.css} | 45 +++++++++++-------- .../+catalog/catalog-entity-details.tsx | 14 +++--- src/renderer/components/drawer/drawer.scss | 2 +- 3 files changed, 35 insertions(+), 26 deletions(-) rename src/renderer/components/+catalog/{catalog-entity-details.scss => catalog-entity-details.module.css} (68%) diff --git a/src/renderer/components/+catalog/catalog-entity-details.scss b/src/renderer/components/+catalog/catalog-entity-details.module.css similarity index 68% rename from src/renderer/components/+catalog/catalog-entity-details.scss rename to src/renderer/components/+catalog/catalog-entity-details.module.css index f63e7c3ba3..27b2c69701 100644 --- a/src/renderer/components/+catalog/catalog-entity-details.scss +++ b/src/renderer/components/+catalog/catalog-entity-details.module.css @@ -19,25 +19,32 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - .CatalogEntityDetails { - .EntityMetadata { - margin-right: $margin; - } - .EntityIcon.box.top.left { - margin-right: $margin * 2; +.entityDetails { + /* Move Details panel under TopBar line */ + height: calc(100% - var(--main-layout-header)); + margin-top: var(--main-layout-header); +} - .IconHint { - text-align: center; - font-size: var(--font-size-small); - text-transform: uppercase; - margin-top: $margin; - cursor: default; - user-select: none; - opacity: 0.5; - } +.metadata { + margin-right: var(--margin); +} - div * { - font-size: 1.5em; +.entityIcon { + margin-right: calc(var(--margin) * 3); + + .avatar { + :global(.MuiAvatar-root) { + font-size: 3ch; } - } - } + } + + .hint { + text-align: center; + font-size: var(--font-size-small); + text-transform: uppercase; + margin-top: var(--margin); + cursor: default; + user-select: none; + opacity: 0.5; + } +} diff --git a/src/renderer/components/+catalog/catalog-entity-details.tsx b/src/renderer/components/+catalog/catalog-entity-details.tsx index bff73a3fa2..40bc63fdee 100644 --- a/src/renderer/components/+catalog/catalog-entity-details.tsx +++ b/src/renderer/components/+catalog/catalog-entity-details.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./catalog-entity-details.scss"; +import styles from "./catalog-entity-details.module.css"; import React, { Component } from "react"; import { observer } from "mobx-react"; import { Drawer, DrawerItem } from "../drawer"; @@ -30,6 +30,7 @@ import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import { HotbarIcon } from "../hotbar/hotbar-icon"; import type { CatalogEntityItem } from "./catalog-entity-item"; import { isDevelopment } from "../../../common/vars"; +import { cssNames } from "../../utils"; interface Props { item: CatalogEntityItem | null | undefined; @@ -57,8 +58,8 @@ export class CatalogEntityDetails extends Component {showDetails && ( -
-
+
+
extends Component item.onRun()} size={128} data-testid="detail-panel-hot-bar-icon" + className={styles.avatar} /> {item?.enabled && ( -
+
Click to open
)}
-
+
{item.name} @@ -114,7 +116,7 @@ export class CatalogEntityDetails extends Component Date: Wed, 20 Oct 2021 15:22:19 +0300 Subject: [PATCH 11/37] Replacing icon for Remove from Hotbar action (#4086) Signed-off-by: Alex Andreev --- .../components/+catalog/catalog-entity-drawer-menu.tsx | 4 ++-- src/renderer/components/+catalog/catalog.tsx | 2 +- .../components/+catalog/hotbar-toggle-menu-item.tsx | 2 +- src/renderer/components/icon/push_off.svg | 1 + src/renderer/components/icon/unpin.svg | 6 ------ 5 files changed, 5 insertions(+), 10 deletions(-) create mode 100644 src/renderer/components/icon/push_off.svg delete mode 100644 src/renderer/components/icon/unpin.svg diff --git a/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx b/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx index 2d180bf9db..f356f31a8e 100644 --- a/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx +++ b/src/renderer/components/+catalog/catalog-entity-drawer-menu.tsx @@ -98,8 +98,8 @@ export class CatalogEntityDrawerMenu extends React.Comp } - removeContent={} + addContent={} + removeContent={} /> ); diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 594bb610e3..06fdb5b21b 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -220,7 +220,7 @@ export class Catalog extends React.Component { small className={styles.pinIcon} material={!isItemInHotbar && "push_pin"} - svg={isItemInHotbar && "unpin"} + svg={isItemInHotbar && "push_off"} tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"} onClick={prevDefault(() => isItemInHotbar ? this.removeFromHotbar(item) : this.addToHotbar(item))} /> diff --git a/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx b/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx index 7d679e4d81..00f6f3600f 100644 --- a/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx +++ b/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx @@ -26,7 +26,7 @@ import { MenuItem } from "../menu"; import type { CatalogEntity } from "../../api/catalog-entity"; export function HotbarToggleMenuItem(props: { entity: CatalogEntity, addContent: ReactNode, removeContent: ReactNode }) { - const store = HotbarStore.getInstance(false); + const store = HotbarStore.getInstance(); const add = () => store.addToHotbar(props.entity); const remove = () => store.removeFromHotbar(props.entity.getId()); const [itemInHotbar, setItemInHotbar] = useState(store.isAddedToActive(props.entity)); diff --git a/src/renderer/components/icon/push_off.svg b/src/renderer/components/icon/push_off.svg new file mode 100644 index 0000000000..07221c1cc2 --- /dev/null +++ b/src/renderer/components/icon/push_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/components/icon/unpin.svg b/src/renderer/components/icon/unpin.svg deleted file mode 100644 index f685eb5e92..0000000000 --- a/src/renderer/components/icon/unpin.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - From 38276bbbff6b32fe7e29ebcb8c318de6316c09b4 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 20 Oct 2021 08:50:52 -0400 Subject: [PATCH 12/37] Update helm cache if files have been modified (#3973) --- src/common/utils/sort-compare.ts | 2 +- src/main/helm/helm-chart-manager.ts | 104 ++++++++++++++++++---------- 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/common/utils/sort-compare.ts b/src/common/utils/sort-compare.ts index 799c47f957..7167761151 100644 --- a/src/common/utils/sort-compare.ts +++ b/src/common/utils/sort-compare.ts @@ -111,5 +111,5 @@ export function sortCharts(charts: RawHelmChart[]) { return chartsWithVersion .sort(sortCompareChartVersions) - .map(chart => (delete chart.__version, chart)); + .map(chart => (delete chart.__version, chart as RawHelmChart)); } diff --git a/src/main/helm/helm-chart-manager.ts b/src/main/helm/helm-chart-manager.ts index e99554dcd0..b7afb6ff3d 100644 --- a/src/main/helm/helm-chart-manager.ts +++ b/src/main/helm/helm-chart-manager.ts @@ -24,10 +24,15 @@ import v8 from "v8"; import * as yaml from "js-yaml"; import type { HelmRepo } from "./helm-repo-manager"; import logger from "../logger"; -import { promiseExec } from "../promise-exec"; +import { promiseExecFile } from "../promise-exec"; import { helmCli } from "./helm-cli"; import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api"; -import { sortCharts } from "../../common/utils"; +import { iter, sortCharts } from "../../common/utils"; + +interface ChartCacheEntry { + data: Buffer, + mtimeMs: number, +} export interface HelmCacheFile { apiVersion: string; @@ -35,7 +40,7 @@ export interface HelmCacheFile { } export class HelmChartManager { - static #cache = new Map(); + static #cache = new Map(); private constructor(protected repo: HelmRepo) {} @@ -59,16 +64,17 @@ export class HelmChartManager { } } - private async executeCommand(action: string, name: string, version?: string) { + private async executeCommand(args: string[], name: string, version?: string) { const helm = await helmCli.binaryPath(); - const cmd = [`"${helm}" ${action} ${this.repo.name}/${name}`]; + + args.push(`${this.repo.name}/${name}`); if (version) { - cmd.push("--version", version); + args.push("--version", version); } try { - const { stdout } = await promiseExec(cmd.join(" ")); + const { stdout } = await promiseExecFile(helm, args); return stdout; } catch (error) { @@ -77,47 +83,69 @@ export class HelmChartManager { } public async getReadme(name: string, version?: string) { - return this.executeCommand("show readme", name, version); + return this.executeCommand(["show", "readme"], name, version); } public async getValues(name: string, version?: string) { - return this.executeCommand("show values", name, version); + return this.executeCommand(["show", "values"], name, version); + } + + protected async updateYamlCache() { + const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, "utf-8"); + const cacheFileStats = await fs.promises.stat(this.repo.cacheFilePath); + const data = yaml.load(cacheFile) as string | number | HelmCacheFile; + + if (!data || typeof data !== "object" || typeof data.entries !== "object") { + throw Object.assign(new TypeError("Helm Cache file does not parse correctly"), { file: this.repo.cacheFilePath, data }); + } + + const normalized = normalizeHelmCharts(this.repo.name, data.entries); + + HelmChartManager.#cache.set(this.repo.name, { + data: v8.serialize(normalized), + mtimeMs: cacheFileStats.mtimeMs, + }); } protected async cachedYaml(): Promise { if (!HelmChartManager.#cache.has(this.repo.name)) { - const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, "utf-8"); - const data = yaml.load(cacheFile) as string | number | HelmCacheFile; + await this.updateYamlCache(); + } else { + const newStats = await fs.promises.stat(this.repo.cacheFilePath); + const cacheEntry = HelmChartManager.#cache.get(this.repo.name); - if (typeof data !== "object" || !data) { - return {}; + if (cacheEntry.mtimeMs < newStats.mtimeMs) { + await this.updateYamlCache(); } - - /** - * Do some initial preprocessing on the data, so as to avoid needing to do it later - * 1. Set the repo name - * 2. Normalize the created date - * 3. Filter out deprecated items - */ - - const normalized = Object.fromEntries( - Object.entries(data.entries) - .map(([name, charts]) => [ - name, - sortCharts( - charts.map(chart => ({ - ...chart, - created: Date.parse(chart.created).toString(), - repo: this.repo.name, - })), - ), - ] as const) - .filter(([, charts]) => !charts.every(chart => chart.deprecated)) - ); - - HelmChartManager.#cache.set(this.repo.name, v8.serialize(normalized)); } - return v8.deserialize(HelmChartManager.#cache.get(this.repo.name)); + return v8.deserialize(HelmChartManager.#cache.get(this.repo.name).data); } } + +/** + * Do some initial preprocessing on the data, so as to avoid needing to do it later + * 1. Set the repo name + * 2. Normalize the created date + * 3. Filter out charts that only have deprecated entries + */ +function normalizeHelmCharts(repoName: string, entries: RepoHelmChartList): RepoHelmChartList { + return Object.fromEntries( + iter.filter( + iter.map( + Object.entries(entries), + ([name, charts]) => [ + name, + sortCharts( + charts.map(chart => ({ + ...chart, + created: Date.parse(chart.created).toString(), + repo: repoName, + })), + ), + ] as const + ), + ([, charts]) => !charts.every(chart => chart.deprecated), + ) + ); +} From b4a4cc6be43f3f9939b532e6695b386da53a4ca7 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 20 Oct 2021 08:51:03 -0400 Subject: [PATCH 13/37] Correctly handle empty string valued secrets in SecretDetails (#4079) --- src/common/k8s-api/endpoints/secret.api.ts | 12 +- .../__tests__/secret-details.test.tsx | 49 ++++++++ .../+config-secrets/secret-details.tsx | 117 ++++++++++-------- .../+service-accounts/details.tsx | 5 +- 4 files changed, 125 insertions(+), 58 deletions(-) create mode 100644 src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx diff --git a/src/common/k8s-api/endpoints/secret.api.ts b/src/common/k8s-api/endpoints/secret.api.ts index 99200933ff..7c90c2e9c9 100644 --- a/src/common/k8s-api/endpoints/secret.api.ts +++ b/src/common/k8s-api/endpoints/secret.api.ts @@ -41,12 +41,9 @@ export interface ISecretRef { name: string; } -export interface Secret { +export interface SecretData extends KubeJsonApiData { type: SecretType; - data: { - [prop: string]: string; - token?: string; - }; + data?: Record; } export class Secret extends KubeObject { @@ -54,7 +51,10 @@ export class Secret extends KubeObject { static namespaced = true; static apiBase = "/api/v1/secrets"; - constructor(data: KubeJsonApiData) { + declare type: SecretType; + declare data: Record; + + constructor(data: SecretData) { super(data); autoBind(this); diff --git a/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx new file mode 100644 index 0000000000..292f228cf9 --- /dev/null +++ b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx @@ -0,0 +1,49 @@ +/** + * 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 { SecretDetails } from "../secret-details"; +import { Secret, SecretType } from "../../../../common/k8s-api/endpoints"; + +jest.mock("../../kube-object-meta/kube-object-meta"); + + +describe("SecretDetails tests", () => { + it("should show the visibility toggle when the secret value is ''", () => { + const secret = new Secret({ + apiVersion: "v1", + kind: "secret", + metadata: { + name: "test", + resourceVersion: "1", + uid: "uid" + }, + data: { + foobar: "", + }, + type: SecretType.Opaque, + }); + const result = render(); + + expect(result.getByTestId("foobar-secret-entry").querySelector(".Icon")).toBeDefined(); + }); +}); diff --git a/src/renderer/components/+config-secrets/secret-details.tsx b/src/renderer/components/+config-secrets/secret-details.tsx index a8f4555ee1..30aff8640f 100644 --- a/src/renderer/components/+config-secrets/secret-details.tsx +++ b/src/renderer/components/+config-secrets/secret-details.tsx @@ -22,14 +22,13 @@ import "./secret-details.scss"; import React from "react"; -import isEmpty from "lodash/isEmpty"; import { autorun, observable, makeObservable } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Input } from "../input"; import { Button } from "../button"; import { Notifications } from "../notifications"; -import { base64 } from "../../utils"; +import { base64, ObservableToggleSet } from "../../utils"; import { Icon } from "../icon"; import { secretsStore } from "./secrets.store"; import type { KubeObjectDetailsProps } from "../kube-object-details"; @@ -44,7 +43,7 @@ interface Props extends KubeObjectDetailsProps { export class SecretDetails extends React.Component { @observable isSaving = false; @observable data: { [name: string]: string } = {}; - @observable revealSecret: { [name: string]: boolean } = {}; + revealSecret = new ObservableToggleSet(); constructor(props: Props) { super(props); @@ -58,7 +57,7 @@ export class SecretDetails extends React.Component { if (secret) { this.data = secret.data; - this.revealSecret = {}; + this.revealSecret.clear(); } }) ]); @@ -82,6 +81,69 @@ export class SecretDetails extends React.Component { this.data[name] = encoded ? value : base64.encode(value); }; + renderSecret = ([name, value]: [string, string]) => { + let decodedVal: string | undefined; + + try { + decodedVal = base64.decode(value); + } catch { + /** + * The value failed to be decoded, so don't show the visibility + * toggle until the value is saved + */ + this.revealSecret.delete(name); + } + + const revealSecret = this.revealSecret.has(name); + + if (revealSecret && typeof decodedVal === "string") { + value = decodedVal; + } + + return ( +
+
{name}
+
+ this.editData(name, value, !revealSecret)} + /> + {typeof decodedVal === "string" && ( + this.revealSecret.toggle(name)} + /> + )} +
+
+ ); + }; + + renderData() { + const secrets = Object.entries(this.data); + + if (secrets.length === 0) { + return null; + } + + return ( + <> + + {secrets.map(this.renderSecret)} +
); } diff --git a/src/renderer/components/+user-management/+service-accounts/details.tsx b/src/renderer/components/+user-management/+service-accounts/details.tsx index 3cc12dd24d..ee19079c40 100644 --- a/src/renderer/components/+user-management/+service-accounts/details.tsx +++ b/src/renderer/components/+user-management/+service-accounts/details.tsx @@ -27,7 +27,7 @@ import React from "react"; import { Link } from "react-router-dom"; import { secretsStore } from "../../+config-secrets/secrets.store"; -import { Secret, ServiceAccount } from "../../../../common/k8s-api/endpoints"; +import { Secret, SecretType, ServiceAccount } from "../../../../common/k8s-api/endpoints"; import { DrawerItem, DrawerTitle } from "../../drawer"; import { Icon } from "../../icon"; import type { KubeObjectDetailsProps } from "../../kube-object-details"; @@ -124,7 +124,8 @@ export class ServiceAccountsDetails extends React.Component { uid: null, selfLink: null, resourceVersion: null - } + }, + type: SecretType.Opaque }); } From 32562b11a4c88157af242bc31e405ff2d19c5d56 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 20 Oct 2021 08:51:26 -0400 Subject: [PATCH 14/37] Remove ClusterStore.removedClusters and correctly delete lens storage file (#4077) --- src/common/cluster-store.ts | 10 ---------- src/main/cluster-manager.ts | 17 +--------------- src/main/initializers/ipc.ts | 20 ++++++++++++++++--- .../delete-cluster-dialog.tsx | 1 + src/renderer/utils/createStorage.ts | 11 ---------- 5 files changed, 19 insertions(+), 40 deletions(-) diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 8e8940c644..26806ae239 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -40,7 +40,6 @@ export class ClusterStore extends BaseStore { private static StateChannel = "cluster:state"; clusters = observable.map(); - removedClusters = observable.map(); protected disposer = disposer(); @@ -144,7 +143,6 @@ export class ClusterStore extends BaseStore { protected fromStore({ clusters = [] }: ClusterStoreModel = {}) { const currentClusters = new Map(this.clusters); const newClusters = new Map(); - const removedClusters = new Map(); // update new clusters for (const clusterModel of clusters) { @@ -162,15 +160,7 @@ export class ClusterStore extends BaseStore { } } - // update removed clusters - currentClusters.forEach(cluster => { - if (!newClusters.has(cluster.id)) { - removedClusters.set(cluster.id, cluster); - } - }); - this.clusters.replace(newClusters); - this.removedClusters.replace(removedClusters); } toJSON(): ClusterStoreModel { diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index 8cd6664b6a..c082bc78c9 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -21,7 +21,7 @@ import "../common/cluster-ipc"; import type http from "http"; -import { action, autorun, makeObservable, observable, observe, reaction, toJS } from "mobx"; +import { action, makeObservable, observable, observe, reaction, toJS } from "mobx"; import { Cluster } from "./cluster"; import logger from "./logger"; import { apiKubePrefix } from "../common/vars"; @@ -71,21 +71,6 @@ export class ClusterManager extends Singleton { } }); - // auto-stop removed clusters - autorun(() => { - const removedClusters = Array.from(this.store.removedClusters.values()); - - if (removedClusters.length > 0) { - const meta = removedClusters.map(cluster => cluster.getMeta()); - - logger.info(`${logPrefix} removing clusters`, meta); - removedClusters.forEach(cluster => cluster.disconnect()); - this.store.removedClusters.clear(); - } - }, { - delay: 250 - }); - ipcMainOn("network:offline", this.onNetworkOffline); ipcMainOn("network:online", this.onNetworkOnline); }); diff --git a/src/main/initializers/ipc.ts b/src/main/initializers/ipc.ts index 0eb36b4f3a..c16c3e1fe1 100644 --- a/src/main/initializers/ipc.ts +++ b/src/main/initializers/ipc.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { BrowserWindow, dialog, IpcMainInvokeEvent } from "electron"; +import { app, BrowserWindow, dialog, IpcMainInvokeEvent } from "electron"; import { KubernetesCluster } from "../../common/catalog-entities"; import { clusterFrameMap } from "../../common/cluster-frames"; import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler, clusterSetDeletingHandler, clusterClearDeletingHandler } from "../../common/cluster-ipc"; @@ -32,6 +32,8 @@ import { pushCatalogToRenderer } from "../catalog-pusher"; import { ClusterManager } from "../cluster-manager"; import { ResourceApplier } from "../resource-applier"; import { WindowManager } from "../window-manager"; +import path from "path"; +import { remove } from "fs-extra"; export function initIpcMainHandlers() { ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { @@ -79,9 +81,11 @@ export function initIpcMainHandlers() { } }); - ipcMainHandle(clusterDeleteHandler, (event, clusterId: ClusterId) => { + ipcMainHandle(clusterDeleteHandler, async (event, clusterId: ClusterId) => { appEventBus.emit({ name: "cluster", action: "remove" }); - const cluster = ClusterStore.getInstance().getById(clusterId); + + const clusterStore = ClusterStore.getInstance(); + const cluster = clusterStore.getById(clusterId); if (!cluster) { return; @@ -89,6 +93,16 @@ export function initIpcMainHandlers() { cluster.disconnect(); clusterFrameMap.delete(cluster.id); + + // Remove from the cluster store as well, this should clear any old settings + clusterStore.clusters.delete(cluster.id); + + try { + // remove the local storage file + const localStorageFilePath = path.resolve(app.getPath("userData"), "lens-local-storage", `${cluster.id}.json`); + + await remove(localStorageFilePath); + } catch {} }); ipcMainHandle(clusterSetDeletingHandler, (event, clusterId: string) => { diff --git a/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.tsx b/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.tsx index 01cf35eec8..94ad6c79bd 100644 --- a/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.tsx +++ b/src/renderer/components/delete-cluster-dialog/delete-cluster-dialog.tsx @@ -116,6 +116,7 @@ export class DeleteClusterDialog extends React.Component { await requestMain(clusterDeleteHandler, cluster.id); } catch(error) { Notifications.error(`Cannot remove cluster, failed to process config file. ${error}`); + } finally { await requestMain(clusterClearDeletingHandler, cluster.id); } diff --git a/src/renderer/utils/createStorage.ts b/src/renderer/utils/createStorage.ts index 2e79e35209..e2ff3dc314 100755 --- a/src/renderer/utils/createStorage.ts +++ b/src/renderer/utils/createStorage.ts @@ -25,7 +25,6 @@ import path from "path"; import { comparer, observable, reaction, toJS, when } from "mobx"; import fse from "fs-extra"; import { StorageHelper } from "./storageHelper"; -import { ClusterStore } from "../../common/cluster-store"; import logger from "../../main/logger"; import { getHostedClusterId, getPath } from "../../common/utils"; import { isTestEnv } from "../../common/vars"; @@ -75,11 +74,6 @@ export function createAppStorage(key: string, defaultValue: T, clusterId?: st equals: comparer.structural, // save only when something really changed }); - // remove json-file when cluster deleted - if (clusterId !== undefined) { - when(() => ClusterStore.getInstance(false)?.removedClusters.has(clusterId)).then(removeFile); - } - async function saveFile(state: Record = {}) { logger.info(`${logPrefix} saving ${filePath}`); @@ -92,11 +86,6 @@ export function createAppStorage(key: string, defaultValue: T, clusterId?: st }); } } - - function removeFile() { - logger.debug(`${logPrefix} removing ${filePath}`); - fse.unlink(filePath).catch(Function); - } } return new StorageHelper(key, { From 3d72db636344ed96dc19544b0bdd16cbd22065de Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 20 Oct 2021 16:00:14 +0300 Subject: [PATCH 15/37] Cleanup dependentStores (#4089) --- src/renderer/components/+workloads-daemonsets/daemonsets.tsx | 3 +-- .../components/+workloads-deployments/deployments.tsx | 5 +---- src/renderer/components/+workloads-jobs/jobs.tsx | 3 +-- src/renderer/components/+workloads-pods/pods.tsx | 3 +-- .../components/+workloads-replicasets/replicasets.tsx | 2 ++ .../components/+workloads-statefulsets/statefulsets.tsx | 3 +-- 6 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/renderer/components/+workloads-daemonsets/daemonsets.tsx b/src/renderer/components/+workloads-daemonsets/daemonsets.tsx index e9c4442b15..31f3203113 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonsets.tsx +++ b/src/renderer/components/+workloads-daemonsets/daemonsets.tsx @@ -28,7 +28,6 @@ import type { DaemonSet } from "../../../common/k8s-api/endpoints"; import { eventStore } from "../+events/event.store"; import { daemonSetStore } from "./daemonsets.store"; import { podsStore } from "../+workloads-pods/pods.store"; -import { nodesStore } from "../+nodes/nodes.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { Badge } from "../badge"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; @@ -63,7 +62,7 @@ export class DaemonSets extends React.Component { isConfigurable tableId="workload_daemonsets" className="DaemonSets" store={daemonSetStore} - dependentStores={[podsStore, nodesStore, eventStore]} + dependentStores={[podsStore, eventStore]} // status icon component uses event store sortingCallbacks={{ [columnId.name]: daemonSet => daemonSet.getName(), [columnId.namespace]: daemonSet => daemonSet.getNs(), diff --git a/src/renderer/components/+workloads-deployments/deployments.tsx b/src/renderer/components/+workloads-deployments/deployments.tsx index 28705fd53d..74cbb81d84 100644 --- a/src/renderer/components/+workloads-deployments/deployments.tsx +++ b/src/renderer/components/+workloads-deployments/deployments.tsx @@ -31,9 +31,6 @@ import { Icon } from "../icon"; import { DeploymentScaleDialog } from "./deployment-scale-dialog"; import { ConfirmDialog } from "../confirm-dialog"; import { deploymentStore } from "./deployments.store"; -import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; -import { podsStore } from "../+workloads-pods/pods.store"; -import { nodesStore } from "../+nodes/nodes.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { cssNames } from "../../utils"; @@ -79,7 +76,7 @@ export class Deployments extends React.Component { isConfigurable tableId="workload_deployments" className="Deployments" store={deploymentStore} - dependentStores={[replicaSetStore, podsStore, nodesStore, eventStore]} + dependentStores={[eventStore]} // status icon component uses event store sortingCallbacks={{ [columnId.name]: deployment => deployment.getName(), [columnId.namespace]: deployment => deployment.getNs(), diff --git a/src/renderer/components/+workloads-jobs/jobs.tsx b/src/renderer/components/+workloads-jobs/jobs.tsx index 6dc2362604..caa0b9b9f3 100644 --- a/src/renderer/components/+workloads-jobs/jobs.tsx +++ b/src/renderer/components/+workloads-jobs/jobs.tsx @@ -24,7 +24,6 @@ import "./jobs.scss"; import React from "react"; import { observer } from "mobx-react"; import type { RouteComponentProps } from "react-router"; -import { podsStore } from "../+workloads-pods/pods.store"; import { jobStore } from "./job.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; @@ -51,7 +50,7 @@ export class Jobs extends React.Component { isConfigurable tableId="workload_jobs" className="Jobs" store={jobStore} - dependentStores={[podsStore, eventStore]} + dependentStores={[eventStore]} // status icon component uses event store sortingCallbacks={{ [columnId.name]: job => job.getName(), [columnId.namespace]: job => job.getNs(), diff --git a/src/renderer/components/+workloads-pods/pods.tsx b/src/renderer/components/+workloads-pods/pods.tsx index 7724e16bf7..8cb46e19c9 100644 --- a/src/renderer/components/+workloads-pods/pods.tsx +++ b/src/renderer/components/+workloads-pods/pods.tsx @@ -26,7 +26,6 @@ import { observer } from "mobx-react"; import { Link } from "react-router-dom"; import { podsStore } from "./pods.store"; import type { RouteComponentProps } from "react-router"; -import { volumeClaimStore } from "../+storage-volume-claims/volume-claim.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { nodesApi, Pod } from "../../../common/k8s-api/endpoints"; @@ -94,7 +93,7 @@ export class Pods extends React.Component { return ( { isConfigurable tableId="workload_replicasets" className="ReplicaSets" store={replicaSetStore} + dependentStores={[eventStore]} // status icon component uses event store sortingCallbacks={{ [columnId.name]: replicaSet => replicaSet.getName(), [columnId.namespace]: replicaSet => replicaSet.getNs(), diff --git a/src/renderer/components/+workloads-statefulsets/statefulsets.tsx b/src/renderer/components/+workloads-statefulsets/statefulsets.tsx index c3b5b0e023..82704bbb91 100644 --- a/src/renderer/components/+workloads-statefulsets/statefulsets.tsx +++ b/src/renderer/components/+workloads-statefulsets/statefulsets.tsx @@ -27,7 +27,6 @@ import type { RouteComponentProps } from "react-router"; import type { StatefulSet } from "../../../common/k8s-api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { statefulSetStore } from "./statefulset.store"; -import { nodesStore } from "../+nodes/nodes.store"; import { eventStore } from "../+events/event.store"; import type { KubeObjectMenuProps } from "../kube-object-menu"; import { KubeObjectListLayout } from "../kube-object-list-layout"; @@ -62,7 +61,7 @@ export class StatefulSets extends React.Component { isConfigurable tableId="workload_statefulsets" className="StatefulSets" store={statefulSetStore} - dependentStores={[podsStore, nodesStore, eventStore]} + dependentStores={[podsStore, eventStore]} // status icon component uses event store, details component uses podStore sortingCallbacks={{ [columnId.name]: statefulSet => statefulSet.getName(), [columnId.namespace]: statefulSet => statefulSet.getNs(), From 938cf990e89799117beb521afd2e2a8cef3318b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 09:34:39 -0400 Subject: [PATCH 16/37] Bump @types/react-beautiful-dnd from 13.1.1 to 13.1.2 (#4085) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a6d50c48bf..2e5ce9847a 100644 --- a/package.json +++ b/package.json @@ -293,7 +293,7 @@ "@types/proper-lockfile": "^4.1.2", "@types/randomcolor": "^0.5.6", "@types/react": "^17.0.29", - "@types/react-beautiful-dnd": "^13.1.1", + "@types/react-beautiful-dnd": "^13.1.2", "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.3.1", "@types/react-select": "3.1.2", diff --git a/yarn.lock b/yarn.lock index 7eb4a48a79..1e275af16b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1820,10 +1820,10 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== -"@types/react-beautiful-dnd@^13.1.1": - version "13.1.1" - resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#fb3fe24a334cc757d290e75722e4d3c8368ce3a3" - integrity sha512-1lBBxVSutE8CQM37Jq7KvJwuA94qaEEqsx+G0dnwzG6Sfwf6JGcNeFk5jjjhJli1q2naeMZm+D/dvT/zyX4QPw== +"@types/react-beautiful-dnd@^13.1.2": + version "13.1.2" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz#510405abb09f493afdfd898bf83995dc6385c130" + integrity sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg== dependencies: "@types/react" "*" From b43d019e338a6feab1b52be1de2f2e40dd967195 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 09:34:50 -0400 Subject: [PATCH 17/37] Bump @testing-library/user-event from 13.2.1 to 13.5.0 (#4083) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2e5ce9847a..ab6601689e 100644 --- a/package.json +++ b/package.json @@ -263,7 +263,7 @@ "@testing-library/dom": "^8.9.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.6", - "@testing-library/user-event": "^13.2.1", + "@testing-library/user-event": "^13.5.0", "@types/byline": "^4.2.33", "@types/chart.js": "^2.9.34", "@types/color": "^3.0.2", diff --git a/yarn.lock b/yarn.lock index 1e275af16b..a34e726418 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1262,10 +1262,10 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^7.28.1" -"@testing-library/user-event@^13.2.1": - version "13.2.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.2.1.tgz#7a71a39e50b4a733afbe2916fa2b99966e941f98" - integrity sha512-cczlgVl+krjOb3j1625usarNEibI0IFRJrSWX9UsJ1HKYFgCQv9Nb7QAipUDXl3Xdz8NDTsiS78eAkPSxlzTlw== +"@testing-library/user-event@^13.5.0": + version "13.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" + integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== dependencies: "@babel/runtime" "^7.12.5" From 93ea1a1e24a8e2c524b002d7c37ed0e3434cb5d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 09:34:56 -0400 Subject: [PATCH 18/37] Bump @types/react-table from 7.7.6 to 7.7.7 (#4082) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ab6601689e..24a0f7172c 100644 --- a/package.json +++ b/package.json @@ -297,7 +297,7 @@ "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.3.1", "@types/react-select": "3.1.2", - "@types/react-table": "^7.7.6", + "@types/react-table": "^7.7.7", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", "@types/readable-stream": "^2.3.11", diff --git a/yarn.lock b/yarn.lock index a34e726418..d5350320a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1870,10 +1870,10 @@ "@types/react-dom" "*" "@types/react-transition-group" "*" -"@types/react-table@^7.7.6": - version "7.7.6" - resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.6.tgz#4899ccc46b4a1de08cc1daf120842e37e112e51e" - integrity sha512-ZMFHh1sG5AGDmhVRpz9mgGByGmBFAqnZ7QnyqGa5iAlKtcSC3vb/gul47lM0kJ1uvlawc+qN5k+++pe+GBdJ+g== +"@types/react-table@^7.7.7": + version "7.7.7" + resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.7.tgz#503be1ce2e06857c11b281629f539358d32671f9" + integrity sha512-3l2TP4detx9n5Jt44XhdH7Ku6PYwz6kB83P8W+YcBMUkIHtiw2gsCCcq9c4wyCIcdSwcTlWZ9WqH4PF7Yfbprg== dependencies: "@types/react" "*" From 1f42a5148e02be060acbaf67ff57ca3617a6401f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 20 Oct 2021 10:44:13 -0400 Subject: [PATCH 19/37] Remove api lookup in KubeObjectStore (#4092) --- src/common/k8s-api/kube-object.store.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 7c8118da1f..6cf5ff6705 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -26,7 +26,6 @@ import { autoBind, noop, rejectPromiseBy } from "../utils"; import { KubeObject, KubeStatus } from "./kube-object"; import type { IKubeWatchEvent } from "./kube-watch-api"; import { ItemStore } from "../item.store"; -import { apiManager } from "./api-manager"; import { ensureObjectSelfLink, IKubeApiQueryParams, KubeApi } from "./kube-api"; import { parseKubeApi } from "./kube-api-parse"; import type { KubeJsonApiData } from "./kube-json-api"; @@ -401,12 +400,11 @@ export abstract class KubeObjectStore extends ItemStore for (const { type, object } of this.eventsBuffer.clear()) { const index = items.findIndex(item => item.getId() === object.metadata?.uid); const item = items[index]; - const api = apiManager.getApiByKind(object.kind, object.apiVersion); switch (type) { case "ADDED": case "MODIFIED": - const newItem = new api.objectConstructor(object) as T; + const newItem = new this.api.objectConstructor(object); if (!item) { items.push(newItem); From e16623c82d3fbb8e40c004e3087fd261c1524406 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 20 Oct 2021 11:54:38 -0400 Subject: [PATCH 20/37] Remove unused dep @emeraldpay/hashicon-react (#4091) Signed-off-by: Sebastian Malton --- package.json | 1 - yarn.lock | 29 ----------------------------- 2 files changed, 30 deletions(-) diff --git a/package.json b/package.json index 24a0f7172c..99a801b0b6 100644 --- a/package.json +++ b/package.json @@ -253,7 +253,6 @@ "ws": "^7.5.5" }, "devDependencies": { - "@emeraldpay/hashicon-react": "^0.4.0", "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", diff --git a/yarn.lock b/yarn.lock index d5350320a5..744d913d5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -386,21 +386,6 @@ dir-compare "^2.4.0" fs-extra "^9.0.1" -"@emeraldpay/hashicon-react@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@emeraldpay/hashicon-react/-/hashicon-react-0.4.0.tgz#a05a1c9af23721a81450978fad816908fa3246b7" - integrity sha512-n+84V9R4g84B62NneYg5mz9Vf1xEBURZoyC4G1K9Rs9C7949D3SxgLhuXYyFKkNO5F5qzms5Qr3LGE5QNEdqJg== - dependencies: - "@emeraldpay/hashicon" "^0.4.0" - react "^16.8.0" - -"@emeraldpay/hashicon@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@emeraldpay/hashicon/-/hashicon-0.4.0.tgz#0dcd4db6d589b4eb30495b9995ff66fc9e57f833" - integrity sha512-2EB6Z8gS/DK36+SslIuoHE8wdU6pBqljMTjIYxLC8hBN8n2yhhE+xdmifPjqfqOvF/7Wb5HmoNaPX6RGuBrasg== - dependencies: - js-sha3 "^0.8.0" - "@emotion/cache@^10.0.27", "@emotion/cache@^10.0.9": version "10.0.29" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -8561,11 +8546,6 @@ jpeg-js@^0.4.2: resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== -js-sha3@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -11817,15 +11797,6 @@ react-window@^1.8.5: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" -react@^16.8.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" From f52a2295a244bb0b521ed50c461b6cadac0c4253 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 20 Oct 2021 15:42:44 -0400 Subject: [PATCH 21/37] Fix captitalization in CrdResources component (#4093) --- src/renderer/components/+custom-resources/crd-resources.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/+custom-resources/crd-resources.tsx b/src/renderer/components/+custom-resources/crd-resources.tsx index 7c0e36639a..b8645dfbd1 100644 --- a/src/renderer/components/+custom-resources/crd-resources.tsx +++ b/src/renderer/components/+custom-resources/crd-resources.tsx @@ -89,11 +89,11 @@ export class CrdResources extends React.Component { searchFilters={[ item => item.getSearchFields(), ]} - renderHeaderTitle={crd.getResourceTitle()} + renderHeaderTitle={crd.getResourceKind()} customizeHeader={({ searchProps, ...headerPlaceholders }) => ({ searchProps: { ...searchProps, - placeholder: `Search ${crd.getResourceTitle()}...`, + placeholder: `${crd.getResourceKind()} search ...`, }, ...headerPlaceholders })} From 731f119b1b9dc85d975871402f49486ca0c7c6e9 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:02:20 -0400 Subject: [PATCH 22/37] added port-forward details (#4080) * added port-forward details Signed-off-by: Jim Ehrismann * address review comments, plus added toolbar/menu to port-forward details Drawer Signed-off-by: Jim Ehrismann * added getById() to portForwardStore to eliminate direct items access from outside Signed-off-by: Jim Ehrismann --- src/common/routes/port-forwards.ts | 3 +- .../port-forward-details.scss | 30 +++++ .../port-forward-details.tsx | 107 ++++++++++++++++++ .../+network-port-forwards/port-forwards.tsx | 43 ++++++- .../port-forward/port-forward.store.ts | 10 ++ 5 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/renderer/components/+network-port-forwards/port-forward-details.scss create mode 100644 src/renderer/components/+network-port-forwards/port-forward-details.tsx diff --git a/src/common/routes/port-forwards.ts b/src/common/routes/port-forwards.ts index d05b8583ab..bf644e5003 100644 --- a/src/common/routes/port-forwards.ts +++ b/src/common/routes/port-forwards.ts @@ -23,10 +23,11 @@ import type { RouteProps } from "react-router"; import { buildURL } from "../utils/buildUrl"; export const portForwardsRoute: RouteProps = { - path: "/port-forwards" + path: "/port-forwards/:forwardport?" }; export interface PortForwardsRouteParams { + forwardport?: string; } export const portForwardsURL = buildURL(portForwardsRoute.path); diff --git a/src/renderer/components/+network-port-forwards/port-forward-details.scss b/src/renderer/components/+network-port-forwards/port-forward-details.scss new file mode 100644 index 0000000000..6fcc501884 --- /dev/null +++ b/src/renderer/components/+network-port-forwards/port-forward-details.scss @@ -0,0 +1,30 @@ +/** + * 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. + */ + +.PortForwardDetails { + .SubTitle { + text-transform: none + } + + .status { + @include port-forward-status-colors; + } +} \ No newline at end of file diff --git a/src/renderer/components/+network-port-forwards/port-forward-details.tsx b/src/renderer/components/+network-port-forwards/port-forward-details.tsx new file mode 100644 index 0000000000..4fa4b395f7 --- /dev/null +++ b/src/renderer/components/+network-port-forwards/port-forward-details.tsx @@ -0,0 +1,107 @@ +/** + * 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 "./port-forward-details.scss"; + +import React from "react"; +import { Link } from "react-router-dom"; +import type { PortForwardItem } from "../../port-forward"; +import { Drawer, DrawerItem } from "../drawer"; +import { cssNames } from "../../utils"; +import { podsApi, serviceApi } from "../../../common/k8s-api/endpoints"; +import { getDetailsUrl } from "../kube-detail-params"; +import { PortForwardMenu } from "./port-forward-menu"; + +interface Props { + portForward: PortForwardItem; + hideDetails(): void; +} + +export class PortForwardDetails extends React.Component { + + renderResourceName() { + const { portForward } = this.props; + const name = portForward.getName(); + const api = { + "service": serviceApi, + "pod": podsApi + }[portForward.kind]; + + if (!api) { + return ( + {name} + ); + } + + return ( + + {name} + + ); + } + + renderContent() { + const { portForward } = this.props; + + if (!portForward) return null; + + return ( +
+ + {this.renderResourceName()} + + + {portForward.getNs()} + + + {portForward.getKind()} + + + {portForward.getPort()} + + + {portForward.getForwardPort()} + + + {portForward.getStatus()} + +
+ ); + } + + render() { + const { hideDetails, portForward } = this.props; + const toolbar = ; + + return ( + + {this.renderContent()} + + ); + } +} diff --git a/src/renderer/components/+network-port-forwards/port-forwards.tsx b/src/renderer/components/+network-port-forwards/port-forwards.tsx index fb5a1e89ac..9fdeec4f6a 100644 --- a/src/renderer/components/+network-port-forwards/port-forwards.tsx +++ b/src/renderer/components/+network-port-forwards/port-forwards.tsx @@ -23,9 +23,13 @@ import "./port-forwards.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; +import type { RouteComponentProps } from "react-router-dom"; import { ItemListLayout } from "../item-object-list/item-list-layout"; import { PortForwardItem, portForwardStore } from "../../port-forward"; import { PortForwardMenu } from "./port-forward-menu"; +import { PortForwardsRouteParams, portForwardsURL } from "../../../common/routes"; +import { PortForwardDetails } from "./port-forward-details"; +import { navigation } from "../../navigation"; enum columnId { name = "name", @@ -36,8 +40,11 @@ enum columnId { status = "status", } +interface Props extends RouteComponentProps { +} + @observer -export class PortForwards extends React.Component { +export class PortForwards extends React.Component { componentDidMount() { disposeOnUnmount(this, [ @@ -45,6 +52,32 @@ export class PortForwards extends React.Component { ]); } + get selectedPortForward() { + const { match: { params: { forwardport } } } = this.props; + + return portForwardStore.getById(forwardport); + } + + onDetails = (item: PortForwardItem) => { + if (item === this.selectedPortForward) { + this.hideDetails(); + } else { + this.showDetails(item); + } + }; + + showDetails = (item: PortForwardItem) => { + navigation.push(portForwardsURL({ + params: { + forwardport: String(item.getForwardPort()), + } + })); + }; + + hideDetails = () => { + navigation.push(portForwardsURL()); + }; + renderRemoveDialogMessage(selectedItems: PortForwardItem[]) { const forwardPorts = selectedItems.map(item => item.getForwardPort()).join(", "); @@ -100,7 +133,15 @@ export class PortForwards extends React.Component { customizeRemoveDialog={selectedItems => ({ message: this.renderRemoveDialogMessage(selectedItems) })} + detailsItem={this.selectedPortForward} + onDetails={this.onDetails} /> + {this.selectedPortForward && ( + + )} ); } diff --git a/src/renderer/port-forward/port-forward.store.ts b/src/renderer/port-forward/port-forward.store.ts index 49ebee61d4..7731d5e087 100644 --- a/src/renderer/port-forward/port-forward.store.ts +++ b/src/renderer/port-forward/port-forward.store.ts @@ -81,6 +81,16 @@ export class PortForwardStore extends ItemStore { async removeSelectedItems() { return Promise.all(this.selectedItems.map(removePortForward)); } + + getById(id: string) { + const index = this.getIndexById(id); + + if (index === -1) { + return null; + } + + return this.getItems()[index]; + } } interface PortForwardResult { From 5bdebfda43a71e57a1eaf74c1059f30a9f5d650a Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 20 Oct 2021 22:33:28 -0400 Subject: [PATCH 23/37] Add better error message if the proxy test fails (#3913) * Add better error message if the proxy test fails Signed-off-by: Sebastian Malton * resolve pr comment Signed-off-by: Sebastian Malton --- src/main/index.ts | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 3dd478fc76..18bfa0980e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -27,7 +27,7 @@ import * as Mobx from "mobx"; import * as LensExtensionsCommonApi from "../extensions/common-api"; import * as LensExtensionsMainApi from "../extensions/main-api"; import { app, autoUpdater, dialog, powerMonitor } from "electron"; -import { appName, isIntegrationTesting, isMac, productName } from "../common/vars"; +import { appName, isIntegrationTesting, isMac, isWindows, productName } from "../common/vars"; import { LensProxy } from "./lens-proxy"; import { WindowManager } from "./window-manager"; import { ClusterManager } from "./cluster-manager"; @@ -133,9 +133,7 @@ app.on("ready", async () => { bindBroadcastHandlers(); - powerMonitor.on("shutdown", () => { - app.exit(); - }); + powerMonitor.on("shutdown", () => app.exit()); registerFileProtocol("static", __static); @@ -183,7 +181,8 @@ app.on("ready", async () => { await lensProxy.listen(); } catch (error) { dialog.showErrorBox("Lens Error", `Could not start proxy: ${error?.message || "unknown error"}`); - app.exit(); + + return app.exit(); } // test proxy connection @@ -193,13 +192,27 @@ app.on("ready", async () => { if (getAppVersion() !== versionFromProxy) { logger.error("Proxy server responded with invalid response"); - app.exit(); - } else { - logger.info("⚡ LensProxy connection OK"); + + return app.exit(); } + + logger.info("⚡ LensProxy connection OK"); } catch (error) { logger.error(`🛑 LensProxy: failed connection test: ${error}`); - app.exit(); + + const hostsPath = isWindows + ? "C:\\windows\\system32\\drivers\\etc\\hosts" + : "/etc/hosts"; + const message = [ + `Failed connection test: ${error}`, + "Check to make sure that no other versions of Lens are running", + `Check ${hostsPath} to make sure that it is clean and that the localhost loopback is at the top and set to 127.0.0.1`, + "If you have HTTP_PROXY or http_proxy set in your environment, make sure that the localhost and the ipv4 loopback address 127.0.0.1 are added to the NO_PROXY environment variable.", + ]; + + dialog.showErrorBox("Lens Proxy Error", message.join("\n\n")); + + return app.exit(); } initializers.initRegistries(); From 13ee39c9e386983f29b96c74ada4c9a4eb82827d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 09:04:35 -0400 Subject: [PATCH 24/37] Bump esbuild-loader from 2.15.1 to 2.16.0 (#4100) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 99a801b0b6..831338dabe 100644 --- a/package.json +++ b/package.json @@ -330,7 +330,7 @@ "electron-builder": "^22.11.11", "electron-notarize": "^0.3.0", "esbuild": "^0.13.8", - "esbuild-loader": "^2.15.1", + "esbuild-loader": "^2.16.0", "eslint": "^7.32.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-react": "^7.26.1", diff --git a/yarn.lock b/yarn.lock index 744d913d5d..abecec6705 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5505,12 +5505,12 @@ esbuild-linux-ppc64le@0.13.8: resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.8.tgz#fdb82a059a5b86bb10fb42091b4ebcf488b9cd46" integrity sha512-eZSQ0ERsWkukJp2px/UWJHVNuy0lMoz/HZcRWAbB6reoaBw7S9vMzYNUnflfL3XA6WDs+dZn3ekHE4Y2uWLGig== -esbuild-loader@^2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.15.1.tgz#5a3940f5d20317f1a35720efa33e933f97c923e9" - integrity sha512-JRBL6uTeWplMbylNBt9gxLKMjD8wKnqGq786QV/cm/nPBSNA9/kC7/vNwCXTDPfYqHoWsjyfH7ub9ekN0kdAYQ== +esbuild-loader@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.16.0.tgz#a44a57a77ed2810d6b278579271f77d739aa7bc9" + integrity sha512-LCJEwkf+nMJbNmVYNgg/0PaIZDdr5OcHw1qbWAZLkrmBRX+KwHY/yAS6ia98UBtwzk/WhsftUBNB6tfPHgFIxw== dependencies: - esbuild "^0.12.21" + esbuild "^0.13.4" joycon "^3.0.1" json5 "^2.2.0" loader-utils "^2.0.0" @@ -5548,12 +5548,7 @@ esbuild-windows-arm64@0.13.8: resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.8.tgz#e59ae004496fd8a5ab67bfc7945a2e47480d6fb9" integrity sha512-HW6Mtq5eTudllxY2YgT62MrVcn7oq2o8TAoAvDUhyiEmRmDY8tPwAhb1vxw5/cdkbukM3KdMYtksnUhF/ekWeg== -esbuild@^0.12.21: - version "0.12.29" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.29.tgz#be602db7c4dc78944a9dbde0d1ea19d36c1f882d" - integrity sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g== - -esbuild@^0.13.8: +esbuild@^0.13.4, esbuild@^0.13.8: version "0.13.8" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.8.tgz#bd7cc51b881ab067789f88e17baca74724c1ec4f" integrity sha512-A4af7G7YZLfG5OnARJRMtlpEsCkq/zHZQXewgPA864l9D6VjjbH1SuFYK/OSV6BtHwDGkdwyRrX0qQFLnMfUcw== From 54f249f0fb5a04da754ac7e2ca9994693ef6c73c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 09:29:02 -0400 Subject: [PATCH 25/37] Bump @types/node from 14.17.26 to 14.17.27 (#4098) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 831338dabe..3a22835dab 100644 --- a/package.json +++ b/package.json @@ -285,7 +285,7 @@ "@types/mini-css-extract-plugin": "^0.9.1", "@types/mock-fs": "^4.13.1", "@types/module-alias": "^2.0.1", - "@types/node": "14.17.26", + "@types/node": "14.17.27", "@types/node-fetch": "^2.5.12", "@types/npm": "^2.0.32", "@types/progress-bar-webpack-plugin": "^2.1.2", diff --git a/yarn.lock b/yarn.lock index abecec6705..cc5f7d9d5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1713,10 +1713,10 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@14.17.26", "@types/node@^14.6.2": - version "14.17.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.26.tgz#47a53c7e7804490155a4646d60c8e194816d073c" - integrity sha512-eSTNkK/nfmnC7IKpOJZixDgG0W2/eHz1qyFN7o/rwwwIHsVRp+G9nbh4BrQ77kbQ2zPu286AQRxkuRLPcR3gXw== +"@types/node@*", "@types/node@14.17.27", "@types/node@^14.6.2": + version "14.17.27" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.27.tgz#5054610d37bb5f6e21342d0e6d24c494231f3b85" + integrity sha512-94+Ahf9IcaDuJTle/2b+wzvjmutxXAEXU6O81JHblYXUg2BDG+dnBy7VxIPHKAyEEDHzCMQydTJuWvrE+Aanzw== "@types/node@^10.12.0": version "10.17.24" From 3eebb6e9bf1006450703afd470e6a339d245a779 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 09:29:20 -0400 Subject: [PATCH 26/37] Bump ts-node from 10.2.1 to 10.3.0 (#4099) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 3a22835dab..9575017508 100644 --- a/package.json +++ b/package.json @@ -373,7 +373,7 @@ "tailwindcss": "^2.2.17", "ts-jest": "26.5.6", "ts-loader": "^7.0.5", - "ts-node": "^10.2.1", + "ts-node": "^10.3.0", "type-fest": "^1.0.2", "typed-emitter": "^1.3.1", "typedoc": "0.22.6", diff --git a/yarn.lock b/yarn.lock index cc5f7d9d5c..72576911a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -330,10 +330,10 @@ resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== -"@cspotcode/source-map-support@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz#118511f316e2e87ee4294761868e254d3da47960" - integrity sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg== +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== dependencies: "@cspotcode/source-map-consumer" "0.8.0" @@ -13804,12 +13804,12 @@ ts-loader@^7.0.5: micromatch "^4.0.0" semver "^6.0.0" -ts-node@^10.2.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5" - integrity sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw== +ts-node@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.3.0.tgz#a797f2ed3ff50c9a5d814ce400437cb0c1c048b4" + integrity sha512-RYIy3i8IgpFH45AX4fQHExrT8BxDeKTdC83QFJkNzkvt8uFB6QJ8XMyhynYiKMLxt9a7yuXaDBZNOYS3XjDcYw== dependencies: - "@cspotcode/source-map-support" "0.6.1" + "@cspotcode/source-map-support" "0.7.0" "@tsconfig/node10" "^1.0.7" "@tsconfig/node12" "^1.0.7" "@tsconfig/node14" "^1.0.0" From 3fc9436a2e39dceece2e7848b063c8a1e0751d4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 09:31:44 -0400 Subject: [PATCH 27/37] Bump eslint-plugin-unused-imports from 1.0.1 to 1.1.5 (#4101) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 23 +++++++---------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 9575017508..5ebf32277c 100644 --- a/package.json +++ b/package.json @@ -335,7 +335,7 @@ "eslint-plugin-header": "^3.1.1", "eslint-plugin-react": "^7.26.1", "eslint-plugin-react-hooks": "^4.2.0", - "eslint-plugin-unused-imports": "^1.0.1", + "eslint-plugin-unused-imports": "^1.1.5", "file-loader": "^6.2.0", "flex.box": "^3.4.4", "fork-ts-checker-webpack-plugin": "^5.2.1", diff --git a/yarn.lock b/yarn.lock index 72576911a8..9e7be6aa29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2164,7 +2164,7 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^4.33.0", "@typescript-eslint/eslint-plugin@^4.5.0": +"@typescript-eslint/eslint-plugin@^4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== @@ -5655,16 +5655,12 @@ eslint-plugin-react@^7.26.1: semver "^6.3.0" string.prototype.matchall "^4.0.5" -eslint-plugin-unused-imports@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-1.0.1.tgz#f95f48637bb166ed924ecb5ebf27761a57da4178" - integrity sha512-AVDcgeoZZoBH/g5743nvWQK7/V7w2RMILHvogfBnYa1s47los7G2ysEweRx0yJ8pSVnITJvxTBkefQbJowTi3w== +eslint-plugin-unused-imports@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-1.1.5.tgz#a2b992ef0faf6c6c75c3815cc47bde76739513c2" + integrity sha512-TeV8l8zkLQrq9LBeYFCQmYVIXMjfHgdRQLw7dEZp4ZB3PeR10Y5Uif11heCsHRmhdRIYMoewr1d9ouUHLbLHew== dependencies: - "@typescript-eslint/eslint-plugin" "^4.5.0" - eslint "^7.11.0" eslint-rule-composer "^0.3.0" - requireindex "~1.2.0" - typescript "^4.0.3" eslint-rule-composer@^0.3.0: version "0.3.0" @@ -5711,7 +5707,7 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint@^7.11.0, eslint@^7.32.0: +eslint@^7.32.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== @@ -12148,11 +12144,6 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requireindex@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" - integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -13992,7 +13983,7 @@ typescript-plugin-css-modules@^3.4.0: stylus "^0.54.8" tsconfig-paths "^3.9.0" -typescript@^4.0.3, typescript@^4.4.3: +typescript@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA== From d5719f29b804ee15449356eb140f19cb2d419378 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 21 Oct 2021 09:38:48 -0400 Subject: [PATCH 28/37] Fix PodDetailsList being empty over 100 pods (#4096) --- .../+workloads-pods/pod-details-list.tsx | 49 +++++++------- src/renderer/components/table/table.tsx | 48 +++++++++----- .../components/virtual-list/virtual-list.tsx | 65 ++++++++++++------- 3 files changed, 96 insertions(+), 66 deletions(-) diff --git a/src/renderer/components/+workloads-pods/pod-details-list.tsx b/src/renderer/components/+workloads-pods/pod-details-list.tsx index b3dbe80fe7..e7e603375c 100644 --- a/src/renderer/components/+workloads-pods/pod-details-list.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-list.tsx @@ -51,26 +51,14 @@ interface Props extends OptionalProps { interface OptionalProps { maxCpu?: number; maxMemory?: number; - showTitle?: boolean; } @observer export class PodDetailsList extends React.Component { - static defaultProps: OptionalProps = { - showTitle: true - }; - private metricsWatcher = interval(120, () => { podsStore.loadKubeMetrics(this.props.owner.getNs()); }); - private sortingCallbacks = { - [sortBy.name]: (pod: Pod) => pod.getName(), - [sortBy.namespace]: (pod: Pod) => pod.getNs(), - [sortBy.cpu]: (pod: Pod) => podsStore.getPodKubeMetrics(pod).cpu, - [sortBy.memory]: (pod: Pod) => podsStore.getPodKubeMetrics(pod).memory, - }; - componentDidMount() { this.metricsWatcher.start(true); disposeOnUnmount(this, [ @@ -144,27 +132,43 @@ export class PodDetailsList extends React.Component { } render() { - const { pods, showTitle } = this.props; - const virtual = pods.length > 100; + const { pods } = this.props; - if (!pods.length && !podsStore.isLoaded) return ( -
- ); - if (!pods.length) return null; + if (!podsStore.isLoaded) { + return ( +
+ +
+ ); + } + + if (!pods.length) { + return null; + } + + const virtual = pods.length > 20; return (
- {showTitle && } +
pod.getName(), + [sortBy.namespace]: pod => pod.getNs(), + [sortBy.cpu]: pod => podsStore.getPodKubeMetrics(pod).cpu, + [sortBy.memory]: pod => podsStore.getPodKubeMetrics(pod).memory, + }} sortByDefault={{ sortBy: sortBy.cpu, orderBy: "desc" }} sortSyncWithUrl={false} getTableRow={this.getTableRow} + renderRow={!virtual && (pod => this.getTableRow(pod.getId()))} className="box grow" > @@ -176,9 +180,6 @@ export class PodDetailsList extends React.Component { Memory Status - { - !virtual && pods.map(pod => this.getTableRow(pod.getId())) - }
); diff --git a/src/renderer/components/table/table.tsx b/src/renderer/components/table/table.tsx index 1d428c62be..6d71ba78a2 100644 --- a/src/renderer/components/table/table.tsx +++ b/src/renderer/components/table/table.tsx @@ -57,9 +57,26 @@ export interface TableProps extends React.DOMAttributes { onSort?: (params: TableSortParams) => void; // callback on sort change, default: global sync with url noItems?: React.ReactNode; // Show no items state table list is empty selectedItemId?: string; // Allows to scroll list to selected item - virtual?: boolean; // Use virtual list component to render only visible rows - rowPadding?: string; - rowLineHeight?: string; + + /** + * Use virtual list component to render only visible rows. By default uses a + * auto sizer to fill available height + */ + virtual?: boolean; + /** + * Only used when virtual is true. Sets the virtual list to be a fixed height. + * Needed when used in contexts that already have a parent component that + * is `overflow-y: scroll`, + */ + virtualHeight?: number; + /** + * Row padding in pixels + */ + rowPadding?: number; + /** + * Row line height in pixels + */ + rowLineHeight?: number; customRowHeights?: (item: Item, lineHeight: number, paddings: number) => number; getTableRow?: (uid: string) => React.ReactElement; renderRow?: (item: Item) => React.ReactElement; @@ -78,9 +95,10 @@ export class Table extends React.Component> { static defaultProps: TableProps = { scrollable: true, autoSize: true, - rowPadding: "8px", - rowLineHeight: "17px", + rowPadding: 8, + rowLineHeight: 17, sortSyncWithUrl: true, + customRowHeights: (item, lineHeight, paddings) => lineHeight + paddings, }; constructor(props: TableProps) { @@ -185,7 +203,10 @@ export class Table extends React.Component> { } renderRows() { - const { noItems, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props; + const { + noItems, virtual, customRowHeights, rowLineHeight, rowPadding, items, + getTableRow, selectedItemId, className, virtualHeight + } = this.props; const content = this.getContent(); let rows: React.ReactElement[] = content.filter(elem => elem.type === TableRow); let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items]; @@ -194,9 +215,7 @@ export class Table extends React.Component> { sortedItems = this.getSorted(sortedItems); if (rows.length) { - rows = sortedItems.map(item => rows.find(row => { - return item == row.props.sortItem; - })); + rows = sortedItems.map(item => rows.find(row => item == row.props.sortItem)); } } @@ -205,15 +224,7 @@ export class Table extends React.Component> { } if (virtual) { - const lineHeight = parseFloat(rowLineHeight); - const padding = parseFloat(rowPadding); - let rowHeights: number[] = Array(items.length).fill(lineHeight + padding * 2); - - if (customRowHeights) { - rowHeights = sortedItems.map(item => { - return customRowHeights(item, lineHeight, padding * 2); - }); - } + const rowHeights = sortedItems.map(item => customRowHeights(item, rowLineHeight, rowPadding * 2)); return ( extends React.Component> { getRow={getTableRow} selectedItemId={selectedItemId} className={className} + fixedHeight={virtualHeight} /> ); } diff --git a/src/renderer/components/virtual-list/virtual-list.tsx b/src/renderer/components/virtual-list/virtual-list.tsx index 36f62dff93..be3f2fae61 100644 --- a/src/renderer/components/virtual-list/virtual-list.tsx +++ b/src/renderer/components/virtual-list/virtual-list.tsx @@ -44,6 +44,12 @@ interface Props { getRow?: (uid: string | number) => React.ReactElement; onScroll?: (props: ListOnScrollProps) => void; outerRef?: React.Ref + + /** + * If specified then AutoSizer will not be used and instead a fixed height + * virtual list will be rendered + */ + fixedHeight?: number; } interface State { @@ -98,34 +104,45 @@ export class VirtualList extends Component { this.listRef.current?.scrollToItem(index, align); }; - render() { - const { width, className, items, getRow, onScroll, outerRef } = this.props; + renderList(height: number | undefined) { + const { width, items, getRow, onScroll, outerRef } = this.props; const { overscanCount } = this.state; - const rowData: RowData = { - items, - getRow - }; + + return ( + + {Row} + + ); + } + + render() { + const { className, fixedHeight } = this.props; return (
- - {({ height }) => ( - - {Row} - - )} - + { + typeof fixedHeight === "number" + ? this.renderList(fixedHeight) + : ( + + {({ height }) => this.renderList(height)} + + ) + }
); } From 0dab7b807365b18c673fb040da5a4b602dea386e Mon Sep 17 00:00:00 2001 From: Panu Horsmalahti Date: Thu, 21 Oct 2021 19:18:32 +0300 Subject: [PATCH 29/37] Add option for providing request options to watch requests (#4102) * Add option for providing request options to watch requests. Signed-off-by: Panu Horsmalahti * Move getRequestOptions to JsonApi. Signed-off-by: Panu Horsmalahti * Style fix Signed-off-by: Panu Horsmalahti * Make token to be a string or a function Signed-off-by: Panu Horsmalahti * Fix isFunction import. Signed-off-by: Panu Horsmalahti * Make getRequestOptions async. Signed-off-by: Panu Horsmalahti * Remove commented code. Signed-off-by: Panu Horsmalahti * Only set token if it's truthy. Signed-off-by: Panu Horsmalahti * Review fixes. Signed-off-by: Panu Horsmalahti * Add this.getRequestOptions default Signed-off-by: Panu Horsmalahti --- src/common/k8s-api/json-api.ts | 20 +++++++++++++++++--- src/common/k8s-api/kube-api.ts | 17 ++++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/common/k8s-api/json-api.ts b/src/common/k8s-api/json-api.ts index dde8f5f5a2..15bad9864a 100644 --- a/src/common/k8s-api/json-api.ts +++ b/src/common/k8s-api/json-api.ts @@ -52,6 +52,7 @@ export interface JsonApiConfig { apiBase: string; serverAddress: string; debug?: boolean; + getRequestOptions?: () => Promise; } export class JsonApi { static reqInitDefault: RequestInit = { @@ -68,18 +69,26 @@ export class JsonApi { this.config = Object.assign({}, JsonApi.configDefault, config); this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit); this.parseResponse = this.parseResponse.bind(this); + this.getRequestOptions = config.getRequestOptions ?? (() => Promise.resolve({})); } public onData = new EventEmitter<[D, Response]>(); public onError = new EventEmitter<[JsonApiErrorParsed, Response]>(); + + private getRequestOptions: JsonApiConfig["getRequestOptions"]; get(path: string, params?: P, reqInit: RequestInit = {}) { return this.request(path, params, { ...reqInit, method: "get" }); } - getResponse(path: string, params?: P, init: RequestInit = {}): Promise { + async getResponse(path: string, params?: P, init: RequestInit = {}): Promise { let reqUrl = `${this.config.serverAddress}${this.config.apiBase}${path}`; - const reqInit: RequestInit = merge({}, this.reqInit, init); + const reqInit: RequestInit = merge( + {}, + this.reqInit, + await this.getRequestOptions(), + init + ); const { query } = params || {} as P; if (!reqInit.method) { @@ -113,7 +122,12 @@ export class JsonApi { protected async request(path: string, params?: P, init: RequestInit = {}) { let reqUrl = `${this.config.serverAddress}${this.config.apiBase}${path}`; - const reqInit: RequestInit = merge({}, this.reqInit, init); + const reqInit: RequestInit = merge( + {}, + this.reqInit, + await this.getRequestOptions(), + init + ); const { data, query } = params || {} as P; if (data && !reqInit.body) { diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 54b0e5726f..7e29500689 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -22,6 +22,7 @@ // Base class for building all kubernetes apis import merge from "lodash/merge"; +import { isFunction } from "lodash"; import { stringify } from "querystring"; import { apiKubePrefix, isDevelopment } from "../../common/vars"; import logger from "../../main/logger"; @@ -108,7 +109,7 @@ export interface IRemoteKubeApiConfig { skipTLSVerify?: boolean; } user: { - token?: string; + token?: string | (() => Promise); clientCertificateData?: string; clientKeyData?: string; } @@ -135,12 +136,6 @@ export function forCluster(cluster: ILocalKubeApiConfig, k export function forRemoteCluster(config: IRemoteKubeApiConfig, kubeClass: KubeObjectConstructor): KubeApi { const reqInit: RequestInit = {}; - if (config.user.token) { - reqInit.headers = { - "Authorization": `Bearer ${config.user.token}` - }; - } - const agentOptions: AgentOptions = {}; if (config.cluster.skipTLSVerify === true) { @@ -163,10 +158,18 @@ export function forRemoteCluster(config: IRemoteKubeApiCon reqInit.agent = new Agent(agentOptions); } + const token = config.user.token; const request = new KubeJsonApi({ serverAddress: config.cluster.server, apiBase: "", debug: isDevelopment, + ...(token ? { + getRequestOptions: async () => ({ + headers: { + "Authorization": `Bearer ${isFunction(token) ? await token() : token}` + } + }) + } : {}) }, reqInit); return new KubeApi({ From c0c0b3ae112f751bcd8439ca304d12d8852cd4fb Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Thu, 21 Oct 2021 12:32:34 -0400 Subject: [PATCH 30/37] Allow port-forwards to be restarted with the same local port (#4097) * allow port-forwards to be restarted with the same local port Signed-off-by: Jim Ehrismann * address review comment Signed-off-by: Jim Ehrismann --- src/renderer/port-forward/port-forward-dialog.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/renderer/port-forward/port-forward-dialog.tsx b/src/renderer/port-forward/port-forward-dialog.tsx index 100fdf5b94..7299228789 100644 --- a/src/renderer/port-forward/port-forward-dialog.tsx +++ b/src/renderer/port-forward/port-forward-dialog.tsx @@ -49,7 +49,6 @@ const dialogState = observable.object({ @observer export class PortForwardDialog extends Component { - @observable ready = false; @observable currentPort = 0; @observable desiredPort = 0; @@ -81,16 +80,13 @@ export class PortForwardDialog extends Component { this.currentPort = +portForward.forwardPort; this.desiredPort = this.currentPort; - this.ready = this.currentPort ? false : true; }; onClose = () => { - this.ready = false; }; changePort = (value: string) => { this.desiredPort = Number(value); - this.ready = Boolean(this.desiredPort == 0 || this.currentPort !== this.desiredPort); }; startPortForward = async () => { @@ -170,7 +166,6 @@ export class PortForwardDialog extends Component { contentClass="flex gaps column" next={this.startPortForward} nextLabel={this.currentPort === 0 ? "Start" : "Restart"} - disabledNext={!this.ready} > {this.renderContents()} From 62526c21925245bc85fa5d4e99eb6ff1b5a00551 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 22 Oct 2021 13:19:26 +0300 Subject: [PATCH 31/37] Remove iframe completely on disconnect (#4116) Signed-off-by: Jari Kolehmainen --- src/renderer/components/app.tsx | 7 ------- src/renderer/components/cluster-manager/lens-views.ts | 8 +------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index d126e5260f..7932b1f880 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -113,13 +113,6 @@ export class App extends React.Component { window.location.reload(); }); - window.addEventListener("message", (ev: MessageEvent) => { - if (ev.data === "teardown") { - unmountComponentAtNode(rootElem); - window.location.href = "about:blank"; - } - }); - window.onbeforeunload = () => { logger.info(`[APP]: Unload dashboard, clusterId=${App.clusterId}, frameId=${frameId}`); diff --git a/src/renderer/components/cluster-manager/lens-views.ts b/src/renderer/components/cluster-manager/lens-views.ts index 830fe20cb3..b4f8415e71 100644 --- a/src/renderer/components/cluster-manager/lens-views.ts +++ b/src/renderer/components/cluster-manager/lens-views.ts @@ -79,13 +79,7 @@ export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrame logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`); lensViews.delete(clusterId); - // Keep frame in DOM to avoid possible bugs when same cluster re-created after being removed. - // In that case for some reasons `webFrame.routingId` returns some previous frameId (usage in app.tsx) - // Issue: https://github.com/lensapp/lens/issues/811 - iframe.style.display = "none"; - iframe.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}`; - iframe.removeAttribute("name"); - iframe.contentWindow.postMessage("teardown", "*"); + iframe.parentNode.removeChild(iframe); } export function refreshViews(visibleClusterId?: string) { From b4e85375b0b4015fd3d5d7e5ac7575c9a469997b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Oct 2021 13:24:25 +0300 Subject: [PATCH 32/37] Bump node-fetch from 2.6.1 to 2.6.5 (#4114) Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.5. - [Release notes](https://github.com/node-fetch/node-fetch/releases) - [Changelog](https://github.com/node-fetch/node-fetch/blob/v2.6.5/CHANGELOG.md) - [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.5) --- updated-dependencies: - dependency-name: node-fetch dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5ebf32277c..62f9e08139 100644 --- a/package.json +++ b/package.json @@ -221,7 +221,7 @@ "moment": "^2.29.1", "moment-timezone": "^0.5.33", "monaco-editor": "^0.26.1", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.5", "node-pty": "^0.10.1", "npm": "^6.14.15", "p-limit": "^3.1.0", diff --git a/yarn.lock b/yarn.lock index 9e7be6aa29..39c2f58938 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9942,11 +9942,18 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@2.6.1, node-fetch@^2.6.1: +node-fetch@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@^2.6.5: + version "2.6.5" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" + integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== + dependencies: + whatwg-url "^5.0.0" + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -13746,6 +13753,11 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -14394,6 +14406,11 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -14560,6 +14577,14 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^8.0.0, whatwg-url@^8.5.0: version "8.7.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" From 1872a8da87f54b025095c3bba29dae33c43997a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Oct 2021 13:25:00 +0300 Subject: [PATCH 33/37] Bump nodemon from 2.0.13 to 2.0.14 (#4113) Bumps [nodemon](https://github.com/remy/nodemon) from 2.0.13 to 2.0.14. - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v2.0.13...v2.0.14) --- updated-dependencies: - dependency-name: nodemon dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 62f9e08139..bfc62f9ae0 100644 --- a/package.json +++ b/package.json @@ -351,7 +351,7 @@ "mini-css-extract-plugin": "^1.6.2", "node-gyp": "7.1.2", "node-loader": "^1.0.3", - "nodemon": "^2.0.13", + "nodemon": "^2.0.14", "playwright": "^1.15.2", "postcss": "^8.3.6", "postcss-loader": "4.3.0", diff --git a/yarn.lock b/yarn.lock index 39c2f58938..23dbed97e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10058,10 +10058,10 @@ node-pty@^0.10.1: dependencies: nan "^2.14.0" -nodemon@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.13.tgz#67d40d3a4d5bd840aa785c56587269cfcf5d24aa" - integrity sha512-UMXMpsZsv1UXUttCn6gv8eQPhn6DR4BW+txnL3IN5IHqrCwcrT/yWHfL35UsClGXknTH79r5xbu+6J1zNHuSyA== +nodemon@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.14.tgz#287c7a2f6cd8a18b07e94cd776ecb6a82e4ba439" + integrity sha512-frcpDx+PviKEQRSYzwhckuO2zoHcBYLHI754RE9z5h1RGtrngerc04mLpQQCPWBkH/2ObrX7We9YiwVSYZpFJQ== dependencies: chokidar "^3.2.2" debug "^3.2.6" From cd140976689674e78d838eb605488e20b3f18adb Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 22 Oct 2021 09:03:35 -0400 Subject: [PATCH 34/37] Fix errors in HotbarStore migrations (#4105) --- src/migrations/hotbar-store/5.0.0-alpha.2.ts | 3 ++- src/migrations/hotbar-store/5.0.0-beta.10.ts | 16 ++++++++++++++-- src/migrations/hotbar-store/5.0.0-beta.5.ts | 16 +++++++++------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/migrations/hotbar-store/5.0.0-alpha.2.ts b/src/migrations/hotbar-store/5.0.0-alpha.2.ts index 0a662b466a..6eeb759779 100644 --- a/src/migrations/hotbar-store/5.0.0-alpha.2.ts +++ b/src/migrations/hotbar-store/5.0.0-alpha.2.ts @@ -27,7 +27,8 @@ import type { MigrationDeclaration } from "../helpers"; export default { version: "5.0.0-alpha.2", run(store) { - const hotbars = (store.get("hotbars") || []) as Hotbar[]; + const rawHotbars = store.get("hotbars"); + const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : []; store.set("hotbars", hotbars.map((hotbar) => ({ id: uuid.v4(), 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 7d7abc01b7..a9a0de11b2 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.10.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.10.ts @@ -46,9 +46,20 @@ interface PartialHotbar { export default { version: "5.0.0-beta.10", run(store) { - const hotbars = (store.get("hotbars") as Hotbar[] ?? []).filter(Boolean); + const rawHotbars = store.get("hotbars"); + const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars.filter(h => h && typeof h === "object") : []; const userDataPath = app.getPath("userData"); + // Hotbars might be empty, if some of the previous migrations weren't run + if (hotbars.length === 0) { + const hotbar = getEmptyHotbar("default"); + const { metadata: { uid, name, source } } = catalogEntity; + + hotbar.items[0] = { entity: { uid, name, source } }; + + hotbars.push(hotbar); + } + try { const workspaceStoreData: Pre500WorkspaceStoreModel = fse.readJsonSync(path.join(userDataPath, "lens-workspace-store.json")); const { clusters }: ClusterStoreModel = fse.readJSONSync(path.join(userDataPath, "lens-cluster-store.json")); @@ -144,12 +155,13 @@ export default { } } - store.set("hotbars", hotbars); } catch (error) { // ignore files being missing if (error.code !== "ENOENT") { throw error; } } + + store.set("hotbars", hotbars); } } as MigrationDeclaration; diff --git a/src/migrations/hotbar-store/5.0.0-beta.5.ts b/src/migrations/hotbar-store/5.0.0-beta.5.ts index f9cfd77262..40f36e13fe 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.5.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.5.ts @@ -26,25 +26,27 @@ import type { MigrationDeclaration } from "../helpers"; export default { version: "5.0.0-beta.5", run(store) { - const hotbars: Hotbar[] = store.get("hotbars"); + const rawHotbars = store.get("hotbars"); + const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : []; - hotbars.forEach((hotbar, hotbarIndex) => { - hotbar.items.forEach((item, itemIndex) => { + for (const hotbar of hotbars) { + for (let i = 0; i < hotbar.items.length; i += 1) { + const item = hotbar.items[i]; const entity = catalogEntityRegistry.items.find((entity) => entity.metadata.uid === item?.entity.uid); if (!entity) { // Clear disabled item - hotbars[hotbarIndex].items[itemIndex] = null; + hotbar.items[i] = null; } else { // Save additional data - hotbars[hotbarIndex].items[itemIndex].entity = { + hotbar.items[i].entity = { ...item.entity, name: entity.metadata.name, source: entity.metadata.source }; } - }); - }); + } + } store.set("hotbars", hotbars); } From 8fa2adf8709e99a2f9bbe6dede0d54049622d451 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 22 Oct 2021 09:10:21 -0400 Subject: [PATCH 35/37] Add double click to reset sidebar width (#4109) --- src/renderer/components/layout/main-layout.tsx | 6 +++--- src/renderer/components/layout/sidebar-storage.ts | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/layout/main-layout.tsx b/src/renderer/components/layout/main-layout.tsx index 34b0c1ed95..e8316bfd3c 100755 --- a/src/renderer/components/layout/main-layout.tsx +++ b/src/renderer/components/layout/main-layout.tsx @@ -26,7 +26,7 @@ import { observer } from "mobx-react"; import { cssNames } from "../../utils"; import { ErrorBoundary } from "../error-boundary"; import { ResizeDirection, ResizeGrowthDirection, ResizeSide, ResizingAnchor } from "../resizing-anchor"; -import { sidebarStorage } from "./sidebar-storage"; +import { defaultSidebarWidth, sidebarStorage } from "./sidebar-storage"; interface Props { sidebar: React.ReactNode; @@ -46,7 +46,6 @@ export class MainLayout extends React.Component { }; render() { - const { onSidebarResize } = this; const { className, footer, children, sidebar } = this.props; const { width: sidebarWidth } = sidebarStorage.get(); const style = { "--sidebar-width": `${sidebarWidth}px` } as React.CSSProperties; @@ -60,7 +59,8 @@ export class MainLayout extends React.Component { placement={ResizeSide.TRAILING} growthDirection={ResizeGrowthDirection.LEFT_TO_RIGHT} getCurrentExtent={() => sidebarWidth} - onDrag={onSidebarResize} + onDrag={this.onSidebarResize} + onDoubleClick={() => this.onSidebarResize(defaultSidebarWidth)} minExtent={120} maxExtent={400} /> diff --git a/src/renderer/components/layout/sidebar-storage.ts b/src/renderer/components/layout/sidebar-storage.ts index 663cb7c177..162ca3e6b7 100644 --- a/src/renderer/components/layout/sidebar-storage.ts +++ b/src/renderer/components/layout/sidebar-storage.ts @@ -28,7 +28,9 @@ export interface SidebarStorageState { } } +export const defaultSidebarWidth = 200; + export const sidebarStorage = createStorage("sidebar", { - width: 200, + width: defaultSidebarWidth, expanded: {}, }); From e643b37c7fdfd4fc384d81cb7029df095492e0c2 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 22 Oct 2021 16:13:43 +0300 Subject: [PATCH 36/37] Cluster overview css fixes (#4104) --- integration/__tests__/cluster-pages.tests.ts | 2 +- ...-issues.scss => cluster-issues.module.css} | 73 +++++++++---------- .../components/+cluster/cluster-issues.tsx | 14 ++-- .../+cluster/cluster-metric-switchers.tsx | 6 +- ...etrics.scss => cluster-metrics.module.css} | 8 +- .../components/+cluster/cluster-metrics.tsx | 9 ++- ...rview.scss => cluster-overview.module.css} | 24 +++--- .../components/+cluster/cluster-overview.tsx | 4 +- ...ers.scss => cluster-pie-charts.module.css} | 15 ++-- .../+cluster/cluster-pie-charts.scss | 50 ------------- .../+cluster/cluster-pie-charts.tsx | 18 ++--- 11 files changed, 84 insertions(+), 139 deletions(-) rename src/renderer/components/+cluster/{cluster-issues.scss => cluster-issues.module.css} (66%) rename src/renderer/components/+cluster/{cluster-metrics.scss => cluster-metrics.module.css} (90%) rename src/renderer/components/+cluster/{cluster-overview.scss => cluster-overview.module.css} (80%) rename src/renderer/components/+cluster/{cluster-metric-switchers.scss => cluster-pie-charts.module.css} (80%) delete mode 100644 src/renderer/components/+cluster/cluster-pie-charts.scss diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index bd31a80102..f54565e1a8 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -74,7 +74,7 @@ const commonPageTests: CommonPageTest[] = [{ page: { name: "Cluster", href: "/overview", - expectedSelector: "div.ClusterOverview div.label", + expectedSelector: "div[data-testid='cluster-overview-page'] div.label", expectedText: "CPU" } }, diff --git a/src/renderer/components/+cluster/cluster-issues.scss b/src/renderer/components/+cluster/cluster-issues.module.css similarity index 66% rename from src/renderer/components/+cluster/cluster-issues.scss rename to src/renderer/components/+cluster/cluster-issues.module.css index aadb46ee39..fc421f1401 100644 --- a/src/renderer/components/+cluster/cluster-issues.scss +++ b/src/renderer/components/+cluster/cluster-issues.module.css @@ -19,61 +19,56 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.ClusterIssues { + .ClusterIssues { min-height: 350px; position: relative; - grid-column-start: 1; - grid-column-end: 3; + grid-area: issues; + padding: calc(var(--margin) * 2) 0; + background: var(--contentColor); - @include media("<1024px") { - grid-column-start: 1!important; - grid-column-end: 1!important; + &:global(.OnlyClusterIssues) { + grid-row: row1-start / row2-end; + } + + @media (max-width: 1150px) { + grid-area: unset; + + &:global(.OnlyClusterIssues) { + grid-row: auto; + } } .SubHeader { - .Icon { + padding-top: 0; + padding-bottom: 0; + + :global(.Icon) { font-size: 130%; - color: $colorError; + color: var(--colorError); } } - .Table { - .TableHead { - background-color: transparent; - border-bottom: 1px solid $borderFaintColor; - - .TableCell { - padding-top: 0; - } - } - - .TableCell { - white-space: nowrap; - text-overflow: ellipsis; - - &.message { - flex-grow: 3; - } - - &.object { - flex-grow: 2; - } - } + .message { + white-space: nowrap; + text-overflow: ellipsis; + flex-grow: 3; } - .no-issues { - .Icon { - color: white; - } + .object { + white-space: nowrap; + text-overflow: ellipsis; + flex-grow: 2; + } + .noIssues { .ok-title { font-size: large; - color: $textColorAccent; + color: var(--textColorAccent); font-weight: bold; } + + .allGood { + color: white; + } } } - -.OnlyClusterIssues { - grid-row: row1-start / row2-end; -} diff --git a/src/renderer/components/+cluster/cluster-issues.tsx b/src/renderer/components/+cluster/cluster-issues.tsx index 3f9b5c6b90..6165fde8bd 100644 --- a/src/renderer/components/+cluster/cluster-issues.tsx +++ b/src/renderer/components/+cluster/cluster-issues.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./cluster-issues.scss"; +import styles from "./cluster-issues.module.css"; import React from "react"; import { observer } from "mobx-react"; @@ -121,10 +121,10 @@ export class ClusterIssues extends React.Component { selected={selfLink === kubeSelectedUrlParam.get()} onClick={prevDefault(() => toggleDetails(selfLink))} > - + {message} - + {getName()} @@ -148,8 +148,8 @@ export class ClusterIssues extends React.Component { if (!warnings.length) { return ( -
-
+
+
No issues found
Everything is fine in the Cluster
@@ -158,7 +158,7 @@ export class ClusterIssues extends React.Component { return ( <> - + {" "} <>Warnings: {warnings.length} @@ -186,7 +186,7 @@ export class ClusterIssues extends React.Component { render() { return ( -
+
{this.renderContent()}
); diff --git a/src/renderer/components/+cluster/cluster-metric-switchers.tsx b/src/renderer/components/+cluster/cluster-metric-switchers.tsx index b80dd10527..aa2ff2985f 100644 --- a/src/renderer/components/+cluster/cluster-metric-switchers.tsx +++ b/src/renderer/components/+cluster/cluster-metric-switchers.tsx @@ -19,8 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./cluster-metric-switchers.scss"; - import React from "react"; import { observer } from "mobx-react"; import { nodesStore } from "../+nodes/nodes.store"; @@ -36,7 +34,7 @@ export const ClusterMetricSwitchers = observer(() => { const disableMetrics = !metricsValues.length; return ( -
+
{
-
+
{ } if (!memoryCapacity || !cpuCapacity) { - return ; + return ; } return ( @@ -106,12 +106,13 @@ export const ClusterMetrics = observer(() => { timeLabelStep={5} showLegend={false} plugins={[ZebraStripes]} + className={styles.chart} /> ); }; return ( -
+
{renderMetrics()}
diff --git a/src/renderer/components/+cluster/cluster-overview.scss b/src/renderer/components/+cluster/cluster-overview.module.css similarity index 80% rename from src/renderer/components/+cluster/cluster-overview.scss rename to src/renderer/components/+cluster/cluster-overview.module.css index 8dba6d65e8..7700ec038b 100644 --- a/src/renderer/components/+cluster/cluster-overview.scss +++ b/src/renderer/components/+cluster/cluster-overview.module.css @@ -20,28 +20,22 @@ */ .ClusterOverview { - $gridGap: $margin * 2; - position: relative; height: 100%; min-height: 650px; display: grid; - grid-gap: $gridGap; - grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); + grid-gap: calc(var(--margin) * 2); + grid-template-areas: + "barcharts piechars" + "issues issues"; + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); grid-template-rows: [row1-start] 1fr [row2-start] 1fr; - @include media(">1600px") { - grid-template-columns: 1fr 1fr; - } - - > *:not(.Spinner) { - padding: $gridGap; - background: $contentColor; - - > .SubHeader { - padding-top: 0; - } + @media (max-width: 1150px) { + grid-template-columns: minmax(500px, 1fr); + grid-template-rows: 1fr; + grid-template-areas: none; } } diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index 248c7de427..b4fa25da45 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./cluster-overview.scss"; +import styles from "./cluster-overview.module.css"; import React from "react"; import { reaction } from "mobx"; @@ -101,7 +101,7 @@ export class ClusterOverview extends React.Component { return ( -
+
{this.renderClusterOverview(isLoaded, isMetricHidden)}
diff --git a/src/renderer/components/+cluster/cluster-metric-switchers.scss b/src/renderer/components/+cluster/cluster-pie-charts.module.css similarity index 80% rename from src/renderer/components/+cluster/cluster-metric-switchers.scss rename to src/renderer/components/+cluster/cluster-pie-charts.module.css index ef8dab156f..1998c11989 100644 --- a/src/renderer/components/+cluster/cluster-metric-switchers.scss +++ b/src/renderer/components/+cluster/cluster-pie-charts.module.css @@ -19,10 +19,15 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.ClusterMetricSwitchers { - margin-bottom: $margin * 2; +.empty { + background: var(--contentColor); + min-height: 280px; + text-align: center; + padding: calc(var(--padding) * 2) 0; +} - .metric-switch { - text-align: right; - } +.chart { + --flex-gap: calc(var(--padding) * 2); + background: var(--contentColor); + padding: calc(var(--padding) * 2) var(--padding); } \ No newline at end of file diff --git a/src/renderer/components/+cluster/cluster-pie-charts.scss b/src/renderer/components/+cluster/cluster-pie-charts.scss deleted file mode 100644 index 4f2c392bb7..0000000000 --- a/src/renderer/components/+cluster/cluster-pie-charts.scss +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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. - */ - -.ClusterPieCharts { - background: transparent!important; - padding: 0!important; - - .empty { - background: $contentColor; - min-height: 280px; - text-align: center; - padding: $padding * 2; - } - - .NodeCharts { - margin-bottom: 0; - } - - .chart { - --flex-gap: #{$padding * 2}; - background: $contentColor; - padding: $padding * 2 $padding; - - .chart-title { - margin-bottom: 0; - } - - .legend { - --flex-gap: #{$padding}; - } - } -} \ No newline at end of file diff --git a/src/renderer/components/+cluster/cluster-pie-charts.tsx b/src/renderer/components/+cluster/cluster-pie-charts.tsx index 76bb6967f6..5079866870 100644 --- a/src/renderer/components/+cluster/cluster-pie-charts.tsx +++ b/src/renderer/components/+cluster/cluster-pie-charts.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./cluster-pie-charts.scss"; +import styles from "./cluster-pie-charts.module.css"; import React from "react"; import { observer } from "mobx-react"; @@ -29,7 +29,7 @@ import { Icon } from "../icon"; import { nodesStore } from "../+nodes/nodes.store"; import { ChartData, PieChart } from "../chart"; import { ClusterNoMetrics } from "./cluster-no-metrics"; -import { bytesToUnits } from "../../utils"; +import { bytesToUnits, cssNames } from "../../utils"; import { ThemeStore } from "../../theme.store"; import { getMetricLastPoints } from "../../../common/k8s-api/endpoints/metrics.api"; @@ -173,8 +173,8 @@ export const ClusterPieCharts = observer(() => { }; return ( -
-
+
+
{ /> {cpuLimitsOverload && renderLimitWarning()}
-
+
{ /> {memoryLimitsOverload && renderLimitWarning()}
-
+
{ if (!nodes.length) { return ( -
+
No Nodes Available.
@@ -237,14 +237,14 @@ export const ClusterPieCharts = observer(() => { const { memoryCapacity, cpuCapacity, podCapacity } = getMetricLastPoints(clusterOverviewStore.metrics); if (!memoryCapacity || !cpuCapacity || !podCapacity) { - return ; + return ; } return renderCharts(); }; return ( -
+
{renderContent()}
); From 169f443c3b3b2a4b00fb26fdb599321eda996eeb Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Fri, 22 Oct 2021 09:23:51 -0400 Subject: [PATCH 37/37] attempt to fix immer/merge bug (#4107) Co-authored-by: Roman --- src/renderer/utils/storageHelper.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/renderer/utils/storageHelper.ts b/src/renderer/utils/storageHelper.ts index 80c5df8fba..8ff99c5b84 100755 --- a/src/renderer/utils/storageHelper.ts +++ b/src/renderer/utils/storageHelper.ts @@ -22,7 +22,7 @@ // Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.) import { action, comparer, makeObservable, observable, toJS, when, } from "mobx"; import produce, { Draft, isDraft } from "immer"; -import { isEqual } from "lodash"; +import { isEqual, isPlainObject } from "lodash"; import logger from "../../main/logger"; export interface StorageAdapter { @@ -80,7 +80,7 @@ export class StorageHelper { const notDefault = !this.isDefaultValue(data); if (notEmpty && notDefault) { - this.merge(data); + this.set(data); } this.initialized = true; @@ -159,9 +159,11 @@ export class StorageHelper { if (newValue && !isDraft(newValue)) { Object.assign(draft, newValue); } - } else { + } else if (isPlainObject(value)) { Object.assign(draft, value); } + + return draft; }); this.set(nextValue);