From c113a6030e793a118f16ea2242de62ceef4da0c5 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 9 Dec 2020 21:03:16 +0300 Subject: [PATCH 1/9] Fixing logs autoscroll behavior (#1720) * Fixing autoscroll behavior Signed-off-by: Alex Andreev * Making statement simpler to read Signed-off-by: Alex Andreev --- src/renderer/components/dock/pod-log-list.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/renderer/components/dock/pod-log-list.tsx b/src/renderer/components/dock/pod-log-list.tsx index 392e0eefc0..a3ab172a16 100644 --- a/src/renderer/components/dock/pod-log-list.tsx +++ b/src/renderer/components/dock/pod-log-list.tsx @@ -95,15 +95,9 @@ export class PodLogList extends React.Component { @action setLastLineVisibility = (props: ListOnScrollProps) => { const { scrollHeight, clientHeight } = this.virtualListDiv.current; - const { scrollOffset, scrollDirection } = props; + const { scrollOffset } = props; - if (scrollDirection == "backward") { - this.isLastLineVisible = false; - } else { - if (clientHeight + scrollOffset === scrollHeight) { - this.isLastLineVisible = true; - } - } + this.isLastLineVisible = (clientHeight + scrollOffset) === scrollHeight; }; /** From d128219328f9cc8c1528e715f0858649b44eebd0 Mon Sep 17 00:00:00 2001 From: Panu Horsmalahti Date: Thu, 10 Dec 2020 12:39:37 +0200 Subject: [PATCH 2/9] Remove symlink on extension install and manual runtime uninstall (#1718) * Remove broken symlink on extension install Signed-off-by: Panu Horsmalahti * Remove broken symlink on manual uninstall during runtime Signed-off-by: Panu Horsmalahti --- .../__tests__/extension-discovery.test.ts | 5 +- src/extensions/extension-discovery.ts | 58 +++++++++++++------ 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/extensions/__tests__/extension-discovery.test.ts b/src/extensions/__tests__/extension-discovery.test.ts index 0e72cf16fb..0317319329 100644 --- a/src/extensions/__tests__/extension-discovery.test.ts +++ b/src/extensions/__tests__/extension-discovery.test.ts @@ -18,7 +18,7 @@ const mockedWatch = watch as jest.MockedFunction; describe("ExtensionDiscovery", () => { it("emits add for added extension", async done => { - globalThis.__non_webpack_require__.mockImplementationOnce(() => ({ + globalThis.__non_webpack_require__.mockImplementation(() => ({ name: "my-extension" })); let addHandler: (filePath: string) => void; @@ -61,9 +61,6 @@ describe("ExtensionDiscovery", () => { }); it("doesn't emit add for added file under extension", async done => { - globalThis.__non_webpack_require__.mockImplementationOnce(() => ({ - name: "my-extension" - })); let addHandler: (filePath: string) => void; const mockWatchInstance: any = { diff --git a/src/extensions/extension-discovery.ts b/src/extensions/extension-discovery.ts index 287c232be9..2b26d79a9c 100644 --- a/src/extensions/extension-discovery.ts +++ b/src/extensions/extension-discovery.ts @@ -155,23 +155,26 @@ export class ExtensionDiscovery { .on("unlinkDir", this.handleWatchUnlinkDir); } - handleWatchFileAdd = async (filePath: string) => { + handleWatchFileAdd = async (manifestPath: string) => { // e.g. "foo/package.json" - const relativePath = path.relative(this.localFolderPath, filePath); + const relativePath = path.relative(this.localFolderPath, manifestPath); // Converts "foo/package.json" to ["foo", "package.json"], where length of 2 implies // that the added file is in a folder under local folder path. // This safeguards against a file watch being triggered under a sub-directory which is not an extension. const isUnderLocalFolderPath = relativePath.split(path.sep).length === 2; - if (path.basename(filePath) === manifestFilename && isUnderLocalFolderPath) { + if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) { try { - const absPath = path.dirname(filePath); + const absPath = path.dirname(manifestPath); // this.loadExtensionFromPath updates this.packagesJson - const extension = await this.loadExtensionFromPath(absPath); + const extension = await this.loadExtensionFromFolder(absPath); if (extension) { + // Remove a broken symlink left by a previous installation if it exists. + await this.removeSymlinkByManifestPath(manifestPath); + // Install dependencies for the new extension await this.installPackages(); @@ -199,6 +202,9 @@ export class ExtensionDiscovery { .find(([, extensionFolder]) => filePath === extensionFolder)?.[0]; if (extensionName !== undefined) { + // If the extension is deleted manually while the application is running, also remove the symlink + await this.removeSymlinkByPackageName(extensionName); + delete this.packagesJson.dependencies[extensionName]; // Reinstall dependencies to remove the extension from package.json @@ -216,6 +222,26 @@ export class ExtensionDiscovery { } }; + /** + * Remove the symlink under node_modules if exists. + * If we don't remove the symlink, the uninstall would leave a non-working symlink, + * which wouldn't be fixed if the extension was reinstalled, causing the extension not to work. + * @param name e.g. "@mirantis/lens-extension-cc" + */ + removeSymlinkByPackageName(name: string) { + return fs.remove(this.getInstalledPath(name)); + } + + /** + * Remove the symlink under node_modules if it exists. + * @param manifestPath Path to package.json + */ + removeSymlinkByManifestPath(manifestPath: string) { + const manifestJson = __non_webpack_require__(manifestPath); + + return this.removeSymlinkByPackageName(manifestJson.name); + } + /** * Uninstalls extension. * The application will detect the folder unlink and remove the extension from the UI automatically. @@ -224,16 +250,7 @@ export class ExtensionDiscovery { async uninstallExtension({ absolutePath, manifest }: InstalledExtension) { logger.info(`${logModule} Uninstalling ${manifest.name}`); - // remove the symlink under node_modules. - // If we don't remove the symlink, the uninstall would leave a non-working symlink, - // which wouldn't be fixed if the extension was reinstalled, causing the extension not to work. - await fs.remove(this.getInstalledPath(manifest.name)); - - const exists = await fs.pathExists(absolutePath); - - if (!exists) { - throw new Error(`Extension path ${absolutePath} doesn't exist`); - } + await this.removeSymlinkByPackageName(manifest.name); // fs.remove does nothing if the path doesn't exist anymore await fs.remove(absolutePath); @@ -290,6 +307,10 @@ export class ExtensionDiscovery { return path.join(this.getInstalledPath(name), manifestFilename); } + /** + * Returns InstalledExtension from path to package.json file. + * Also updates this.packagesJson. + */ protected async getByManifest(manifestPath: string, { isBundled = false }: { isBundled?: boolean; } = {}): Promise { @@ -349,7 +370,7 @@ export class ExtensionDiscovery { } const absPath = path.resolve(folderPath, fileName); - const extension = await this.loadExtensionFromPath(absPath, { isBundled: true }); + const extension = await this.loadExtensionFromFolder(absPath, { isBundled: true }); if (extension) { extensions.push(extension); @@ -384,7 +405,7 @@ export class ExtensionDiscovery { continue; } - const extension = await this.loadExtensionFromPath(absPath); + const extension = await this.loadExtensionFromFolder(absPath); if (extension) { extensions.push(extension); @@ -398,8 +419,9 @@ export class ExtensionDiscovery { /** * Loads extension from absolute path, updates this.packagesJson to include it and returns the extension. + * @param absPath Folder path to extension */ - async loadExtensionFromPath(absPath: string, { isBundled = false }: { + async loadExtensionFromFolder(absPath: string, { isBundled = false }: { isBundled?: boolean; } = {}): Promise { const manifestPath = path.resolve(absPath, manifestFilename); From 1f624147ca1d6b8b689afff585467183d094c471 Mon Sep 17 00:00:00 2001 From: Violetta <38247153+vshakirova@users.noreply.github.com> Date: Thu, 10 Dec 2020 23:18:30 +0400 Subject: [PATCH 3/9] Fix Deployment Scale Button "minus" (#1728) Signed-off-by: vshakirova --- .../deployment-scale-dialog.test.tsx | 48 ++++++++++++------- .../deployment-scale-dialog.tsx | 4 +- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/renderer/components/+workloads-deployments/deployment-scale-dialog.test.tsx b/src/renderer/components/+workloads-deployments/deployment-scale-dialog.test.tsx index ead4a37487..e3d18669f9 100644 --- a/src/renderer/components/+workloads-deployments/deployment-scale-dialog.test.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-scale-dialog.test.tsx @@ -128,33 +128,45 @@ describe("", () => { const initReplicas = 1; deploymentApi.getReplicas = jest.fn().mockImplementationOnce(async () => initReplicas); - const { getByTestId } = render(); + const component = render(); DeploymentScaleDialog.open(dummyDeployment); await waitFor(async () => { - const desiredScale = await getByTestId("desired-scale"); - - expect(desiredScale).toHaveTextContent(`${initReplicas}`); + expect(await component.getByTestId("desired-scale")).toHaveTextContent(`${initReplicas}`); + expect(await component.getByTestId("current-scale")).toHaveTextContent(`${initReplicas}`); + expect((await component.baseElement.querySelector("input").value)).toBe(`${initReplicas}`); }); - const up = await getByTestId("desired-replicas-up"); - const down = await getByTestId("desired-replicas-down"); + const up = await component.getByTestId("desired-replicas-up"); + const down = await component.getByTestId("desired-replicas-down"); fireEvent.click(up); - expect(await getByTestId("desired-scale")).toHaveTextContent(`${initReplicas + 1}`); - fireEvent.click(down); - expect(await getByTestId("desired-scale")).toHaveTextContent("1"); - // edge case, desiredScale must > 0 - fireEvent.click(down); - fireEvent.click(down); - expect(await getByTestId("desired-scale")).toHaveTextContent("1"); - const times = 120; + expect(await component.getByTestId("desired-scale")).toHaveTextContent(`${initReplicas + 1}`); + expect(await component.getByTestId("current-scale")).toHaveTextContent(`${initReplicas}`); + expect((await component.baseElement.querySelector("input").value)).toBe(`${initReplicas + 1}`); + + fireEvent.click(down); + expect(await component.getByTestId("desired-scale")).toHaveTextContent(`${initReplicas}`); + expect(await component.getByTestId("current-scale")).toHaveTextContent(`${initReplicas}`); + expect((await component.baseElement.querySelector("input").value)).toBe(`${initReplicas}`); + + // edge case, desiredScale must = 0 + let times = 10; + + for (let i = 0; i < times; i++) { + fireEvent.click(down); + } + expect(await component.getByTestId("desired-scale")).toHaveTextContent("0"); + expect((await component.baseElement.querySelector("input").value)).toBe("0"); + + // edge case, desiredScale must = 100 scaleMax (100) + times = 120; - // edge case, desiredScale must < scaleMax (100) for (let i = 0; i < times; i++) { fireEvent.click(up); } - expect(await getByTestId("desired-scale")).toHaveTextContent("100"); + expect(await component.getByTestId("desired-scale")).toHaveTextContent("100"); + expect((component.baseElement.querySelector("input").value)).toBe("100"); + expect(await component.getByTestId("warning")) + .toHaveTextContent("High number of replicas may cause cluster performance issues"); }); - }); - diff --git a/src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx b/src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx index 5fb43368d5..42105aeac7 100644 --- a/src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-scale-dialog.tsx @@ -91,7 +91,7 @@ export class DeploymentScaleDialog extends Component { }; desiredReplicasDown = () => { - this.desiredReplicas > 1 && this.desiredReplicas--; + this.desiredReplicas > 0 && this.desiredReplicas--; }; renderContents() { @@ -124,7 +124,7 @@ export class DeploymentScaleDialog extends Component { {warning && -
+
High number of replicas may cause cluster performance issues
From d620aa6722d9e41e0ae13f2b562a1554ad7be6bd Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 11 Dec 2020 11:21:43 +0300 Subject: [PATCH 4/9] Replace cronjob api version to batch/v1beta1 (#1747) Signed-off-by: Alex Andreev --- .../components/+workloads-cronjobs/cronjob-details.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx b/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx index ad24ceede6..fe319c0a7e 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx +++ b/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx @@ -91,14 +91,14 @@ export class CronJobDetails extends React.Component { kubeObjectDetailRegistry.add({ kind: "CronJob", - apiVersions: ["batch/v1"], + apiVersions: ["batch/v1beta1"], components: { Details: (props) => } }); kubeObjectDetailRegistry.add({ kind: "CronJob", - apiVersions: ["batch/v1"], + apiVersions: ["batch/v1beta1"], priority: 5, components: { Details: (props) => From eb7703f3897c611e484b0f565c7024dafe6cd171 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 11 Dec 2020 11:21:35 +0200 Subject: [PATCH 5/9] Fix buggy node metrics/chart (#1748) * fix buggy node metrics/chart Signed-off-by: Jari Kolehmainen * fix linter error Signed-off-by: Jari Kolehmainen --- src/renderer/components/+nodes/node-charts.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/components/+nodes/node-charts.tsx b/src/renderer/components/+nodes/node-charts.tsx index 5900e574df..52caa37ba7 100644 --- a/src/renderer/components/+nodes/node-charts.tsx +++ b/src/renderer/components/+nodes/node-charts.tsx @@ -26,9 +26,11 @@ export const NodeCharts = observer(() => { const [ memoryUsage, memoryRequests, + _memoryLimits, // eslint-disable-line unused-imports/no-unused-vars-ts memoryCapacity, cpuUsage, cpuRequests, + _cpuLimits, // eslint-disable-line unused-imports/no-unused-vars-ts cpuCapacity, podUsage, podCapacity, From 69e5edd6f20c65d86aa576bbe8bf36e07000bed2 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 11 Dec 2020 13:13:40 +0200 Subject: [PATCH 6/9] Use correct apiversion for HPA details (#1745) Signed-off-by: Jari Kolehmainen --- src/renderer/components/+config-autoscalers/hpa-details.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/+config-autoscalers/hpa-details.tsx b/src/renderer/components/+config-autoscalers/hpa-details.tsx index 7cf5e3142f..b6fa920b37 100644 --- a/src/renderer/components/+config-autoscalers/hpa-details.tsx +++ b/src/renderer/components/+config-autoscalers/hpa-details.tsx @@ -134,7 +134,7 @@ export class HpaDetails extends React.Component { kubeObjectDetailRegistry.add({ kind: "HorizontalPodAutoscaler", - apiVersions: ["autoscaling/v1"], + apiVersions: ["autoscaling/v2beta1"], components: { Details: (props) => } @@ -142,7 +142,7 @@ kubeObjectDetailRegistry.add({ kubeObjectDetailRegistry.add({ kind: "HorizontalPodAutoscaler", - apiVersions: ["autoscaling/v1"], + apiVersions: ["autoscaling/v2beta1"], priority: 5, components: { Details: (props) => From 0388c68f4f6fa07411311768102eb4abe6a95970 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 11 Dec 2020 14:07:07 +0200 Subject: [PATCH 7/9] Ensure telemetry report is sent on extensions start (#1753) Signed-off-by: Lauri Nevala --- extensions/telemetry/src/tracker.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/telemetry/src/tracker.ts b/extensions/telemetry/src/tracker.ts index 398142305c..28595a6a93 100644 --- a/extensions/telemetry/src/tracker.ts +++ b/extensions/telemetry/src/tracker.ts @@ -74,6 +74,7 @@ export class Tracker extends Util.Singleton { } reportPeriodically() { + this.reportData(); this.reportInterval = setInterval(() => { this.reportData(); }, 60 * 60 * 1000); // report every 1h From 1317a626502be3777e10b24a7aca99113678396c Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 11 Dec 2020 08:36:47 +0300 Subject: [PATCH 8/9] ClusterOverview page refactorings (#1696) * ClusterOverview page refactorings Signed-off-by: Alex Andreev * Minor test fix for MainLayoutHeader Signed-off-by: Alex Andreev * Replacing class name in tests Signed-off-by: Alex Andreev * Remove unnecessary parenthesis Signed-off-by: Alex Andreev --- integration/__tests__/app.tests.ts | 2 +- .../components/+cluster/cluster-issues.scss | 7 +- .../+cluster/cluster-metric-switchers.tsx | 8 +- .../components/+cluster/cluster-metrics.tsx | 9 +-- .../{cluster.scss => cluster-overview.scss} | 2 +- ...ter.store.ts => cluster-overview.store.ts} | 33 ++------ .../components/+cluster/cluster-overview.tsx | 79 +++++++++++++++++++ .../+cluster/cluster-pie-charts.tsx | 8 +- src/renderer/components/+cluster/cluster.tsx | 74 ----------------- src/renderer/components/app.tsx | 4 +- 10 files changed, 104 insertions(+), 122 deletions(-) rename src/renderer/components/+cluster/{cluster.scss => cluster-overview.scss} (95%) rename src/renderer/components/+cluster/{cluster.store.ts => cluster-overview.store.ts} (76%) create mode 100644 src/renderer/components/+cluster/cluster-overview.tsx delete mode 100644 src/renderer/components/+cluster/cluster.tsx diff --git a/integration/__tests__/app.tests.ts b/integration/__tests__/app.tests.ts index 8672076226..14b55a3c74 100644 --- a/integration/__tests__/app.tests.ts +++ b/integration/__tests__/app.tests.ts @@ -226,7 +226,7 @@ describe("Lens integration tests", () => { pages: [{ name: "Cluster", href: "cluster", - expectedSelector: "div.Cluster div.label", + expectedSelector: "div.ClusterOverview div.label", expectedText: "Master" }] }, diff --git a/src/renderer/components/+cluster/cluster-issues.scss b/src/renderer/components/+cluster/cluster-issues.scss index b552cf6877..8886fa40e3 100644 --- a/src/renderer/components/+cluster/cluster-issues.scss +++ b/src/renderer/components/+cluster/cluster-issues.scss @@ -1,17 +1,14 @@ .ClusterIssues { min-height: 350px; position: relative; + grid-column-start: 1; + grid-column-end: 3; @include media("<1024px") { grid-column-start: 1!important; grid-column-end: 1!important; } - &.wide { - grid-column-start: 1; - grid-column-end: 3; - } - .SubHeader { .Icon { font-size: 130%; diff --git a/src/renderer/components/+cluster/cluster-metric-switchers.tsx b/src/renderer/components/+cluster/cluster-metric-switchers.tsx index f2e090cdbb..02ffbd8755 100644 --- a/src/renderer/components/+cluster/cluster-metric-switchers.tsx +++ b/src/renderer/components/+cluster/cluster-metric-switchers.tsx @@ -6,10 +6,10 @@ import { observer } from "mobx-react"; import { nodesStore } from "../+nodes/nodes.store"; import { cssNames } from "../../utils"; import { Radio, RadioGroup } from "../radio"; -import { clusterStore, MetricNodeRole, MetricType } from "./cluster.store"; +import { clusterOverviewStore, MetricNodeRole, MetricType } from "./cluster-overview.store"; export const ClusterMetricSwitchers = observer(() => { - const { metricType, metricNodeRole, getMetricsValues, metrics } = clusterStore; + const { metricType, metricNodeRole, getMetricsValues, metrics } = clusterOverviewStore; const { masterNodes, workerNodes } = nodesStore; const metricsValues = getMetricsValues(metrics); const disableRoles = !masterNodes.length || !workerNodes.length; @@ -22,7 +22,7 @@ export const ClusterMetricSwitchers = observer(() => { asButtons className={cssNames("RadioGroup flex gaps", { disabled: disableRoles })} value={metricNodeRole} - onChange={(metric: MetricNodeRole) => clusterStore.metricNodeRole = metric} + onChange={(metric: MetricNodeRole) => clusterOverviewStore.metricNodeRole = metric} > Master} value={MetricNodeRole.MASTER}/> Worker} value={MetricNodeRole.WORKER}/> @@ -33,7 +33,7 @@ export const ClusterMetricSwitchers = observer(() => { asButtons className={cssNames("RadioGroup flex gaps", { disabled: disableMetrics })} value={metricType} - onChange={(value: MetricType) => clusterStore.metricType = value} + onChange={(value: MetricType) => clusterOverviewStore.metricType = value} > CPU} value={MetricType.CPU}/> Memory} value={MetricType.MEMORY}/> diff --git a/src/renderer/components/+cluster/cluster-metrics.tsx b/src/renderer/components/+cluster/cluster-metrics.tsx index b049cfc2f4..6461bae7f3 100644 --- a/src/renderer/components/+cluster/cluster-metrics.tsx +++ b/src/renderer/components/+cluster/cluster-metrics.tsx @@ -3,7 +3,7 @@ import "./cluster-metrics.scss"; import React from "react"; import { observer } from "mobx-react"; import { ChartOptions, ChartPoint } from "chart.js"; -import { clusterStore, MetricType } from "./cluster.store"; +import { clusterOverviewStore, MetricType } from "./cluster-overview.store"; import { BarChart } from "../chart"; import { bytesToUnits } from "../../utils"; import { Spinner } from "../spinner"; @@ -13,10 +13,9 @@ import { ClusterMetricSwitchers } from "./cluster-metric-switchers"; import { getMetricLastPoints } from "../../api/endpoints/metrics.api"; export const ClusterMetrics = observer(() => { - const { metricType, metricNodeRole, getMetricsValues, metricsLoaded, metrics, liveMetrics } = clusterStore; - const { memoryCapacity, cpuCapacity } = getMetricLastPoints(clusterStore.metrics); + const { metricType, metricNodeRole, getMetricsValues, metricsLoaded, metrics } = clusterOverviewStore; + const { memoryCapacity, cpuCapacity } = getMetricLastPoints(clusterOverviewStore.metrics); const metricValues = getMetricsValues(metrics); - const liveMetricValues = getMetricsValues(liveMetrics); const colors = { cpu: "#3D90CE", memory: "#C93DCE" }; const data = metricValues.map(value => ({ x: value[0], @@ -70,7 +69,7 @@ export const ClusterMetrics = observer(() => { const options = metricType === MetricType.CPU ? cpuOptions : memoryOptions; const renderMetrics = () => { - if ((!metricValues.length || !liveMetricValues.length) && !metricsLoaded) { + if (!metricValues.length && !metricsLoaded) { return ; } diff --git a/src/renderer/components/+cluster/cluster.scss b/src/renderer/components/+cluster/cluster-overview.scss similarity index 95% rename from src/renderer/components/+cluster/cluster.scss rename to src/renderer/components/+cluster/cluster-overview.scss index 32739c378c..c0534f4fff 100644 --- a/src/renderer/components/+cluster/cluster.scss +++ b/src/renderer/components/+cluster/cluster-overview.scss @@ -1,4 +1,4 @@ -.Cluster { +.ClusterOverview { $gridGap: $margin * 2; position: relative; diff --git a/src/renderer/components/+cluster/cluster.store.ts b/src/renderer/components/+cluster/cluster-overview.store.ts similarity index 76% rename from src/renderer/components/+cluster/cluster.store.ts rename to src/renderer/components/+cluster/cluster-overview.store.ts index 04bc1d8658..64faa2394c 100644 --- a/src/renderer/components/+cluster/cluster.store.ts +++ b/src/renderer/components/+cluster/cluster-overview.store.ts @@ -1,4 +1,4 @@ -import { observable, reaction, when } from "mobx"; +import { action, observable, reaction, when } from "mobx"; import { KubeObjectStore } from "../../kube-object.store"; import { Cluster, clusterApi, IClusterMetrics } from "../../api/endpoints"; import { autobind, createStorage } from "../../utils"; @@ -17,11 +17,10 @@ export enum MetricNodeRole { } @autobind() -export class ClusterStore extends KubeObjectStore { +export class ClusterOverviewStore extends KubeObjectStore { api = clusterApi; @observable metrics: Partial = {}; - @observable liveMetrics: Partial = {}; @observable metricsLoaded = false; @observable metricType: MetricType; @observable metricNodeRole: MetricNodeRole; @@ -46,9 +45,8 @@ export class ClusterStore extends KubeObjectStore { reaction(() => this.metricNodeRole, () => { if (!this.metricsLoaded) return; this.metrics = {}; - this.liveMetrics = {}; this.metricsLoaded = false; - this.getAllMetrics(); + this.loadMetrics(); }); // check which node type to select @@ -60,33 +58,16 @@ export class ClusterStore extends KubeObjectStore { }); } + @action async loadMetrics(params?: IMetricsReqParams) { await when(() => nodesStore.isLoaded); const { masterNodes, workerNodes } = nodesStore; const nodes = this.metricNodeRole === MetricNodeRole.MASTER && masterNodes.length ? masterNodes : workerNodes; - return clusterApi.getMetrics(nodes.map(node => node.getName()), params); - } - - async getAllMetrics() { - await this.getMetrics(); - await this.getLiveMetrics(); + this.metrics = await clusterApi.getMetrics(nodes.map(node => node.getName()), params); this.metricsLoaded = true; } - async getMetrics() { - this.metrics = await this.loadMetrics(); - } - - async getLiveMetrics() { - const step = 3; - const range = 15; - const end = Date.now() / 1000; - const start = end - range; - - this.liveMetrics = await this.loadMetrics({ start, end, step, range }); - } - getMetricsValues(source: Partial): [number, string][] { switch (this.metricType) { case MetricType.CPU: @@ -111,5 +92,5 @@ export class ClusterStore extends KubeObjectStore { } } -export const clusterStore = new ClusterStore(); -apiManager.registerStore(clusterStore); +export const clusterOverviewStore = new ClusterOverviewStore(); +apiManager.registerStore(clusterOverviewStore); diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx new file mode 100644 index 0000000000..104c6fd022 --- /dev/null +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -0,0 +1,79 @@ +import "./cluster-overview.scss"; + +import React from "react"; +import { reaction } from "mobx"; +import { disposeOnUnmount, observer } from "mobx-react"; + +import { eventStore } from "../+events/event.store"; +import { nodesStore } from "../+nodes/nodes.store"; +import { podsStore } from "../+workloads-pods/pods.store"; +import { getHostedCluster } from "../../../common/cluster-store"; +import { isAllowedResource } from "../../../common/rbac"; +import { KubeObjectStore } from "../../kube-object.store"; +import { interval } from "../../utils"; +import { TabLayout } from "../layout/tab-layout"; +import { Spinner } from "../spinner"; +import { ClusterIssues } from "./cluster-issues"; +import { ClusterMetrics } from "./cluster-metrics"; +import { clusterOverviewStore } from "./cluster-overview.store"; +import { ClusterPieCharts } from "./cluster-pie-charts"; + +@observer +export class ClusterOverview extends React.Component { + private stores: KubeObjectStore[] = []; + private subscribers: Array<() => void> = []; + private metricPoller = interval(60, this.loadMetrics); + + @disposeOnUnmount + fetchMetrics = reaction( + () => clusterOverviewStore.metricNodeRole, // Toggle Master/Worker node switcher + () => this.metricPoller.restart(true) + ); + + loadMetrics() { + getHostedCluster().available && clusterOverviewStore.loadMetrics(); + } + + async componentDidMount() { + if (isAllowedResource("nodes")) { + this.stores.push(nodesStore); + } + + if (isAllowedResource("pods")) { + this.stores.push(podsStore); + } + + if (isAllowedResource("events")) { + this.stores.push(eventStore); + } + + await Promise.all(this.stores.map(store => store.loadAll())); + this.loadMetrics(); + + this.subscribers = this.stores.map(store => store.subscribe()); + this.metricPoller.start(); + } + + componentWillUnmount() { + this.subscribers.forEach(dispose => dispose()); // unsubscribe all + this.metricPoller.stop(); + } + + render() { + const isLoaded = nodesStore.isLoaded && podsStore.isLoaded; + + return ( + +
+ {!isLoaded ? : ( + <> + + + + + )} +
+
+ ); + } +} diff --git a/src/renderer/components/+cluster/cluster-pie-charts.tsx b/src/renderer/components/+cluster/cluster-pie-charts.tsx index 246af6a3ca..684233f8ca 100644 --- a/src/renderer/components/+cluster/cluster-pie-charts.tsx +++ b/src/renderer/components/+cluster/cluster-pie-charts.tsx @@ -4,7 +4,7 @@ import React from "react"; import { observer } from "mobx-react"; import { t, Trans } from "@lingui/macro"; import { useLingui } from "@lingui/react"; -import { clusterStore, MetricNodeRole } from "./cluster.store"; +import { clusterOverviewStore, MetricNodeRole } from "./cluster-overview.store"; import { Spinner } from "../spinner"; import { Icon } from "../icon"; import { nodesStore } from "../+nodes/nodes.store"; @@ -27,7 +27,7 @@ export const ClusterPieCharts = observer(() => { }; const renderCharts = () => { - const data = getMetricLastPoints(clusterStore.metrics); + const data = getMetricLastPoints(clusterOverviewStore.metrics); const { memoryUsage, memoryRequests, memoryCapacity, memoryLimits } = data; const { cpuUsage, cpuRequests, cpuCapacity, cpuLimits } = data; const { podUsage, podCapacity } = data; @@ -173,7 +173,7 @@ export const ClusterPieCharts = observer(() => { const renderContent = () => { const { masterNodes, workerNodes } = nodesStore; - const { metricNodeRole, metricsLoaded } = clusterStore; + const { metricNodeRole, metricsLoaded } = clusterOverviewStore; const nodes = metricNodeRole === MetricNodeRole.MASTER ? masterNodes : workerNodes; if (!nodes.length) { @@ -192,7 +192,7 @@ export const ClusterPieCharts = observer(() => {
); } - const { memoryCapacity, cpuCapacity, podCapacity } = getMetricLastPoints(clusterStore.metrics); + const { memoryCapacity, cpuCapacity, podCapacity } = getMetricLastPoints(clusterOverviewStore.metrics); if (!memoryCapacity || !cpuCapacity || !podCapacity) { return ; diff --git a/src/renderer/components/+cluster/cluster.tsx b/src/renderer/components/+cluster/cluster.tsx deleted file mode 100644 index f99f65c479..0000000000 --- a/src/renderer/components/+cluster/cluster.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import "./cluster.scss"; - -import React from "react"; -import { computed, reaction } from "mobx"; -import { disposeOnUnmount, observer } from "mobx-react"; -import { TabLayout } from "../layout/tab-layout"; -import { ClusterIssues } from "./cluster-issues"; -import { Spinner } from "../spinner"; -import { cssNames, interval, isElectron } from "../../utils"; -import { ClusterPieCharts } from "./cluster-pie-charts"; -import { ClusterMetrics } from "./cluster-metrics"; -import { nodesStore } from "../+nodes/nodes.store"; -import { podsStore } from "../+workloads-pods/pods.store"; -import { clusterStore } from "./cluster.store"; -import { eventStore } from "../+events/event.store"; -import { isAllowedResource } from "../../../common/rbac"; -import { getHostedCluster } from "../../../common/cluster-store"; - -@observer -export class Cluster extends React.Component { - private dependentStores = [nodesStore, podsStore]; - - private watchers = [ - interval(60, () => { getHostedCluster().available && clusterStore.getMetrics();}), - interval(20, () => { getHostedCluster().available && eventStore.loadAll();}) - ]; - - @computed get isLoaded() { - return nodesStore.isLoaded && podsStore.isLoaded; - } - - // todo: refactor - async componentDidMount() { - const { dependentStores } = this; - - if (!isAllowedResource("nodes")) { - dependentStores.splice(dependentStores.indexOf(nodesStore), 1); - } - this.watchers.forEach(watcher => watcher.start(true)); - - await Promise.all([ - ...dependentStores.map(store => store.loadAll()), - clusterStore.getAllMetrics() - ]); - - disposeOnUnmount(this, [ - ...dependentStores.map(store => store.subscribe()), - () => this.watchers.forEach(watcher => watcher.stop()), - reaction( - () => clusterStore.metricNodeRole, - () => this.watchers.forEach(watcher => watcher.restart()) - ) - ]); - } - - render() { - const { isLoaded } = this; - - return ( - -
- {!isLoaded && } - {isLoaded && ( - <> - - - - - )} -
-
- ); - } -} diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 1fb57fc0b9..30b1be8ab2 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -16,7 +16,7 @@ import { Workloads, workloadsRoute, workloadsURL } from "./+workloads"; import { Namespaces, namespacesRoute } from "./+namespaces"; import { Network, networkRoute } from "./+network"; import { Storage, storageRoute } from "./+storage"; -import { Cluster } from "./+cluster/cluster"; +import { ClusterOverview } from "./+cluster/cluster-overview"; import { Config, configRoute } from "./+config"; import { Events } from "./+events/events"; import { eventRoute } from "./+events"; @@ -180,7 +180,7 @@ export class App extends React.Component { - + From 0996c9a9eee31631947808e96494d6a25cb14e9c Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 11 Dec 2020 14:41:12 +0200 Subject: [PATCH 9/9] v4.0.2 Signed-off-by: Jari Kolehmainen --- package.json | 2 +- static/RELEASE_NOTES.md | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3b5a340052..ebc045dc4d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "4.0.1", + "version": "4.0.2", "main": "static/build/main.js", "copyright": "© 2020, Mirantis, Inc.", "license": "MIT", diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md index e914b6ad01..b030c0d8b5 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,7 +2,19 @@ Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights! -## 4.0.1 (current version) +## 4.0.2 (current version) + +We are aware some users are encountering issues and regressions from previous version. Many of these issues are something we have not seen as part of our automated or manual testing process. To make it worse, some of them are really difficult to reproduce. We want to ensure we are putting all our energy and effort trying to resolve these issues. We hope you are patient. Expect to see new patch releases still in the coming days! Fixes in this version: + +- Fix: use correct apiversion for HPA details +- Fix: use correct apiversion fro CronJob details +- Fix: wrong values in node metrics +- Fix: Deployment scale button "minus" +- Fix: remove symlink on extension install and manual runtime uninstall +- Fix: logs autoscroll behaviour +- Performance fixes + +## 4.0.1 - Extension install/uninstall fixes - Fix status brick styles in pod-menu-extension