From 2f53e7606089e6588e0a6988063fbbaa6991614a Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 23 Sep 2020 08:57:34 -0400 Subject: [PATCH 01/25] reduce height on draggable-top and only render it on macos (#942) * reduce height on draggable-top and only render it on macos * move drag bar out of app and into bootstrap Signed-off-by: Sebastian Malton --- src/renderer/bootstrap.tsx | 5 ++++- src/renderer/components/app.scss | 3 ++- .../cluster-manager/cluster-manager.tsx | 22 +++++++++---------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 80c27cb418..e42a456884 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -31,7 +31,10 @@ export async function bootstrap(App: AppComponent) { if (App.init) { await App.init(); } - render(, rootElem); + render(<> + {isMac &&
} + + , rootElem); } // run diff --git a/src/renderer/components/app.scss b/src/renderer/components/app.scss index 717c47bb1b..20ca08df66 100755 --- a/src/renderer/components/app.scss +++ b/src/renderer/components/app.scss @@ -13,6 +13,7 @@ :root { --mainBackground: #1e2124; --main-layout-header: 40px; + --drag-region-height: 22px } ::selection { @@ -47,7 +48,7 @@ html, body { left: 0; top: 0; width: 100%; - height: var(--main-layout-header); + height: var(--drag-region-height); z-index: 1000; pointer-events: none; } diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index db0026bf75..c658a31aa4 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -14,6 +14,7 @@ import { ClusterSettings, clusterSettingsRoute } from "../+cluster-settings"; import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route"; import { clusterStore } from "../../../common/cluster-store"; import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views"; +import { isMac } from "../../../common/vars"; @observer export class ClusterManager extends React.Component { @@ -53,21 +54,20 @@ export class ClusterManager extends React.Component { render() { return (
-
-
+
- - - - - - - + + + + + + +
- - + +
) } From b88c0d4fbf8eee5efd81146afd4293574c796f9f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 23 Sep 2020 08:57:51 -0400 Subject: [PATCH 02/25] Make download dir option consistent with other settings (#875) * make download dir option consitent with other settings * make path to kubectl setting consistent Co-authored-by: Lauri Nevala Signed-off-by: Sebastian Malton Co-authored-by: Sebastian Malton Co-authored-by: Lauri Nevala --- src/common/user-store.ts | 2 - src/main/kubectl.ts | 10 ++-- .../+preferences/kubectl-binaries.tsx | 52 ++++++++----------- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/common/user-store.ts b/src/common/user-store.ts index a0cfe09fd5..f7d3ade005 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store.ts @@ -59,8 +59,6 @@ export class UserStore extends BaseStore { colorTheme: UserStore.defaultTheme, downloadMirror: "default", downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version - downloadBinariesPath: this.getDefaultKubectlPath(), - kubectlBinariesPath: "" }; get isNewVersion() { diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index afb7a89111..1e57e29d5d 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -36,7 +36,7 @@ const packageMirrors: Map = new Map([ let bundledPath: string const initScriptVersionString = "# lens-initscript v3\n" -if (isDevelopment || isTestEnv) { +if (isDevelopment || isTestEnv) { const platformName = isWindows ? "windows" : process.platform bundledPath = path.join(process.cwd(), "binaries", "client", platformName, process.arch, "kubectl") } else { @@ -110,7 +110,11 @@ export class Kubectl { } protected getDownloadDir() { - return userStore.preferences?.downloadBinariesPath || Kubectl.kubectlDir + if (userStore.preferences?.downloadBinariesPath) { + return path.join(userStore.preferences.downloadBinariesPath, "kubectl") + } + + return Kubectl.kubectlDir } public async getPath(bundled = false): Promise { @@ -214,7 +218,7 @@ export class Kubectl { }); isValid = !await this.checkBinary(this.path, false) } - if(!isValid) { + if (!isValid) { logger.debug(`Releasing lock for ${this.kubectlVersion}`) release() return false diff --git a/src/renderer/components/+preferences/kubectl-binaries.tsx b/src/renderer/components/+preferences/kubectl-binaries.tsx index 15b01251f4..c57ab1b8a2 100644 --- a/src/renderer/components/+preferences/kubectl-binaries.tsx +++ b/src/renderer/components/+preferences/kubectl-binaries.tsx @@ -18,44 +18,22 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre { value: "china", label: "China (Azure)" }, ] - const save = () => { preferences.downloadBinariesPath = downloadPath; preferences.kubectlBinariesPath = binariesPath; } - const renderPath = () => { - if (preferences.downloadKubectlBinaries) { - return null; - } - return ( - <> - - - - Default:{" "}{Kubectl.bundledKubectlPath} - - - ); - } - return ( <>

Kubectl Binary

- - Download kubectl binaries matching to Kubernetes cluster verison. - Download kubectl binaries} value={preferences.downloadKubectlBinaries} onChange={downloadKubectlBinaries => preferences.downloadKubectlBinaries = downloadKubectlBinaries} /> + + Download kubectl binaries matching to Kubernetes cluster version. + - - Default: {userStore.getDefaultKubectlPath()} + + The directory to download binaries into. + + + + + The path to the kubectl binary on the system. - {renderPath()} ); -}); \ No newline at end of file +}); From fe4a63a9556620be1be0bb72ed437c46169c6fb7 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Thu, 24 Sep 2020 14:30:44 +0300 Subject: [PATCH 03/25] Allow to add the same cluster to multiple workspaces (#961) Signed-off-by: Lauri Nevala --- src/common/cluster-store.ts | 4 ++-- src/common/cluster-store_test.ts | 21 +++++++++++++++++-- .../components/+add-cluster/add-cluster.tsx | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index a7cd5cc3c5..d75de14609 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -122,8 +122,8 @@ export class ClusterStore extends BaseStore { return this.clusters.size > 0; } - hasContext(name: string) { - return this.clustersList.some(cluster => cluster.contextName === name); + hasContextInWorkspace(name: string, workspaceId: string) { + return this.clustersList.some(cluster => cluster.contextName === name && cluster.workspace === workspaceId); } getById(id: ClusterId): Cluster { diff --git a/src/common/cluster-store_test.ts b/src/common/cluster-store_test.ts index d7d6df4d2a..77eb688cfc 100644 --- a/src/common/cluster-store_test.ts +++ b/src/common/cluster-store_test.ts @@ -146,14 +146,22 @@ describe("config with existing clusters", () => { id: 'cluster1', kubeConfig: 'foo', contextName: 'foo', - preferences: { terminalCWD: '/foo' } + preferences: { terminalCWD: '/foo' }, + workspace: 'default' }, { id: 'cluster2', kubeConfig: 'foo2', contextName: 'foo2', preferences: { terminalCWD: '/foo2' } - } + }, + { + id: 'cluster3', + kubeConfig: 'foo', + contextName: 'foo', + preferences: { terminalCWD: '/foo' }, + workspace: 'foo' + }, ] }) } @@ -183,10 +191,19 @@ describe("config with existing clusters", () => { it("allows getting all of the clusters", async () => { const storedClusters = clusterStore.clustersList; + expect(storedClusters.length).toBe(3) expect(storedClusters[0].id).toBe('cluster1') expect(storedClusters[0].preferences.terminalCWD).toBe('/foo') expect(storedClusters[1].id).toBe('cluster2') expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2') + expect(storedClusters[2].id).toBe('cluster3') + }) + + it("allows to test if store already has context in workspace", async () => { + const existingContext = clusterStore.hasContextInWorkspace('foo', 'default'); + expect(existingContext).toBeTruthy + const nonExistingContext = clusterStore.hasContextInWorkspace('foo2', 'foo'); + expect(existingContext).toBeFalsy }) }) diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 624df4c1bf..4dd5371973 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -99,7 +99,7 @@ export class AddCluster extends React.Component { getContexts(config: KubeConfig): Map { const contexts = new Map(); splitConfig(config).forEach(config => { - const isExists = clusterStore.hasContext(config.currentContext); + const isExists = clusterStore.hasContextInWorkspace(config.currentContext, workspaceStore.currentWorkspaceId); if (!isExists) { contexts.set(config.currentContext, config); } From 386e7c63bbdc695d0012430688e4aca78a518c26 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Thu, 24 Sep 2020 14:54:09 +0300 Subject: [PATCH 04/25] Convert bytes in memory BarChart properly (#947) Signed-off-by: Lauri Nevala --- src/renderer/components/chart/bar-chart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/chart/bar-chart.tsx b/src/renderer/components/chart/bar-chart.tsx index f74d00e9a8..b2c7547bc5 100644 --- a/src/renderer/components/chart/bar-chart.tsx +++ b/src/renderer/components/chart/bar-chart.tsx @@ -165,7 +165,7 @@ export const memoryOptions: ChartOptions = { } return bytesToUnits(parseInt(value)); } - return `${value}`; + return bytesToUnits(value); }, stepSize: 1 } From 11ea9d2098341f84db6573f9c31a7611c47e84f2 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Thu, 24 Sep 2020 15:24:39 +0300 Subject: [PATCH 05/25] Fix iframe ipc flakyness after cluster is removed (#954) Signed-off-by: Jari Kolehmainen --- src/common/base-store.ts | 4 +++ src/common/cluster-ipc.ts | 4 +-- src/common/cluster-store.ts | 25 ++++++++++++------- src/main/cluster.ts | 14 ++++++----- src/main/lens-proxy.ts | 1 - src/renderer/bootstrap.tsx | 14 ++++++++++- .../components/cluster-manager/lens-views.ts | 9 ++++--- 7 files changed, 49 insertions(+), 22 deletions(-) diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 0b77a370b1..f476965736 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -93,6 +93,10 @@ export class BaseStore extends Singleton { } } + unregisterIpcListener() { + ipcRenderer.removeAllListeners(this.syncChannel) + } + disableSync() { this.syncDisposers.forEach(dispose => dispose()); this.syncDisposers.length = 0; diff --git a/src/common/cluster-ipc.ts b/src/common/cluster-ipc.ts index f48ce0f9c4..e11a232ea3 100644 --- a/src/common/cluster-ipc.ts +++ b/src/common/cluster-ipc.ts @@ -9,7 +9,7 @@ export const clusterIpc = { const cluster = clusterStore.getById(clusterId); if (cluster) { if (frameId) cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates - return cluster.activate(true); + return cluster.activate(); } }, }), @@ -58,4 +58,4 @@ export const clusterIpc = { return clusterStore.getById(clusterId)?.upgradeFeature(feature, config) } }), -} \ No newline at end of file +} diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index d75de14609..6cfc4fb7db 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -1,5 +1,5 @@ import path from "path"; -import { app, ipcRenderer, remote } from "electron"; +import { app, ipcRenderer, remote, webFrame, webContents } from "electron"; import { unlink } from "fs-extra"; import { action, computed, observable, toJS } from "mobx"; import { BaseStore } from "./base-store"; @@ -73,20 +73,27 @@ export class ClusterStore extends BaseStore { accessPropertiesByDotNotation: false, // To make dots safe in cluster context names migrations: migrations, }); - if (ipcRenderer) { - ipcRenderer.on("cluster:state", (event, model: ClusterState) => { - this.applyWithoutSync(() => { - logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host}`, model); - this.getById(model.id)?.updateModel(model); - }) - }) - } } @observable activeClusterId: ClusterId; @observable removedClusters = observable.map(); @observable clusters = observable.map(); + registerIpcListener() { + logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`) + ipcRenderer.on("cluster:state", (event, model: ClusterState) => { + this.applyWithoutSync(() => { + logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, model); + this.getById(model.id)?.updateModel(model); + }) + }) + } + + unregisterIpcListener() { + super.unregisterIpcListener() + ipcRenderer.removeAllListeners("cluster:state") + } + @computed get activeCluster(): Cluster | null { return this.getById(this.activeClusterId); } diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 9fc9172296..12acb793ab 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -56,10 +56,10 @@ export class Cluster implements ClusterModel { @observable kubeConfigPath: string; @observable apiUrl: string; // cluster server url @observable kubeProxyUrl: string; // lens-proxy to kube-api url - @observable online: boolean; - @observable accessible: boolean; - @observable ready: boolean; - @observable disconnected: boolean; + @observable online = false; + @observable accessible = false; + @observable ready = false; + @observable disconnected = true; @observable failureReason: string; @observable nodes = 0; @observable version: string; @@ -124,13 +124,14 @@ export class Cluster implements ClusterModel { this.eventDisposers.length = 0; } - async activate(init = false) { + @action + async activate() { logger.info(`[CLUSTER]: activate`, this.getMeta()); await this.whenInitialized; if (!this.eventDisposers.length) { this.bindEvents(); } - if (this.disconnected || (!init && !this.accessible)) { + if (this.disconnected || !this.accessible) { await this.reconnect(); } await this.refreshConnectionStatus() @@ -143,6 +144,7 @@ export class Cluster implements ClusterModel { return this.pushState(); } + @action async reconnect() { logger.info(`[CLUSTER]: reconnect`, this.getMeta()); this.contextHandler.stopServer(); diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index 765b3f4d1a..9801f776d0 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -134,7 +134,6 @@ export class LensProxy { protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) { const cluster = this.clusterManager.getClusterForRequest(req) if (cluster) { - await cluster.contextHandler.ensureServer(); const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler) if (proxyTarget) { // allow to fetch apis in "clusterId.localhost:port" from "localhost:port" diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index e42a456884..37fdc35e03 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -1,6 +1,6 @@ import "./components/app.scss" import React from "react"; -import { render } from "react-dom"; +import { render, unmountComponentAtNode } from "react-dom"; import { isMac } from "../common/vars"; import { userStore } from "../common/user-store"; import { workspaceStore } from "../common/workspace-store"; @@ -27,10 +27,22 @@ export async function bootstrap(App: AppComponent) { themeStore.init(), ]); + // Register additional store listeners + clusterStore.registerIpcListener(); + // init app's dependencies if any if (App.init) { await App.init(); } + window.addEventListener("message", (ev: MessageEvent) => { + if (ev.data === "teardown") { + userStore.unregisterIpcListener() + workspaceStore.unregisterIpcListener() + clusterStore.unregisterIpcListener() + unmountComponentAtNode(rootElem) + window.location.href = "about:blank" + } + }) render(<> {isMac &&
} diff --git a/src/renderer/components/cluster-manager/lens-views.ts b/src/renderer/components/cluster-manager/lens-views.ts index 370ed665c3..d23bdd6072 100644 --- a/src/renderer/components/cluster-manager/lens-views.ts +++ b/src/renderer/components/cluster-manager/lens-views.ts @@ -19,8 +19,11 @@ export async function initView(clusterId: ClusterId) { if (!clusterId || lensViews.has(clusterId)) { return; } - logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`) const cluster = clusterStore.getById(clusterId); + if (!cluster) { + return; + } + logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`) const parentElem = document.getElementById("lens-views"); const iframe = document.createElement("iframe"); iframe.name = cluster.contextName; @@ -42,9 +45,9 @@ export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrame // 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.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}`; - iframe.removeAttribute("src") + iframe.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}` iframe.removeAttribute("name") + iframe.contentWindow.postMessage("teardown", "*") } export function refreshViews() { From 5b6b19036dfd404fbdd2eec11fdcd2ce40a1d16d Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 24 Sep 2020 09:21:56 -0400 Subject: [PATCH 06/25] dropdowns should have 'cursor: pointer;' (#956) Signed-off-by: Sebastian Malton --- src/renderer/components/select/select.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/components/select/select.scss b/src/renderer/components/select/select.scss index 4f71ed87ac..a9468a0c8a 100644 --- a/src/renderer/components/select/select.scss +++ b/src/renderer/components/select/select.scss @@ -32,6 +32,7 @@ html { background: transparent; min-height: 0; box-shadow: 0 0 0 1px $halfGray; + cursor: pointer; &--is-focused { box-shadow: 0 0 0 2px $primary; @@ -88,6 +89,7 @@ html { &__option { white-space: nowrap; + cursor: pointer; &:active { background: $primary; From d20f890ddbc1300c356af27ce7e7a5c9abe922fe Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Thu, 24 Sep 2020 16:53:49 +0300 Subject: [PATCH 07/25] Fix spdy proxy (#962) Signed-off-by: Jari Kolehmainen --- src/main/lens-proxy.ts | 44 +++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index 9801f776d0..dbd4f90c9e 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -44,9 +44,7 @@ export class LensProxy { const spdyProxy = spdy.createServer({ spdy: { plain: true, - connection: { - autoSpdy31: true - } + protocols: ["http/1.1", "spdy/3.1"] } }, (req: http.IncomingMessage, res: http.ServerResponse) => { this.handleRequest(proxy, req, res) @@ -73,12 +71,40 @@ export class LensProxy { if (cluster) { const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "") const apiUrl = url.parse(cluster.apiUrl) - const res = new http.ServerResponse(req) - res.assignSocket(socket) - res.setHeader("Location", proxyUrl) - res.setHeader("Host", apiUrl.hostname) - res.statusCode = 302 - res.end() + const pUrl = url.parse(proxyUrl) + const connectOpts = { port: parseInt(pUrl.port), host: pUrl.hostname } + const proxySocket = new net.Socket() + proxySocket.connect(connectOpts, () => { + proxySocket.write(`${req.method} ${pUrl.path} HTTP/1.1\r\n`) + proxySocket.write(`Host: ${apiUrl.host}\r\n`) + for (let i = 0; i < req.rawHeaders.length; i += 2) { + const key = req.rawHeaders[i] + if (key !== "Host" && key !== "Authorization") { + proxySocket.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}\r\n`) + } + } + proxySocket.write("\r\n") + proxySocket.write(head) + }) + proxySocket.on('data', function (chunk) { + socket.write(chunk) + }) + proxySocket.on('end', function () { + socket.end() + }) + proxySocket.on('error', function (err) { + socket.write("HTTP/" + req.httpVersion + " 500 Connection error\r\n\r\n"); + socket.end() + }) + socket.on('data', function (chunk) { + proxySocket.write(chunk) + }) + socket.on('end', function () { + proxySocket.end() + }) + socket.on('error', function () { + proxySocket.end() + }) } } From 5401c99298b4be82b108476f3ed4ff6f37b761fa Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 24 Sep 2020 09:54:26 -0400 Subject: [PATCH 08/25] Helm components should always use version information (#949) * clean up code to catch rejections Signed-off-by: Sebastian Malton --- .../+apps-helm-charts/helm-chart-details.scss | 2 +- .../+apps-helm-charts/helm-chart-details.tsx | 93 ++++++++++++------- .../+apps-helm-charts/helm-charts.tsx | 14 +-- .../components/dock/install-chart.store.ts | 50 +++++----- .../components/dock/install-chart.tsx | 10 +- .../notifications/notifications.store.ts | 8 +- .../notifications/notifications.tsx | 10 +- 7 files changed, 112 insertions(+), 75 deletions(-) diff --git a/src/renderer/components/+apps-helm-charts/helm-chart-details.scss b/src/renderer/components/+apps-helm-charts/helm-chart-details.scss index 212e9d723a..c436dfa50f 100644 --- a/src/renderer/components/+apps-helm-charts/helm-chart-details.scss +++ b/src/renderer/components/+apps-helm-charts/helm-chart-details.scss @@ -49,4 +49,4 @@ .chart-description { margin-top: $margin * 2; } -} \ No newline at end of file +} diff --git a/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx b/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx index 32c6cd6a3e..4ae8995f11 100644 --- a/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx +++ b/src/renderer/components/+apps-helm-charts/helm-chart-details.tsx @@ -3,8 +3,8 @@ import "./helm-chart-details.scss"; import React, { Component } from "react"; import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api"; import { t, Trans } from "@lingui/macro"; -import { autorun, observable } from "mobx"; -import { disposeOnUnmount, observer } from "mobx-react"; +import { observable, toJS } from "mobx"; +import { observer } from "mobx-react"; import { Drawer, DrawerItem } from "../drawer"; import { autobind, stopPropagation } from "../../utils"; import { MarkdownViewer } from "../markdown-viewer"; @@ -25,39 +25,41 @@ interface Props { export class HelmChartDetails extends Component { @observable chartVersions: HelmChart[]; @observable selectedChart: HelmChart; - @observable description: string = null; + @observable readme: string = null; + @observable error: string = null; private chartPromise: CancelablePromise<{ readme: string; versions: HelmChart[] }>; - @disposeOnUnmount - chartSelector = autorun(async () => { - if (!this.props.chart) return; - this.chartVersions = null; - this.selectedChart = null; - this.description = null; - this.loadChartData(); - this.chartPromise.then(data => { - this.description = data.readme; - this.chartVersions = data.versions; - this.selectedChart = data.versions[0]; - }); - }); + async componentDidMount() { + const { chart: { name, repo, version } } = this.props - loadChartData(version?: string) { - const { chart: { name, repo } } = this.props; - if (this.chartPromise) this.chartPromise.cancel(); - this.chartPromise = helmChartsApi.get(repo, name, version); + try { + const { readme, versions } = await (this.chartPromise = helmChartsApi.get(repo, name, version)) + this.readme = readme + this.chartVersions = versions + this.selectedChart = versions[0] + } catch (error) { + this.error = error + } + } + + componentWillUnmount() { + this.chartPromise?.cancel(); } @autobind() - onVersionChange(opt: SelectOption) { - const version = opt.value; + async onVersionChange({ value: version }: SelectOption) { this.selectedChart = this.chartVersions.find(chart => chart.version === version); - this.description = null; - this.loadChartData(version); - this.chartPromise.then(data => { - this.description = data.readme - }); + this.readme = null; + + try { + this.chartPromise?.cancel(); + const { chart: { name, repo } } = this.props; + const { readme } = await (this.chartPromise = helmChartsApi.get(repo, name, version)) + this.readme = readme; + } catch (error) { + this.error = error; + } } @autobind() @@ -79,7 +81,7 @@ export class HelmChartDetails extends Component {
{selectedChart.getDescription()} -
{
); } -} \ No newline at end of file +} diff --git a/src/renderer/components/notifications/notifications.store.ts b/src/renderer/components/notifications/notifications.store.ts index 786a65d603..1873ae91a4 100644 --- a/src/renderer/components/notifications/notifications.store.ts +++ b/src/renderer/components/notifications/notifications.store.ts @@ -8,10 +8,16 @@ import { JsonApiErrorParsed } from "../../api/json-api"; export type IMessageId = string | number; export type IMessage = React.ReactNode | React.ReactNode[] | JsonApiErrorParsed; +export enum NotificationStatus { + OK = "ok", + ERROR = "error", + INFO = "info", +} + export interface INotification { id?: IMessageId; message: IMessage; - status?: "ok" | "error" | "info"; + status?: NotificationStatus; timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide } diff --git a/src/renderer/components/notifications/notifications.tsx b/src/renderer/components/notifications/notifications.tsx index 6f59a068d9..35c741505c 100644 --- a/src/renderer/components/notifications/notifications.tsx +++ b/src/renderer/components/notifications/notifications.tsx @@ -5,7 +5,7 @@ import { reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react" import { JsonApiErrorParsed } from "../../api/json-api"; import { cssNames, prevDefault } from "../../utils"; -import { IMessage, INotification, notificationsStore } from "./notifications.store"; +import { IMessage, INotification, notificationsStore, NotificationStatus } from "./notifications.store"; import { Animate } from "../animate"; import { Icon } from "../icon" @@ -17,7 +17,7 @@ export class Notifications extends React.Component { notificationsStore.add({ message: message, timeout: 2500, - status: "ok" + status: NotificationStatus.OK }) } @@ -25,13 +25,13 @@ export class Notifications extends React.Component { notificationsStore.add({ message: message, timeout: 10000, - status: "error" + status: NotificationStatus.ERROR }); } static info(message: IMessage, customOpts: Partial = {}) { return notificationsStore.add({ - status: "info", + status: NotificationStatus.INFO, timeout: 0, message: message, ...customOpts, @@ -78,7 +78,7 @@ export class Notifications extends React.Component { onMouseLeave={() => addAutoHideTimer(notification)} onMouseEnter={() => removeAutoHideTimer(notification)}>
- +
{msgText}
From 51b21347daefe086c5cbc16760e5fbe905c18993 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Thu, 24 Sep 2020 18:14:57 +0300 Subject: [PATCH 09/25] cleanup proxy upgrade handler (#963) Signed-off-by: Jari Kolehmainen --- src/main/lens-proxy.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index dbd4f90c9e..dfb7de7867 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -53,11 +53,7 @@ export class LensProxy { if (req.url.startsWith(`${apiPrefix}?`)) { this.handleWsUpgrade(req, socket, head) } else { - if (req.headers.upgrade?.startsWith("SPDY")) { - this.handleSpdyProxy(proxy, req, socket, head) - } else { - socket.end() - } + this.handleProxyUpgrade(proxy, req, socket, head) } }) spdyProxy.on("error", (err) => { @@ -66,7 +62,7 @@ export class LensProxy { return spdyProxy } - protected async handleSpdyProxy(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) { + protected async handleProxyUpgrade(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) { const cluster = this.clusterManager.getClusterForRequest(req) if (cluster) { const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "") From d0f1d7b74be13258e98cf0891d0dbb109750ef85 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 25 Sep 2020 12:58:38 +0300 Subject: [PATCH 10/25] Do not filter contexts when adding new clusters (#969) Signed-off-by: Lauri Nevala --- src/common/cluster-store.ts | 4 ---- src/common/cluster-store_test.ts | 7 ------- src/renderer/components/+add-cluster/add-cluster.tsx | 5 +---- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 6cfc4fb7db..a382006cdc 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -129,10 +129,6 @@ export class ClusterStore extends BaseStore { return this.clusters.size > 0; } - hasContextInWorkspace(name: string, workspaceId: string) { - return this.clustersList.some(cluster => cluster.contextName === name && cluster.workspace === workspaceId); - } - getById(id: ClusterId): Cluster { return this.clusters.get(id); } diff --git a/src/common/cluster-store_test.ts b/src/common/cluster-store_test.ts index 77eb688cfc..3c0369def3 100644 --- a/src/common/cluster-store_test.ts +++ b/src/common/cluster-store_test.ts @@ -198,13 +198,6 @@ describe("config with existing clusters", () => { expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2') expect(storedClusters[2].id).toBe('cluster3') }) - - it("allows to test if store already has context in workspace", async () => { - const existingContext = clusterStore.hasContextInWorkspace('foo', 'default'); - expect(existingContext).toBeTruthy - const nonExistingContext = clusterStore.hasContextInWorkspace('foo2', 'foo'); - expect(existingContext).toBeFalsy - }) }) describe("pre 2.0 config with an existing cluster", () => { diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 4dd5371973..a0414541d8 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -99,10 +99,7 @@ export class AddCluster extends React.Component { getContexts(config: KubeConfig): Map { const contexts = new Map(); splitConfig(config).forEach(config => { - const isExists = clusterStore.hasContextInWorkspace(config.currentContext, workspaceStore.currentWorkspaceId); - if (!isExists) { - contexts.set(config.currentContext, config); - } + contexts.set(config.currentContext, config); }) return contexts } From 265aa4196891c3446b36e379425fa9d65ecb3149 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 25 Sep 2020 12:59:12 +0300 Subject: [PATCH 11/25] Fix reading CRD conditions (#967) Signed-off-by: Lauri Nevala --- src/renderer/api/endpoints/cert-manager.api.ts | 1 + src/renderer/api/endpoints/crd.api.ts | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/renderer/api/endpoints/cert-manager.api.ts b/src/renderer/api/endpoints/cert-manager.api.ts index bfeb2692cb..3e35000ee1 100644 --- a/src/renderer/api/endpoints/cert-manager.api.ts +++ b/src/renderer/api/endpoints/cert-manager.api.ts @@ -223,6 +223,7 @@ export class Issuer extends KubeObject { } getConditions() { + if (!this.status?.conditions) return []; const { conditions = [] } = this.status; return conditions.map(condition => { const { message, reason, lastTransitionTime, status } = condition; diff --git a/src/renderer/api/endpoints/crd.api.ts b/src/renderer/api/endpoints/crd.api.ts index ebacf832ca..2a029e6f9d 100644 --- a/src/renderer/api/endpoints/crd.api.ts +++ b/src/renderer/api/endpoints/crd.api.ts @@ -9,12 +9,12 @@ type AdditionalPrinterColumnsCommon = { description: string; } -type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & { - jsonPath: string; +type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & { + jsonPath: string; } -type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & { - JSONPath: string; +type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & { + JSONPath: string; } export class CustomResourceDefinition extends KubeObject { @@ -132,7 +132,7 @@ export class CustomResourceDefinition extends KubeObject { } getConditions() { - if (!this.status.conditions) return []; + if (!this.status?.conditions) return []; return this.status.conditions.map(condition => { const { message, reason, lastTransitionTime, status } = condition; return { From 1168abfa32f48ee21bb97641f30ff0b340962198 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 25 Sep 2020 13:08:06 +0300 Subject: [PATCH 12/25] Helm 3.3.4 (#964) Signed-off-by: Jari Kolehmainen --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 70230c3268..f2c50e2720 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "config": { "bundledKubectlVersion": "1.17.11", - "bundledHelmVersion": "3.3.1" + "bundledHelmVersion": "3.3.4" }, "engines": { "node": ">=12.0 <13.0" From 950fd6528fa8f52aec2ef1fadd2fd1b311a3d799 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 25 Sep 2020 15:04:48 +0300 Subject: [PATCH 13/25] Fix kubeconfig fetching for service account (#966) Signed-off-by: Lauri Nevala --- src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx index d2f2f1a06f..d75d97bbcc 100644 --- a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx +++ b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx @@ -116,6 +116,6 @@ export function openServiceAccountKubeConfig(account: ServiceAccount) { const namespace = account.getNs() KubeConfigDialog.open({ title: {accountName} kubeconfig, - loader: () => apiBase.get(`/kubeconfig/service-account/${namespace}/${account}`) + loader: () => apiBase.get(`/kubeconfig/service-account/${namespace}/${accountName}`) }) } \ No newline at end of file From 13b99afa21f7cf7a6f3fc83cb22b3b3ff1935963 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 25 Sep 2020 15:13:20 +0300 Subject: [PATCH 14/25] Add migration to fix kubeconfig paths that point to snap config dir (#972) * Add migration to fix kubeconfig paths that point to snap config dir Signed-off-by: Lauri Nevala --- src/migrations/cluster-store/index.ts | 2 ++ src/migrations/cluster-store/snap.ts | 33 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/migrations/cluster-store/snap.ts diff --git a/src/migrations/cluster-store/index.ts b/src/migrations/cluster-store/index.ts index d178d7106b..f35e8f6c9c 100644 --- a/src/migrations/cluster-store/index.ts +++ b/src/migrations/cluster-store/index.ts @@ -7,6 +7,7 @@ import version260Beta3 from "./2.6.0-beta.3" import version270Beta0 from "./2.7.0-beta.0" import version270Beta1 from "./2.7.0-beta.1" import version360Beta1 from "./3.6.0-beta.1" +import snap from "./snap" export default { ...version200Beta2, @@ -16,4 +17,5 @@ export default { ...version270Beta0, ...version270Beta1, ...version360Beta1, + ...snap } \ No newline at end of file diff --git a/src/migrations/cluster-store/snap.ts b/src/migrations/cluster-store/snap.ts new file mode 100644 index 0000000000..a377ba4268 --- /dev/null +++ b/src/migrations/cluster-store/snap.ts @@ -0,0 +1,33 @@ +// Fix embedded kubeconfig paths under snap config + +import { migration } from "../migration-wrapper"; +import { ClusterModel, ClusterStore } from "../../common/cluster-store"; +import { getAppVersion } from "../../common/utils/app-version"; +import fs from "fs" + +export default migration({ + version: getAppVersion(), // Run always after upgrade + run(store, printLog) { + if (!process.env["SNAP"]) return; + + printLog("Migrating embedded kubeconfig paths") + const storedClusters: ClusterModel[] = store.get("clusters") || []; + if (!storedClusters.length) return; + + printLog("Number of clusters to migrate: ", storedClusters.length) + const migratedClusters = storedClusters + .map(cluster => { + /** + * replace snap version with 'current' in kubeconfig path + */ + if (!fs.existsSync(cluster.kubeConfigPath)) { + const kubeconfigPath = cluster.kubeConfigPath.replace(/\/snap\/kontena-lens\/[0-9]*\//, "/snap/kontena-lens/current/") + cluster.kubeConfigPath = kubeconfigPath + } + return cluster; + }) + + + store.set("clusters", migratedClusters) + } +}) From 9702c645b44d6fad17ac4dcd2941b7ecca128779 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Mon, 28 Sep 2020 09:19:01 +0300 Subject: [PATCH 15/25] Release v3.6.5-rc.1 (#974) Signed-off-by: Lauri Nevala --- package.json | 2 +- static/RELEASE_NOTES.md | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f2c50e2720..53488e932d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "3.6.4", + "version": "3.6.5-rc.1", "main": "static/build/main.js", "copyright": "© 2020, Mirantis, Inc.", "license": "MIT", diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md index a11f954f31..e3f2af3cfb 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,7 +2,26 @@ 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! -## 3.6.4 (current version) +## 3.6.5-rc.1 (current version) +- Fix Notifications not to block items not visually under them from being interacted with +- Fix side bar not to scroll after clicking on lower menu item +- Auto-select context if only one context is present in pasted Kubeconfig +- Fix background image of What's New page on white theme +- Reduce height on draggable-top and only render it on macos +- Download dir option is now consistent with other settings +- Allow to add the same cluster multiple times +- Convert bytes in memory chart properly +- Fix empty dashboard screen after cluster is removed and added multiple times in a row to application +- Dropdowns have pointer cursor now +- Proxy kubectl exec requests properly +- Pass always chart version information when dealing with helm commands +- Fix app crash when conditions are not yet present in CRD objects +- Fix kubeconfig generating for service account +- Update bundled Helm binary to version 3.3.4 +- Fix clusters' kubeconfig paths that point to snap config dir to use current snap config path + + +## 3.6.4 - Fix: deleted namespace does not get auto unselected - Get focus to dock tab (terminal & resource editor) content after resize - Downloading kubectl binary does not block dashboard opening anymore From 163b34463b7c6e33431cb830664b07147cc59584 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 28 Sep 2020 14:59:35 +0300 Subject: [PATCH 16/25] Removing pre-defined input type (#984) Signed-off-by: Alex Andreev --- src/renderer/components/input/input.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/components/input/input.tsx b/src/renderer/components/input/input.tsx index d8c8ec444b..c109735927 100644 --- a/src/renderer/components/input/input.tsx +++ b/src/renderer/components/input/input.tsx @@ -282,7 +282,6 @@ export class Input extends React.Component { onKeyDown: this.onKeyDown, rows: multiLine ? (rows || 1) : null, ref: this.bindRef, - type: "text", spellCheck: "false", }); From 6b231c97aaa3511f02ddf010dba3712e571e91d0 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Mon, 28 Sep 2020 08:40:55 -0400 Subject: [PATCH 17/25] adding more integration tests (#890) * adding more integration tests reorganized tests, added ability to skip cluster tests if minikube is not ready introduced INTEGRATION_TESTS namespace for sandboxing the cluster tests added cluster page tests Signed-off-by: Jim Ehrismann --- integration/specs/app_spec.ts | 510 +++++++++++++++++++++++++++++----- 1 file changed, 442 insertions(+), 68 deletions(-) diff --git a/integration/specs/app_spec.ts b/integration/specs/app_spec.ts index 02024d1647..5cde34afb5 100644 --- a/integration/specs/app_spec.ts +++ b/integration/specs/app_spec.ts @@ -1,99 +1,473 @@ +/* + Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE + namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the + TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube + cluster and vice versa. +*/ import { Application } from "spectron" import * as util from "../helpers/utils" import { spawnSync } from "child_process" -jest.setTimeout(30000) +const describeif = (condition : boolean) => condition ? describe : describe.skip +const itif = (condition : boolean) => condition ? it : it.skip -const BACKSPACE = "\uE003" +jest.setTimeout(60000) -describe("app start", () => { +describe("Lens integration tests", () => { + const TEST_NAMESPACE = "integration-tests" + + const BACKSPACE = "\uE003" let app: Application + + const appStart = async () => { + app = util.setup() + await app.start() + // Wait for splash screen to be closed + while (await app.client.getWindowCount() > 1); + await app.client.windowByIndex(0) + await app.client.waitUntilWindowLoaded() + } + const clickWhatsNew = async (app: Application) => { await app.client.waitUntilTextExists("h1", "What's new") await app.client.click("button.primary") await app.client.waitUntilTextExists("h1", "Welcome") } + describe("app start", () => { + beforeAll(appStart, 20000) + + afterAll(async () => { + if (app && app.isRunning()) { + return util.tearDown(app) + } + }) + + it('shows "whats new"', async () => { + await clickWhatsNew(app) + }) + + // Todo figure out how to access main menu to get these to work + it.skip('shows "add cluster"', async () => { + await app.client.keys(['Shift', 'Meta', 'A']) + await app.client.waitUntilTextExists("h2", "Add Cluster") + await app.client.keys(['Shift', 'Meta']) + }) + + it.skip('shows "preferences"', async () => { + await app.client.keys(['Meta', ',']) + await app.client.waitUntilTextExists("h2", "Preferences") + await app.client.keys('Meta') + }) + + it.skip('quits Lens"', async () => { + await app.client.keys(['Meta', 'Q']) + await app.client.keys('Meta') + }) + }) + + const minikubeReady = (): boolean => { + // determine if minikube is running + let status = spawnSync("minikube status", { shell: true }) + if (status.status !== 0) { + console.warn("minikube not running") + return false + } + + // Remove TEST_NAMESPACE if it already exists + status = spawnSync(`minikube kubectl -- get namespace ${TEST_NAMESPACE}`, { shell: true }) + if (status.status === 0) { + console.warn(`Removing existing ${TEST_NAMESPACE} namespace`) + status = spawnSync(`minikube kubectl -- delete namespace ${TEST_NAMESPACE}`, { shell: true }) + if (status.status !== 0) { + console.warn(`Error removing ${TEST_NAMESPACE} namespace: ${status.stderr.toString()}`) + return false + } + console.log(status.stdout.toString()) + } + return true + } + const ready = minikubeReady() + const addMinikubeCluster = async (app: Application) => { await app.client.click("div.add-cluster") await app.client.waitUntilTextExists("div", "Select kubeconfig file") - await app.client.click("button.primary") + await app.client.click("div.Select__control") // show the context drop-down list + await app.client.waitUntilTextExists("div", "minikube") + if (!await app.client.$("button.primary").isEnabled()) { + await app.client.click("div.minikube") // select minikube context + } // else the only context, which must be 'minikube', is automatically selected + await app.client.click("div.Select__control") // hide the context drop-down list (it might be obscuring the Add cluster(s) button) + await app.client.click("button.primary") // add minikube cluster } const waitForMinikubeDashboard = async (app: Application) => { await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started") - await app.client.getWindowCount() await app.client.waitForExist(`iframe[name="minikube"]`) await app.client.frame("minikube") await app.client.waitUntilTextExists("span.link-text", "Cluster") } - beforeEach(async () => { - app = util.setup() - await app.start() - await app.client.waitUntilWindowLoaded() - // Wait for splash screen to be closed - while (await app.client.getWindowCount() > 1); - await app.client.windowByIndex(0) - await app.client.waitUntilWindowLoaded() - }, 20000) + describeif(ready)("cluster tests", () => { + let clusterAdded = false - it('shows "whats new"', async () => { - await clickWhatsNew(app) - }) - - it('allows to add a cluster', async () => { - const status = spawnSync("minikube status", { shell: true }) - if (status.status !== 0) { - console.warn("minikube not running, skipping test") - return + const addCluster = async () => { + await clickWhatsNew(app) + await addMinikubeCluster(app) + await waitForMinikubeDashboard(app) + await app.client.click('a[href="/nodes"]') + await app.client.waitUntilTextExists("div.TableCell", "Ready") } - await clickWhatsNew(app) - await addMinikubeCluster(app) - await waitForMinikubeDashboard(app) - await app.client.click('a[href="/nodes"]') - await app.client.waitUntilTextExists("div.TableCell", "Ready") - }) - it('allows to create a pod', async () => { - const status = spawnSync("minikube status", { shell: true }) - if (status.status !== 0) { - console.warn("minikube not running, skipping test") - return - } - await clickWhatsNew(app) - await addMinikubeCluster(app) - await waitForMinikubeDashboard(app) - await app.client.click(".sidebar-nav #workloads span.link-text") - await app.client.waitUntilTextExists('a[href="/pods"]', "Pods") - await app.client.click('a[href="/pods"]') - await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver") - await app.client.click('.Icon.new-dock-tab') - await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource") - await app.client.click("li.MenuItem.create-resource-tab") - await app.client.waitForVisible(".CreateResource div.ace_content") - // Write pod manifest to editor - await app.client.keys("apiVersion: v1\n") - await app.client.keys("kind: Pod\n") - await app.client.keys("metadata:\n") - await app.client.keys(" name: nginx\n") - await app.client.keys(BACKSPACE + "spec:\n") - await app.client.keys(" containers:\n") - await app.client.keys("- name: nginx\n") - await app.client.keys(" image: nginx:alpine\n") - // Create deployent - await app.client.waitForEnabled("button.Button=Create & Close") - await app.client.click("button.Button=Create & Close") - // Wait until first bits of pod appears on dashboard - await app.client.waitForExist(".name=nginx") - // Open pod details - await app.client.click(".name=nginx") - await app.client.waitUntilTextExists("div.drawer-title-text", "Pod: nginx") - }) + describe("cluster add", () => { + beforeAll(appStart, 20000) - afterEach(async () => { - if (app && app.isRunning()) { - return util.tearDown(app) + afterAll(async () => { + if (app && app.isRunning()) { + return util.tearDown(app) + } + }) + + it('allows to add a cluster', async () => { + await addCluster() + clusterAdded = true + }) + }) + + const appStartAddCluster = async () => { + if (clusterAdded) { + await appStart() + await addCluster() + } } - }) + + describe("cluster pages", () => { + + beforeAll(appStartAddCluster, 40000) + + afterAll(async () => { + if (app && app.isRunning()) { + return util.tearDown(app) + } + }) + + const tests : { + drawer?: string + drawerId?: string + pages: { + name: string, + href: string, + expectedSelector: string, + expectedText: string + }[] + }[] = [ + { + drawer: "", + drawerId: "", + pages: [ { + name: "Cluster", + href: "cluster", + expectedSelector: "div.ClusterNoMetrics p", + expectedText: "Metrics are not available due" + }] + }, + { + drawer: "", + drawerId: "", + pages: [ { + name: "Nodes", + href: "nodes", + expectedSelector: "h5.title", + expectedText: "Nodes" + }] + }, + { + drawer: "Workloads", + 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: "Jobs", + href: "jobs", + expectedSelector: "h5.title", + expectedText: "Jobs" + }, + { + name: "CronJobs", + href: "cronjobs", + expectedSelector: "h5.title", + expectedText: "Cron Jobs" + } ] + }, + { + drawer: "Configuration", + 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: "HPA", + href: "hpa", + expectedSelector: "h5.title", + expectedText: "Horizontal Pod Autoscalers" + }, + { + name: "Pod Disruption Budgets", + href: "poddisruptionbudgets", + expectedSelector: "h5.title", + expectedText: "Pod Disruption Budgets" + } ] + }, + { + drawer: "Network", + 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" + } ] + }, + { + drawer: "Storage", + 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" + } ] + }, + { + drawer: "", + drawerId: "", + pages: [ { + name: "Namespaces", + href: "namespaces", + expectedSelector: "h5.title", + expectedText: "Namespaces" + }] + }, + { + drawer: "", + drawerId: "", + pages: [ { + name: "Events", + href: "events", + expectedSelector: "h5.title", + expectedText: "Events" + }] + }, + { + drawer: "Apps", + drawerId: "apps", + pages: [ { + name: "Charts", + href: "apps/charts", + expectedSelector: "div.HelmCharts input", + expectedText: "" + }, + { + name: "Releases", + href: "apps/releases", + expectedSelector: "h5.title", + expectedText: "Releases" + } ] + }, + { + drawer: "Access Control", + drawerId: "users", + pages: [ { + name: "Service Accounts", + href: "service-accounts", + expectedSelector: "h5.title", + expectedText: "Service Accounts" + }, + { + name: "Role Bindings", + href: "role-bindings", + expectedSelector: "h5.title", + expectedText: "Role Bindings" + }, + { + name: "Roles", + href: "roles", + expectedSelector: "h5.title", + expectedText: "Roles" + }, + { + name: "Pod Security Policies", + href: "pod-security-policies", + expectedSelector: "h5.title", + expectedText: "Pod Security Policies" + } ] + }, + { + drawer: "Custom Resources", + drawerId: "custom-resources", + pages: [ { + name: "Definitions", + href: "crd/definitions", + expectedSelector: "h5.title", + expectedText: "Custom Resources" + } ] + }, + ]; + tests.forEach(({ drawer = "", drawerId = "", pages }) => { + if (drawer !== "") { + it(`shows ${drawer} drawer`, async () => { + expect(clusterAdded).toBe(true) + await app.client.click(`.sidebar-nav #${drawerId} span.link-text`) + await app.client.waitUntilTextExists(`a[href="/${pages[0].href}"]`, pages[0].name) + }) + } + pages.forEach(({name, href, expectedSelector, expectedText}) => { + it(`shows ${drawer}->${name} page`, async () => { + expect(clusterAdded).toBe(true) + await app.client.click(`a[href="/${href}"]`) + await app.client.waitUntilTextExists(expectedSelector, expectedText) + }) + }) + if (drawer !== "") { + // hide the drawer + it(`hides ${drawer} drawer`, async () => { + expect(clusterAdded).toBe(true) + await app.client.click(`.sidebar-nav #${drawerId} span.link-text`) + await expect(app.client.waitUntilTextExists(`a[href="/${pages[0].href}"]`, pages[0].name, 100)).rejects.toThrow() + }) + } + }) + }) + + describe("cluster operations", () => { + beforeEach(appStartAddCluster, 40000) + + afterEach(async () => { + if (app && app.isRunning()) { + return util.tearDown(app) + } + }) + + it('shows default namespace', async () => { + expect(clusterAdded).toBe(true) + await app.client.click('a[href="/namespaces"]') + await app.client.waitUntilTextExists("div.TableCell", "default") + await app.client.waitUntilTextExists("div.TableCell", "kube-system") + }) + + it(`creates ${TEST_NAMESPACE} namespace`, async () => { + expect(clusterAdded).toBe(true) + await app.client.click('a[href="/namespaces"]') + await app.client.waitUntilTextExists("div.TableCell", "default") + await app.client.waitUntilTextExists("div.TableCell", "kube-system") + await app.client.click("button.add-button") + await app.client.waitUntilTextExists("div.AddNamespaceDialog", "Create Namespace") + await app.client.keys(`${TEST_NAMESPACE}\n`) + await app.client.waitForExist(`.name=${TEST_NAMESPACE}`) + }) + + it(`creates a pod in ${TEST_NAMESPACE} namespace`, async () => { + expect(clusterAdded).toBe(true) + await app.client.click(".sidebar-nav #workloads span.link-text") + await app.client.waitUntilTextExists('a[href="/pods"]', "Pods") + await app.client.click('a[href="/pods"]') + await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver") + await app.client.click('.Icon.new-dock-tab') + await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource") + await app.client.click("li.MenuItem.create-resource-tab") + await app.client.waitForVisible(".CreateResource div.ace_content") + // Write pod manifest to editor + await app.client.keys("apiVersion: v1\n") + await app.client.keys("kind: Pod\n") + await app.client.keys("metadata:\n") + await app.client.keys(" name: nginx-create-pod-test\n") + await app.client.keys(`namespace: ${TEST_NAMESPACE}\n`) + await app.client.keys(BACKSPACE + "spec:\n") + await app.client.keys(" containers:\n") + await app.client.keys("- name: nginx-create-pod-test\n") + await app.client.keys(" image: nginx:alpine\n") + // Create deployment + await app.client.waitForEnabled("button.Button=Create & Close") + await app.client.click("button.Button=Create & Close") + // Wait until first bits of pod appears on dashboard + await app.client.waitForExist(".name=nginx-create-pod-test") + // Open pod details + await app.client.click(".name=nginx-create-pod-test") + await app.client.waitUntilTextExists("div.drawer-title-text", "Pod: nginx-create-pod-test") + }) + }) + }) }) From bc228c9e365867a16590bc19091ded91ca3d2b81 Mon Sep 17 00:00:00 2001 From: Yangjun Wang Date: Mon, 28 Sep 2020 17:24:54 +0300 Subject: [PATCH 18/25] display last-applied-configuration annotation in detail-view, but filter it in search fields still (#943) Signed-off-by: Yangjun Wang Co-authored-by: Yangjun Wang --- src/renderer/api/kube-object.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/api/kube-object.ts b/src/renderer/api/kube-object.ts index abb92dfe58..5e712df9f2 100644 --- a/src/renderer/api/kube-object.ts +++ b/src/renderer/api/kube-object.ts @@ -115,12 +115,12 @@ export class KubeObject implements ItemObject { return KubeObject.stringifyLabels(this.metadata.labels); } - getAnnotations(): string[] { + getAnnotations(filter = false): string[] { const labels = KubeObject.stringifyLabels(this.metadata.annotations); - return labels.filter(label => { + return filter ? labels.filter(label => { const skip = resourceApplierApi.annotations.some(key => label.startsWith(key)); return !skip; - }) + }) : labels; } getOwnerRefs() { @@ -138,7 +138,7 @@ export class KubeObject implements ItemObject { getNs(), getId(), ...getLabels(), - ...getAnnotations(), + ...getAnnotations(true), ] } From 34e141e517c474f074c93a300cb43e8ba392dd1f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 28 Sep 2020 10:30:23 -0400 Subject: [PATCH 19/25] make namespace filter multi select and change onChange (#987) Signed-off-by: Sebastian Malton --- src/renderer/components/+namespaces/namespace-select.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/+namespaces/namespace-select.tsx b/src/renderer/components/+namespaces/namespace-select.tsx index 4d4dcc22da..7657a25345 100644 --- a/src/renderer/components/+namespaces/namespace-select.tsx +++ b/src/renderer/components/+namespaces/namespace-select.tsx @@ -58,7 +58,7 @@ export class NamespaceSelect extends React.Component { const { value, label } = option; return label || ( <> - {showIcons && } + {showIcons && } {value} ); @@ -91,14 +91,15 @@ export class NamespaceSelectFilter extends React.Component { closeMenuOnSelect={false} isOptionSelected={() => false} controlShouldRenderValue={false} - onChange={({ value: namespace }: SelectOption) => toggleContext(namespace)} + isMulti + onChange={([{ value }]: SelectOption[]) => toggleContext(value)} formatOptionLabel={({ value: namespace }: SelectOption) => { const isSelected = hasContext(namespace); return (
- + {namespace} - {isSelected && } + {isSelected && }
) }} From 459742556b931c4c16bb36681996438ade043670 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 29 Sep 2020 10:58:16 +0300 Subject: [PATCH 20/25] Fix CRD conditions rendering (#994) * Using reason field if no type provided Signed-off-by: Alex Andreev * Lowecasing condition badge class Signed-off-by: Alex Andreev --- src/renderer/api/endpoints/crd.api.ts | 2 +- .../+custom-resources/crd-resource-details.tsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/renderer/api/endpoints/crd.api.ts b/src/renderer/api/endpoints/crd.api.ts index 2a029e6f9d..444d59497d 100644 --- a/src/renderer/api/endpoints/crd.api.ts +++ b/src/renderer/api/endpoints/crd.api.ts @@ -50,7 +50,7 @@ export class CustomResourceDefinition extends KubeObject { message: string; reason: string; status: string; - type: string; + type?: string; }[]; acceptedNames: { plural: string; diff --git a/src/renderer/components/+custom-resources/crd-resource-details.tsx b/src/renderer/components/+custom-resources/crd-resource-details.tsx index 201d5e6e4a..ac64bea683 100644 --- a/src/renderer/components/+custom-resources/crd-resource-details.tsx +++ b/src/renderer/components/+custom-resources/crd-resource-details.tsx @@ -13,8 +13,9 @@ import { apiManager } from "../../api/api-manager"; import { crdStore } from "./crd.store"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { Input } from "../input"; +import { CustomResourceDefinition } from "../../api/endpoints/crd.api"; -interface Props extends KubeObjectDetailsProps { +interface Props extends KubeObjectDetailsProps { } function CrdColumnValue({ value }: { value: any[] | {} | string }) { @@ -66,12 +67,14 @@ export class CrdResourceDetails extends React.Component { })} {showStatus && ( Status} className="status" labelsOnly> - {object.status.conditions.map((condition: { type: string; message: string; status: string }) => { - const { type, message, status } = condition; + {object.status.conditions.map((condition, index) => { + const { type, reason, message, status } = condition; + const kind = type || reason; + if (!kind) return null; return ( ); From c542ad03482c25df0c0ac768401b43d35e6ad44c Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 29 Sep 2020 11:25:29 -0400 Subject: [PATCH 21/25] refactor overview statuses to be more DRY (#912) * refactor overview statuses to be more DRY Signed-off-by: Sebastian Malton --- locales/en/messages.po | 170 ++++++++++-------- locales/fi/messages.po | 170 ++++++++++-------- locales/ru/messages.po | 170 ++++++++++-------- .../+workloads-daemonsets/daemonsets.store.ts | 6 +- .../deployments.store.ts | 6 +- .../+workloads-overview/overview-statuses.tsx | 88 ++++----- .../replicasets.store.ts | 6 +- .../statefulset.store.ts | 6 +- src/renderer/components/+workloads/index.ts | 2 +- .../components/+workloads/workloads.route.ts | 10 ++ .../components/+workloads/workloads.stores.ts | 17 ++ src/renderer/kube-object.store.ts | 2 + src/renderer/utils/rbac.ts | 28 +++ 13 files changed, 377 insertions(+), 304 deletions(-) create mode 100644 src/renderer/components/+workloads/workloads.stores.ts create mode 100644 src/renderer/utils/rbac.ts diff --git a/locales/en/messages.po b/locales/en/messages.po index 8ae622d54c..092161d0cf 100644 --- a/locales/en/messages.po +++ b/locales/en/messages.po @@ -25,7 +25,7 @@ msgstr "" msgid "(as a percentage of request)" msgstr "(as a percentage of request)" -#: src/renderer/components/+workspaces/workspaces.tsx:108 +#: src/renderer/components/+workspaces/workspaces.tsx:121 msgid "(current)" msgstr "(current)" @@ -57,11 +57,11 @@ msgstr "<0>{0} successfully created" #~ msgid "A HTTP proxy server URL (format: http://
:)" #~ msgstr "A HTTP proxy server URL (format: http://
:)" -#: src/renderer/components/input/input.validators.ts:40 +#: src/renderer/components/input/input.validators.ts:46 msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics." msgstr "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics." -#: src/renderer/components/+workspaces/workspaces.tsx:84 +#: src/renderer/components/+workspaces/workspaces.tsx:93 msgid "A single workspaces contains a list of clusters and their full configuration." msgstr "A single workspaces contains a list of clusters and their full configuration." @@ -87,8 +87,8 @@ msgstr "Account Name" msgid "Active" msgstr "Active" -#: src/renderer/components/+add-cluster/add-cluster.tsx:303 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:118 +#: src/renderer/components/+add-cluster/add-cluster.tsx:288 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:130 msgid "Add Cluster" msgstr "Add Cluster" @@ -100,7 +100,7 @@ msgstr "Add Namespace" msgid "Add RoleBinding" msgstr "Add RoleBinding" -#: src/renderer/components/+workspaces/workspaces.tsx:125 +#: src/renderer/components/+workspaces/workspaces.tsx:138 msgid "Add Workspace" msgstr "Add Workspace" @@ -112,7 +112,7 @@ msgstr "Add bindings to {name}" #~ msgid "Add cluster" #~ msgstr "Add cluster" -#: src/renderer/components/+add-cluster/add-cluster.tsx:320 +#: src/renderer/components/+add-cluster/add-cluster.tsx:305 msgid "Add cluster(s)" msgstr "Add cluster(s)" @@ -136,7 +136,7 @@ msgstr "Add field" #~ msgid "Adding clusters: <0>{0}" #~ msgstr "Adding clusters: <0>{0}" -#: src/renderer/components/+preferences/preferences.tsx:103 +#: src/renderer/components/+preferences/preferences.tsx:111 msgid "Adding helm branch <0>{0} has failed: {1}" msgstr "Adding helm branch <0>{0} has failed: {1}" @@ -191,7 +191,7 @@ msgstr "Affinities" msgid "Age" msgstr "Age" -#: src/renderer/components/+workspaces/workspaces.tsx:64 +#: src/renderer/components/+workspaces/workspaces.tsx:65 msgid "All clusters within workspace will be cleared as well" msgstr "All clusters within workspace will be cleared as well" @@ -219,11 +219,11 @@ msgstr "Allocatable" msgid "Allow Privilege Escalation" msgstr "Allow Privilege Escalation" -#: src/renderer/components/+preferences/preferences.tsx:162 +#: src/renderer/components/+preferences/preferences.tsx:169 msgid "Allow telemetry & usage tracking" msgstr "Allow telemetry & usage tracking" -#: src/renderer/components/+preferences/preferences.tsx:154 +#: src/renderer/components/+preferences/preferences.tsx:161 msgid "Allow untrusted Certificate Authorities" msgstr "Allow untrusted Certificate Authorities" @@ -281,7 +281,7 @@ msgstr "Applying.." msgid "Apps" msgstr "Apps" -#: src/renderer/components/+workspaces/workspaces.tsx:61 +#: src/renderer/components/+workspaces/workspaces.tsx:62 msgid "Are you sure you want remove workspace <0>{0}?" msgstr "Are you sure you want remove workspace <0>{0}?" @@ -293,7 +293,7 @@ msgstr "Are you sure you want to drain <0>{nodeName}?" msgid "Arguments" msgstr "Arguments" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:108 +#: src/renderer/components/+landing-page/landing-page.tsx:27 msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgstr "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." @@ -323,7 +323,7 @@ msgstr "Binding targets" msgid "Bindings" msgstr "Bindings" -#: src/renderer/components/+add-cluster/add-cluster.tsx:251 +#: src/renderer/components/+add-cluster/add-cluster.tsx:236 msgid "Browse" msgstr "Browse" @@ -402,7 +402,7 @@ msgstr "CPU requests" msgid "CPU:" msgstr "CPU:" -#: src/renderer/components/+workspaces/workspaces.tsx:119 +#: src/renderer/components/+workspaces/workspaces.tsx:133 #: src/renderer/components/confirm-dialog/confirm-dialog.tsx:44 #: src/renderer/components/dock/info-panel.tsx:97 #: src/renderer/components/wizard/wizard.tsx:130 @@ -422,7 +422,7 @@ msgstr "Cancel" msgid "Capacity" msgstr "Capacity" -#: src/renderer/components/+preferences/preferences.tsx:153 +#: src/renderer/components/+preferences/preferences.tsx:160 msgid "Certificate Trust" msgstr "Certificate Trust" @@ -501,7 +501,7 @@ msgstr "Cluster IP" msgid "Cluster Issuers" msgstr "Cluster Issuers" -#: src/renderer/components/+preferences/preferences.tsx:126 +#: src/renderer/components/+preferences/preferences.tsx:134 msgid "Color Theme" msgstr "Color Theme" @@ -712,7 +712,6 @@ msgid "Cron Jobs" msgstr "Cron Jobs" #: src/renderer/components/+workloads/workloads.tsx:77 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:67 msgid "CronJobs" msgstr "CronJobs" @@ -759,7 +758,6 @@ msgid "Daemon Sets" msgstr "Daemon Sets" #: src/renderer/components/+workloads/workloads.tsx:53 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:57 msgid "DaemonSets" msgstr "DaemonSets" @@ -784,11 +782,15 @@ msgstr "Default Add Capabilities" msgid "Default Runtime Class Name" msgstr "Default Runtime Class Name" +#: src/renderer/components/+preferences/kubectl-binaries.tsx:30 +msgid "Default:" +msgstr "Default:" + #: src/renderer/components/+custom-resources/custom-resources.tsx:22 msgid "Definitions" msgstr "Definitions" -#: src/renderer/components/+workspaces/workspaces.tsx:113 +#: src/renderer/components/+workspaces/workspaces.tsx:126 #: src/renderer/components/menu/menu-actions.tsx:84 msgid "Delete" msgstr "Delete" @@ -799,12 +801,11 @@ msgstr "Deploy Revisions" #: src/renderer/components/+workloads/workloads.tsx:45 #: src/renderer/components/+workloads-deployments/deployments.tsx:57 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:47 msgid "Deployments" msgstr "Deployments" #: src/renderer/components/+apps-helm-charts/helm-charts.tsx:65 -#: src/renderer/components/+workspaces/workspaces.tsx:118 +#: src/renderer/components/+workspaces/workspaces.tsx:131 msgid "Description" msgstr "Description" @@ -817,7 +818,7 @@ msgstr "Desired Healthy" msgid "Desired number of replicas" msgstr "Desired number of replicas" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:64 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:65 msgid "Disconnect" msgstr "Disconnect" @@ -831,7 +832,7 @@ msgstr "Disk" msgid "Disk:" msgstr "Disk:" -#: src/renderer/components/+preferences/preferences.tsx:158 +#: src/renderer/components/+preferences/preferences.tsx:165 msgid "Does not affect cluster communications!" msgstr "Does not affect cluster communications!" @@ -840,14 +841,22 @@ msgid "Domains" msgstr "Domains" #: src/renderer/components/+preferences/preferences.tsx:129 -msgid "Download Mirror" -msgstr "Download Mirror" +#~ msgid "Download Mirror" +#~ msgstr "Download Mirror" #: src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx:90 msgid "Download file" msgstr "Download file" -#: src/renderer/components/+preferences/preferences.tsx:130 +#: src/renderer/components/+preferences/kubectl-binaries.tsx:39 +msgid "Download kubectl binaries" +msgstr "Download kubectl binaries" + +#: src/renderer/components/+preferences/kubectl-binaries.tsx:37 +msgid "Download kubectl binaries matching to Kubernetes cluster verison." +msgstr "Download kubectl binaries matching to Kubernetes cluster verison." + +#: src/renderer/components/+preferences/kubectl-binaries.tsx:41 msgid "Download mirror for kubectl" msgstr "Download mirror for kubectl" @@ -873,7 +882,7 @@ msgstr "Duration" msgid "E-mail" msgstr "E-mail" -#: src/renderer/components/+workspaces/workspaces.tsx:112 +#: src/renderer/components/+workspaces/workspaces.tsx:125 #: src/renderer/components/menu/menu-actions.tsx:80 #: src/renderer/components/menu/menu-actions.tsx:81 msgid "Edit" @@ -1000,7 +1009,7 @@ msgstr "From <0>{from} to <1>{to}" msgid "Fs Group" msgstr "Fs Group" -#: src/renderer/components/+landing-page/landing-page.tsx:23 +#: src/renderer/components/+landing-page/landing-page.tsx:37 msgid "Get started by associating one or more clusters to Lens." msgstr "Get started by associating one or more clusters to Lens." @@ -1022,7 +1031,7 @@ msgstr "Groups" msgid "HPA" msgstr "HPA" -#: src/renderer/components/+preferences/preferences.tsx:147 +#: src/renderer/components/+preferences/preferences.tsx:137 msgid "HTTP Proxy" msgstr "HTTP Proxy" @@ -1030,7 +1039,7 @@ msgstr "HTTP Proxy" #~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API." #~ msgstr "HTTP Proxy server. Used for communicating with Kubernetes API." -#: src/renderer/components/+preferences/preferences.tsx:132 +#: src/renderer/components/+preferences/preferences.tsx:145 msgid "Helm" msgstr "Helm" @@ -1050,7 +1059,7 @@ msgstr "Helm Install: {repo}/{name}" msgid "Helm Upgrade: {0}" msgstr "Helm Upgrade: {0}" -#: src/renderer/components/+preferences/preferences.tsx:47 +#: src/renderer/components/+preferences/preferences.tsx:51 msgid "Helm branch <0>{0} already in use" msgstr "Helm branch <0>{0} already in use" @@ -1157,11 +1166,11 @@ msgstr "Installation complete!" msgid "Installing..." msgstr "Installing..." -#: src/renderer/components/input/input.validators.ts:44 +#: src/renderer/components/input/input.validators.ts:50 msgid "Invalid account ID" msgstr "Invalid account ID" -#: src/renderer/components/input/input.validators.ts:15 +#: src/renderer/components/input/input.validators.ts:16 msgid "Invalid number" msgstr "Invalid number" @@ -1197,7 +1206,6 @@ msgstr "Job name" #: src/renderer/components/+workloads/workloads.tsx:69 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-jobs/jobs.tsx:36 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:62 msgid "Jobs" msgstr "Jobs" @@ -1241,6 +1249,10 @@ msgstr "Kubeconfig" msgid "Kubeconfig File" msgstr "Kubeconfig File" +#: src/renderer/components/+preferences/kubectl-binaries.tsx:35 +msgid "Kubectl Binary" +msgstr "Kubectl Binary" + #: src/renderer/components/+nodes/node-details.tsx:98 msgid "Kubelet version" msgstr "Kubelet version" @@ -1357,7 +1369,7 @@ msgstr "Max Pods" msgid "Max Unavailable" msgstr "Max Unavailable" -#: src/renderer/components/input/input.validators.ts:35 +#: src/renderer/components/input/input.validators.ts:41 msgid "Maximum length is {maxLength}" msgstr "Maximum length is {maxLength}" @@ -1433,7 +1445,7 @@ msgstr "Min Pods" msgid "Minimize" msgstr "Minimize" -#: src/renderer/components/input/input.validators.ts:30 +#: src/renderer/components/input/input.validators.ts:36 msgid "Minimum length is {minLength}" msgstr "Minimum length is {minLength}" @@ -1497,7 +1509,7 @@ msgstr "Mounts" #: src/renderer/components/+workloads-pods/pods.tsx:74 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:50 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40 -#: src/renderer/components/+workspaces/workspaces.tsx:117 +#: src/renderer/components/+workspaces/workspaces.tsx:130 #: src/renderer/components/dock/edit-resource.tsx:90 #: src/renderer/components/kube-object/kube-object-meta.tsx:20 msgid "Name" @@ -1565,7 +1577,7 @@ msgstr "Namespaces" msgid "Namespaces: {0}" msgstr "Namespaces: {0}" -#: src/renderer/components/+preferences/preferences.tsx:157 +#: src/renderer/components/+preferences/preferences.tsx:164 msgid "Needed with some corporate proxies that do certificate re-writing." msgstr "Needed with some corporate proxies that do certificate re-writing." @@ -1626,7 +1638,7 @@ msgstr "No Nodes Available." #~ msgid "No contexts available or they already added" #~ msgstr "No contexts available or they already added" -#: src/renderer/components/+add-cluster/add-cluster.tsx:275 +#: src/renderer/components/+add-cluster/add-cluster.tsx:260 msgid "No contexts available or they have been added already" msgstr "No contexts available or they have been added already" @@ -1742,7 +1754,7 @@ msgid "Organization" msgstr "Organization" #: src/renderer/components/+workloads/workloads.tsx:29 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:35 +#: src/renderer/components/+workloads-overview/overview-statuses.tsx:45 msgid "Overview" msgstr "Overview" @@ -1758,7 +1770,7 @@ msgstr "Parallelism" msgid "Parameters" msgstr "Parameters" -#: src/renderer/components/+add-cluster/add-cluster.tsx:245 +#: src/renderer/components/+add-cluster/add-cluster.tsx:230 msgid "Paste as text" msgstr "Paste as text" @@ -1848,7 +1860,6 @@ msgstr "Pod shell" #: src/renderer/components/+workloads/workloads.tsx:37 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47 #: src/renderer/components/+workloads-deployments/deployments.tsx:60 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:42 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:89 #: src/renderer/components/+workloads-pods/pods.tsx:73 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:52 @@ -1899,7 +1910,7 @@ msgstr "Privileged" #~ msgid "Pro-Tip: paste kubeconfig to collect available contexts" #~ msgstr "Pro-Tip: paste kubeconfig to collect available contexts" -#: src/renderer/components/+add-cluster/add-cluster.tsx:263 +#: src/renderer/components/+add-cluster/add-cluster.tsx:248 msgid "Pro-Tip: paste kubeconfig to get available contexts" msgstr "Pro-Tip: paste kubeconfig to get available contexts" @@ -1907,7 +1918,7 @@ msgstr "Pro-Tip: paste kubeconfig to get available contexts" #~ msgid "Pro-Tip: paste kubeconfig to parse available contexts" #~ msgstr "Pro-Tip: paste kubeconfig to parse available contexts" -#: src/renderer/components/+add-cluster/add-cluster.tsx:254 +#: src/renderer/components/+add-cluster/add-cluster.tsx:239 msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" msgstr "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" @@ -1924,11 +1935,11 @@ msgstr "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" msgid "Provisioner" msgstr "Provisioner" -#: src/renderer/components/+preferences/preferences.tsx:150 +#: src/renderer/components/+preferences/preferences.tsx:140 msgid "Proxy is used only for non-cluster communication." msgstr "Proxy is used only for non-cluster communication." -#: src/renderer/components/+add-cluster/add-cluster.tsx:308 +#: src/renderer/components/+add-cluster/add-cluster.tsx:293 msgid "Proxy settings" msgstr "Proxy settings" @@ -2008,10 +2019,10 @@ msgstr "Release: {0}" msgid "Releases" msgstr "Releases" -#: src/renderer/components/+preferences/preferences.tsx:139 +#: src/renderer/components/+preferences/preferences.tsx:152 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:80 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:76 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:82 #: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:85 @@ -2022,7 +2033,7 @@ msgstr "Remove" msgid "Remove <0>{releaseNames}?" msgstr "Remove <0>{releaseNames}?" -#: src/renderer/components/+workspaces/workspaces.tsx:51 +#: src/renderer/components/+workspaces/workspaces.tsx:52 msgid "Remove Workspace" msgstr "Remove Workspace" @@ -2050,7 +2061,7 @@ msgstr "Remove selected items ({0})" msgid "Remove {resourceKind} <0>{resourceName}?" msgstr "Remove {resourceKind} <0>{resourceName}?" -#: src/renderer/components/+preferences/preferences.tsx:114 +#: src/renderer/components/+preferences/preferences.tsx:122 msgid "Removing helm branch <0>{0} has failed: {1}" msgstr "Removing helm branch <0>{0} has failed: {1}" @@ -2074,7 +2085,7 @@ msgstr "Replicas" msgid "Repo/Name" msgstr "Repo/Name" -#: src/renderer/components/+preferences/preferences.tsx:133 +#: src/renderer/components/+preferences/preferences.tsx:146 msgid "Repositories" msgstr "Repositories" @@ -2109,7 +2120,7 @@ msgstr "Required Drop Capabilities" msgid "Required field" msgstr "Required field" -#: src/renderer/components/+add-cluster/add-cluster.tsx:250 +#: src/renderer/components/+add-cluster/add-cluster.tsx:235 #: src/renderer/components/item-object-list/page-filters-list.tsx:31 msgid "Reset" msgstr "Reset" @@ -2252,7 +2263,7 @@ msgstr "Runtime Class" #: src/renderer/components/+config-maps/config-map-details.tsx:78 #: src/renderer/components/+config-secrets/secret-details.tsx:97 #: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216 -#: src/renderer/components/+workspaces/workspaces.tsx:120 +#: src/renderer/components/+workspaces/workspaces.tsx:132 #: src/renderer/components/dock/edit-resource.tsx:88 msgid "Save" msgstr "Save" @@ -2341,13 +2352,13 @@ msgstr "Select a quota.." #~ msgid "Select context(s)" #~ msgstr "Select context(s)" -#: src/renderer/components/+add-cluster/add-cluster.tsx:272 -#~ msgid "Select contexts" -#~ msgstr "Select contexts" +#: src/renderer/components/+add-cluster/add-cluster.tsx:257 +msgid "Select contexts" +msgstr "Select contexts" #: src/renderer/components/+add-cluster/add-cluster.tsx:272 -msgid "Select contexts (available: {0})" -msgstr "Select contexts (available: {0})" +#~ msgid "Select contexts (available: {0})" +#~ msgstr "Select contexts (available: {0})" #: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76 @@ -2371,7 +2382,7 @@ msgstr "Select custom kubeconfig file" #~ msgid "Select kubeconfig" #~ msgstr "Select kubeconfig" -#: src/renderer/components/+add-cluster/add-cluster.tsx:244 +#: src/renderer/components/+add-cluster/add-cluster.tsx:229 msgid "Select kubeconfig file" msgstr "Select kubeconfig file" @@ -2399,7 +2410,7 @@ msgstr "Select service accounts" #~ msgid "Selected contexts ({0}): <0>{1}" #~ msgstr "Selected contexts ({0}): <0>{1}" -#: src/renderer/components/+add-cluster/add-cluster.tsx:271 +#: src/renderer/components/+add-cluster/add-cluster.tsx:256 msgid "Selected contexts: <0>{0}" msgstr "Selected contexts: <0>{0}" @@ -2503,7 +2514,6 @@ msgid "Stateful Sets" msgstr "Stateful Sets" #: src/renderer/components/+workloads/workloads.tsx:61 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:52 msgid "StatefulSets" msgstr "StatefulSets" @@ -2605,11 +2615,11 @@ msgstr "TLS" msgid "Taints" msgstr "Taints" -#: src/renderer/components/+preferences/preferences.tsx:161 +#: src/renderer/components/+preferences/preferences.tsx:168 msgid "Telemetry & Usage Tracking" msgstr "Telemetry & Usage Tracking" -#: src/renderer/components/+preferences/preferences.tsx:164 +#: src/renderer/components/+preferences/preferences.tsx:171 msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgstr "Telemetry & usage data is collected to continuously improve the Lens experience." @@ -2629,15 +2639,19 @@ msgstr "There are no logs available for container." msgid "There are no logs available." msgstr "There are no logs available." -#: src/renderer/components/input/input.validators.ts:5 +#: src/renderer/components/input/input.validators.ts:6 msgid "This field is required" msgstr "This field is required" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:106 +#: src/renderer/components/input/input.validators.ts:31 +msgid "This field must be a valid path" +msgstr "This field must be a valid path" + +#: src/renderer/components/+landing-page/landing-page.tsx:25 msgid "This is the quick launch menu." msgstr "This is the quick launch menu." -#: src/renderer/components/+preferences/preferences.tsx:156 +#: src/renderer/components/+preferences/preferences.tsx:163 msgid "This will make Lens to trust ANY certificate authority without any validations." msgstr "This will make Lens to trust ANY certificate authority without any validations." @@ -2661,13 +2675,13 @@ msgstr "Tolerations" msgid "Transmit" msgstr "Transmit" -#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:107 +#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:106 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80 msgid "Trigger" msgstr "Trigger" -#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103 +#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:102 msgid "Trigger CronJob <0>{cronjobName}" msgstr "Trigger CronJob <0>{cronjobName}" @@ -2690,7 +2704,7 @@ msgstr "Trigger CronJob <0>{cronjobName}" msgid "Type" msgstr "Type" -#: src/renderer/components/+preferences/preferences.tsx:148 +#: src/renderer/components/+preferences/preferences.tsx:138 msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)" msgstr "Type HTTP proxy url (example: http://proxy.acme.org:8080)" @@ -2827,11 +2841,11 @@ msgstr "Waiting services to be running" msgid "Warnings: {0}" msgstr "Warnings: {0}" -#: src/renderer/components/+landing-page/landing-page.tsx:20 +#: src/renderer/components/+landing-page/landing-page.tsx:34 msgid "Welcome!" msgstr "Welcome!" -#: src/renderer/components/+workspaces/workspaces.tsx:79 +#: src/renderer/components/+workspaces/workspaces.tsx:88 msgid "What is a Workspace?" msgstr "What is a Workspace?" @@ -2844,19 +2858,19 @@ msgid "Workloads" msgstr "Workloads" #: src/renderer/components/+workspaces/workspace-menu.tsx:39 -#: src/renderer/components/+workspaces/workspaces.tsx:91 +#: src/renderer/components/+workspaces/workspaces.tsx:100 msgid "Workspaces" msgstr "Workspaces" -#: src/renderer/components/+workspaces/workspaces.tsx:81 +#: src/renderer/components/+workspaces/workspaces.tsx:90 msgid "Workspaces are used to organize number of clusters into logical groups." msgstr "Workspaces are used to organize number of clusters into logical groups." -#: src/renderer/components/input/input.validators.ts:10 +#: src/renderer/components/input/input.validators.ts:11 msgid "Wrong email format" msgstr "Wrong email format" -#: src/renderer/components/input/input.validators.ts:25 +#: src/renderer/components/input/input.validators.ts:26 msgid "Wrong url format" msgstr "Wrong url format" @@ -2915,7 +2929,7 @@ msgstr "listKind" msgid "never" msgstr "never" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:121 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:133 msgid "new" msgstr "new" diff --git a/locales/fi/messages.po b/locales/fi/messages.po index f64a50f272..363b9a49cc 100644 --- a/locales/fi/messages.po +++ b/locales/fi/messages.po @@ -25,7 +25,7 @@ msgstr "" msgid "(as a percentage of request)" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:108 +#: src/renderer/components/+workspaces/workspaces.tsx:121 msgid "(current)" msgstr "" @@ -57,11 +57,11 @@ msgstr "" #~ msgid "A HTTP proxy server URL (format: http://
:)" #~ msgstr "" -#: src/renderer/components/input/input.validators.ts:40 +#: src/renderer/components/input/input.validators.ts:46 msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics." msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:84 +#: src/renderer/components/+workspaces/workspaces.tsx:93 msgid "A single workspaces contains a list of clusters and their full configuration." msgstr "" @@ -87,8 +87,8 @@ msgstr "" msgid "Active" msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:303 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:118 +#: src/renderer/components/+add-cluster/add-cluster.tsx:288 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:130 msgid "Add Cluster" msgstr "" @@ -100,7 +100,7 @@ msgstr "" msgid "Add RoleBinding" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:125 +#: src/renderer/components/+workspaces/workspaces.tsx:138 msgid "Add Workspace" msgstr "" @@ -112,7 +112,7 @@ msgstr "" #~ msgid "Add cluster" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:320 +#: src/renderer/components/+add-cluster/add-cluster.tsx:305 msgid "Add cluster(s)" msgstr "" @@ -136,7 +136,7 @@ msgstr "" #~ msgid "Adding clusters: <0>{0}" #~ msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:103 +#: src/renderer/components/+preferences/preferences.tsx:111 msgid "Adding helm branch <0>{0} has failed: {1}" msgstr "" @@ -191,7 +191,7 @@ msgstr "" msgid "Age" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:64 +#: src/renderer/components/+workspaces/workspaces.tsx:65 msgid "All clusters within workspace will be cleared as well" msgstr "" @@ -219,11 +219,11 @@ msgstr "" msgid "Allow Privilege Escalation" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:162 +#: src/renderer/components/+preferences/preferences.tsx:169 msgid "Allow telemetry & usage tracking" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:154 +#: src/renderer/components/+preferences/preferences.tsx:161 msgid "Allow untrusted Certificate Authorities" msgstr "" @@ -281,7 +281,7 @@ msgstr "" msgid "Apps" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:61 +#: src/renderer/components/+workspaces/workspaces.tsx:62 msgid "Are you sure you want remove workspace <0>{0}?" msgstr "" @@ -293,7 +293,7 @@ msgstr "" msgid "Arguments" msgstr "" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:108 +#: src/renderer/components/+landing-page/landing-page.tsx:27 msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgstr "" @@ -323,7 +323,7 @@ msgstr "" msgid "Bindings" msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:251 +#: src/renderer/components/+add-cluster/add-cluster.tsx:236 msgid "Browse" msgstr "" @@ -402,7 +402,7 @@ msgstr "" msgid "CPU:" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:119 +#: src/renderer/components/+workspaces/workspaces.tsx:133 #: src/renderer/components/confirm-dialog/confirm-dialog.tsx:44 #: src/renderer/components/dock/info-panel.tsx:97 #: src/renderer/components/wizard/wizard.tsx:130 @@ -422,7 +422,7 @@ msgstr "" msgid "Capacity" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:153 +#: src/renderer/components/+preferences/preferences.tsx:160 msgid "Certificate Trust" msgstr "" @@ -497,7 +497,7 @@ msgstr "" msgid "Cluster Issuers" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:126 +#: src/renderer/components/+preferences/preferences.tsx:134 msgid "Color Theme" msgstr "" @@ -708,7 +708,6 @@ msgid "Cron Jobs" msgstr "" #: src/renderer/components/+workloads/workloads.tsx:77 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:67 msgid "CronJobs" msgstr "" @@ -755,7 +754,6 @@ msgid "Daemon Sets" msgstr "" #: src/renderer/components/+workloads/workloads.tsx:53 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:57 msgid "DaemonSets" msgstr "" @@ -780,11 +778,15 @@ msgstr "" msgid "Default Runtime Class Name" msgstr "" +#: src/renderer/components/+preferences/kubectl-binaries.tsx:30 +msgid "Default:" +msgstr "" + #: src/renderer/components/+custom-resources/custom-resources.tsx:22 msgid "Definitions" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:113 +#: src/renderer/components/+workspaces/workspaces.tsx:126 #: src/renderer/components/menu/menu-actions.tsx:84 msgid "Delete" msgstr "" @@ -795,12 +797,11 @@ msgstr "" #: src/renderer/components/+workloads/workloads.tsx:45 #: src/renderer/components/+workloads-deployments/deployments.tsx:57 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:47 msgid "Deployments" msgstr "" #: src/renderer/components/+apps-helm-charts/helm-charts.tsx:65 -#: src/renderer/components/+workspaces/workspaces.tsx:118 +#: src/renderer/components/+workspaces/workspaces.tsx:131 msgid "Description" msgstr "" @@ -813,7 +814,7 @@ msgstr "" msgid "Desired number of replicas" msgstr "" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:64 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:65 msgid "Disconnect" msgstr "" @@ -827,7 +828,7 @@ msgstr "" msgid "Disk:" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:158 +#: src/renderer/components/+preferences/preferences.tsx:165 msgid "Does not affect cluster communications!" msgstr "" @@ -836,14 +837,22 @@ msgid "Domains" msgstr "" #: src/renderer/components/+preferences/preferences.tsx:129 -msgid "Download Mirror" -msgstr "" +#~ msgid "Download Mirror" +#~ msgstr "" #: src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx:90 msgid "Download file" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:130 +#: src/renderer/components/+preferences/kubectl-binaries.tsx:39 +msgid "Download kubectl binaries" +msgstr "" + +#: src/renderer/components/+preferences/kubectl-binaries.tsx:37 +msgid "Download kubectl binaries matching to Kubernetes cluster verison." +msgstr "" + +#: src/renderer/components/+preferences/kubectl-binaries.tsx:41 msgid "Download mirror for kubectl" msgstr "" @@ -869,7 +878,7 @@ msgstr "" msgid "E-mail" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:112 +#: src/renderer/components/+workspaces/workspaces.tsx:125 #: src/renderer/components/menu/menu-actions.tsx:80 #: src/renderer/components/menu/menu-actions.tsx:81 msgid "Edit" @@ -991,7 +1000,7 @@ msgstr "" msgid "Fs Group" msgstr "" -#: src/renderer/components/+landing-page/landing-page.tsx:23 +#: src/renderer/components/+landing-page/landing-page.tsx:37 msgid "Get started by associating one or more clusters to Lens." msgstr "" @@ -1013,7 +1022,7 @@ msgstr "" msgid "HPA" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:147 +#: src/renderer/components/+preferences/preferences.tsx:137 msgid "HTTP Proxy" msgstr "" @@ -1021,7 +1030,7 @@ msgstr "" #~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API." #~ msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:132 +#: src/renderer/components/+preferences/preferences.tsx:145 msgid "Helm" msgstr "" @@ -1041,7 +1050,7 @@ msgstr "" msgid "Helm Upgrade: {0}" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:47 +#: src/renderer/components/+preferences/preferences.tsx:51 msgid "Helm branch <0>{0} already in use" msgstr "" @@ -1148,11 +1157,11 @@ msgstr "" msgid "Installing..." msgstr "" -#: src/renderer/components/input/input.validators.ts:44 +#: src/renderer/components/input/input.validators.ts:50 msgid "Invalid account ID" msgstr "" -#: src/renderer/components/input/input.validators.ts:15 +#: src/renderer/components/input/input.validators.ts:16 msgid "Invalid number" msgstr "" @@ -1188,7 +1197,6 @@ msgstr "" #: src/renderer/components/+workloads/workloads.tsx:69 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-jobs/jobs.tsx:36 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:62 msgid "Jobs" msgstr "" @@ -1232,6 +1240,10 @@ msgstr "" msgid "Kubeconfig File" msgstr "" +#: src/renderer/components/+preferences/kubectl-binaries.tsx:35 +msgid "Kubectl Binary" +msgstr "" + #: src/renderer/components/+nodes/node-details.tsx:98 msgid "Kubelet version" msgstr "" @@ -1348,7 +1360,7 @@ msgstr "" msgid "Max Unavailable" msgstr "" -#: src/renderer/components/input/input.validators.ts:35 +#: src/renderer/components/input/input.validators.ts:41 msgid "Maximum length is {maxLength}" msgstr "" @@ -1424,7 +1436,7 @@ msgstr "" msgid "Minimize" msgstr "" -#: src/renderer/components/input/input.validators.ts:30 +#: src/renderer/components/input/input.validators.ts:36 msgid "Minimum length is {minLength}" msgstr "" @@ -1488,7 +1500,7 @@ msgstr "" #: src/renderer/components/+workloads-pods/pods.tsx:74 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:50 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40 -#: src/renderer/components/+workspaces/workspaces.tsx:117 +#: src/renderer/components/+workspaces/workspaces.tsx:130 #: src/renderer/components/dock/edit-resource.tsx:90 #: src/renderer/components/kube-object/kube-object-meta.tsx:20 msgid "Name" @@ -1556,7 +1568,7 @@ msgstr "" msgid "Namespaces: {0}" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:157 +#: src/renderer/components/+preferences/preferences.tsx:164 msgid "Needed with some corporate proxies that do certificate re-writing." msgstr "" @@ -1609,7 +1621,7 @@ msgstr "" #~ msgid "No contexts available or they already added" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:275 +#: src/renderer/components/+add-cluster/add-cluster.tsx:260 msgid "No contexts available or they have been added already" msgstr "" @@ -1725,7 +1737,7 @@ msgid "Organization" msgstr "" #: src/renderer/components/+workloads/workloads.tsx:29 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:35 +#: src/renderer/components/+workloads-overview/overview-statuses.tsx:45 msgid "Overview" msgstr "" @@ -1741,7 +1753,7 @@ msgstr "" msgid "Parameters" msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:245 +#: src/renderer/components/+add-cluster/add-cluster.tsx:230 msgid "Paste as text" msgstr "" @@ -1831,7 +1843,6 @@ msgstr "" #: src/renderer/components/+workloads/workloads.tsx:37 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47 #: src/renderer/components/+workloads-deployments/deployments.tsx:60 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:42 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:89 #: src/renderer/components/+workloads-pods/pods.tsx:73 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:52 @@ -1882,7 +1893,7 @@ msgstr "" #~ msgid "Pro-Tip: paste kubeconfig to collect available contexts" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:263 +#: src/renderer/components/+add-cluster/add-cluster.tsx:248 msgid "Pro-Tip: paste kubeconfig to get available contexts" msgstr "" @@ -1890,7 +1901,7 @@ msgstr "" #~ msgid "Pro-Tip: paste kubeconfig to parse available contexts" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:254 +#: src/renderer/components/+add-cluster/add-cluster.tsx:239 msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" msgstr "" @@ -1907,11 +1918,11 @@ msgstr "" msgid "Provisioner" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:150 +#: src/renderer/components/+preferences/preferences.tsx:140 msgid "Proxy is used only for non-cluster communication." msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:308 +#: src/renderer/components/+add-cluster/add-cluster.tsx:293 msgid "Proxy settings" msgstr "" @@ -1991,10 +2002,10 @@ msgstr "" msgid "Releases" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:139 +#: src/renderer/components/+preferences/preferences.tsx:152 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:80 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:76 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:82 #: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:85 @@ -2005,7 +2016,7 @@ msgstr "" msgid "Remove <0>{releaseNames}?" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:51 +#: src/renderer/components/+workspaces/workspaces.tsx:52 msgid "Remove Workspace" msgstr "" @@ -2033,7 +2044,7 @@ msgstr "" msgid "Remove {resourceKind} <0>{resourceName}?" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:114 +#: src/renderer/components/+preferences/preferences.tsx:122 msgid "Removing helm branch <0>{0} has failed: {1}" msgstr "" @@ -2057,7 +2068,7 @@ msgstr "" msgid "Repo/Name" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:133 +#: src/renderer/components/+preferences/preferences.tsx:146 msgid "Repositories" msgstr "" @@ -2092,7 +2103,7 @@ msgstr "" msgid "Required field" msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:250 +#: src/renderer/components/+add-cluster/add-cluster.tsx:235 #: src/renderer/components/item-object-list/page-filters-list.tsx:31 msgid "Reset" msgstr "" @@ -2235,7 +2246,7 @@ msgstr "" #: src/renderer/components/+config-maps/config-map-details.tsx:78 #: src/renderer/components/+config-secrets/secret-details.tsx:97 #: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216 -#: src/renderer/components/+workspaces/workspaces.tsx:120 +#: src/renderer/components/+workspaces/workspaces.tsx:132 #: src/renderer/components/dock/edit-resource.tsx:88 msgid "Save" msgstr "" @@ -2324,13 +2335,13 @@ msgstr "" #~ msgid "Select context(s)" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:272 -#~ msgid "Select contexts" -#~ msgstr "" +#: src/renderer/components/+add-cluster/add-cluster.tsx:257 +msgid "Select contexts" +msgstr "" #: src/renderer/components/+add-cluster/add-cluster.tsx:272 -msgid "Select contexts (available: {0})" -msgstr "" +#~ msgid "Select contexts (available: {0})" +#~ msgstr "" #: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76 @@ -2354,7 +2365,7 @@ msgstr "" #~ msgid "Select kubeconfig" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:244 +#: src/renderer/components/+add-cluster/add-cluster.tsx:229 msgid "Select kubeconfig file" msgstr "" @@ -2382,7 +2393,7 @@ msgstr "" #~ msgid "Selected contexts ({0}): <0>{1}" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:271 +#: src/renderer/components/+add-cluster/add-cluster.tsx:256 msgid "Selected contexts: <0>{0}" msgstr "" @@ -2486,7 +2497,6 @@ msgid "Stateful Sets" msgstr "" #: src/renderer/components/+workloads/workloads.tsx:61 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:52 msgid "StatefulSets" msgstr "" @@ -2588,11 +2598,11 @@ msgstr "" msgid "Taints" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:161 +#: src/renderer/components/+preferences/preferences.tsx:168 msgid "Telemetry & Usage Tracking" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:164 +#: src/renderer/components/+preferences/preferences.tsx:171 msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgstr "" @@ -2612,15 +2622,19 @@ msgstr "" msgid "There are no logs available." msgstr "" -#: src/renderer/components/input/input.validators.ts:5 +#: src/renderer/components/input/input.validators.ts:6 msgid "This field is required" msgstr "" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:106 +#: src/renderer/components/input/input.validators.ts:31 +msgid "This field must be a valid path" +msgstr "" + +#: src/renderer/components/+landing-page/landing-page.tsx:25 msgid "This is the quick launch menu." msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:156 +#: src/renderer/components/+preferences/preferences.tsx:163 msgid "This will make Lens to trust ANY certificate authority without any validations." msgstr "" @@ -2644,13 +2658,13 @@ msgstr "" msgid "Transmit" msgstr "" -#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:107 +#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:106 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80 msgid "Trigger" msgstr "" -#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103 +#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:102 msgid "Trigger CronJob <0>{cronjobName}" msgstr "" @@ -2673,7 +2687,7 @@ msgstr "" msgid "Type" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:148 +#: src/renderer/components/+preferences/preferences.tsx:138 msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)" msgstr "" @@ -2810,11 +2824,11 @@ msgstr "" msgid "Warnings: {0}" msgstr "" -#: src/renderer/components/+landing-page/landing-page.tsx:20 +#: src/renderer/components/+landing-page/landing-page.tsx:34 msgid "Welcome!" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:79 +#: src/renderer/components/+workspaces/workspaces.tsx:88 msgid "What is a Workspace?" msgstr "" @@ -2827,19 +2841,19 @@ msgid "Workloads" msgstr "" #: src/renderer/components/+workspaces/workspace-menu.tsx:39 -#: src/renderer/components/+workspaces/workspaces.tsx:91 +#: src/renderer/components/+workspaces/workspaces.tsx:100 msgid "Workspaces" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:81 +#: src/renderer/components/+workspaces/workspaces.tsx:90 msgid "Workspaces are used to organize number of clusters into logical groups." msgstr "" -#: src/renderer/components/input/input.validators.ts:10 +#: src/renderer/components/input/input.validators.ts:11 msgid "Wrong email format" msgstr "" -#: src/renderer/components/input/input.validators.ts:25 +#: src/renderer/components/input/input.validators.ts:26 msgid "Wrong url format" msgstr "" @@ -2898,7 +2912,7 @@ msgstr "" msgid "never" msgstr "" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:121 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:133 msgid "new" msgstr "" diff --git a/locales/ru/messages.po b/locales/ru/messages.po index e3a021d837..ea3d5a9731 100644 --- a/locales/ru/messages.po +++ b/locales/ru/messages.po @@ -26,7 +26,7 @@ msgstr "" msgid "(as a percentage of request)" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:108 +#: src/renderer/components/+workspaces/workspaces.tsx:121 msgid "(current)" msgstr "" @@ -58,11 +58,11 @@ msgstr "" #~ msgid "A HTTP proxy server URL (format: http://
:)" #~ msgstr "" -#: src/renderer/components/input/input.validators.ts:40 +#: src/renderer/components/input/input.validators.ts:46 msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics." msgstr "Это поле может содержать только латинские буквы в нижнем регистре, номера и дефис." -#: src/renderer/components/+workspaces/workspaces.tsx:84 +#: src/renderer/components/+workspaces/workspaces.tsx:93 msgid "A single workspaces contains a list of clusters and their full configuration." msgstr "" @@ -88,8 +88,8 @@ msgstr "Название аккаунта" msgid "Active" msgstr "Активный" -#: src/renderer/components/+add-cluster/add-cluster.tsx:303 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:118 +#: src/renderer/components/+add-cluster/add-cluster.tsx:288 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:130 msgid "Add Cluster" msgstr "" @@ -101,7 +101,7 @@ msgstr "Добавить Namespace" msgid "Add RoleBinding" msgstr "Добавить привязку ролей" -#: src/renderer/components/+workspaces/workspaces.tsx:125 +#: src/renderer/components/+workspaces/workspaces.tsx:138 msgid "Add Workspace" msgstr "" @@ -113,7 +113,7 @@ msgstr "Добавить привязки к {name}" #~ msgid "Add cluster" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:320 +#: src/renderer/components/+add-cluster/add-cluster.tsx:305 msgid "Add cluster(s)" msgstr "" @@ -137,7 +137,7 @@ msgstr "Добавить поле" #~ msgid "Adding clusters: <0>{0}" #~ msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:103 +#: src/renderer/components/+preferences/preferences.tsx:111 msgid "Adding helm branch <0>{0} has failed: {1}" msgstr "" @@ -192,7 +192,7 @@ msgstr "Аффинитеты" msgid "Age" msgstr "Возраст" -#: src/renderer/components/+workspaces/workspaces.tsx:64 +#: src/renderer/components/+workspaces/workspaces.tsx:65 msgid "All clusters within workspace will be cleared as well" msgstr "" @@ -220,11 +220,11 @@ msgstr "" msgid "Allow Privilege Escalation" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:162 +#: src/renderer/components/+preferences/preferences.tsx:169 msgid "Allow telemetry & usage tracking" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:154 +#: src/renderer/components/+preferences/preferences.tsx:161 msgid "Allow untrusted Certificate Authorities" msgstr "" @@ -282,7 +282,7 @@ msgstr "Применение.." msgid "Apps" msgstr "Приложения" -#: src/renderer/components/+workspaces/workspaces.tsx:61 +#: src/renderer/components/+workspaces/workspaces.tsx:62 msgid "Are you sure you want remove workspace <0>{0}?" msgstr "" @@ -294,7 +294,7 @@ msgstr "Выполнить команду drain для ноды <0>{nodeName}{from} до <1>{to}" msgid "Fs Group" msgstr "" -#: src/renderer/components/+landing-page/landing-page.tsx:23 +#: src/renderer/components/+landing-page/landing-page.tsx:37 msgid "Get started by associating one or more clusters to Lens." msgstr "" @@ -1023,7 +1032,7 @@ msgstr "Группы" msgid "HPA" msgstr "HPA" -#: src/renderer/components/+preferences/preferences.tsx:147 +#: src/renderer/components/+preferences/preferences.tsx:137 msgid "HTTP Proxy" msgstr "" @@ -1031,7 +1040,7 @@ msgstr "" #~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API." #~ msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:132 +#: src/renderer/components/+preferences/preferences.tsx:145 msgid "Helm" msgstr "" @@ -1051,7 +1060,7 @@ msgstr "Helm установка: {repo}/{name}" msgid "Helm Upgrade: {0}" msgstr "Helm обновление: {0}" -#: src/renderer/components/+preferences/preferences.tsx:47 +#: src/renderer/components/+preferences/preferences.tsx:51 msgid "Helm branch <0>{0} already in use" msgstr "" @@ -1158,11 +1167,11 @@ msgstr "Установка завершена!" msgid "Installing..." msgstr "Установка.." -#: src/renderer/components/input/input.validators.ts:44 +#: src/renderer/components/input/input.validators.ts:50 msgid "Invalid account ID" msgstr "Неверный ID аккаунта" -#: src/renderer/components/input/input.validators.ts:15 +#: src/renderer/components/input/input.validators.ts:16 msgid "Invalid number" msgstr "Неверный номер" @@ -1198,7 +1207,6 @@ msgstr "" #: src/renderer/components/+workloads/workloads.tsx:69 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-jobs/jobs.tsx:36 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:62 msgid "Jobs" msgstr "Jobs" @@ -1242,6 +1250,10 @@ msgstr "Файл конфигурации" msgid "Kubeconfig File" msgstr "Файл конфигурации" +#: src/renderer/components/+preferences/kubectl-binaries.tsx:35 +msgid "Kubectl Binary" +msgstr "" + #: src/renderer/components/+nodes/node-details.tsx:98 msgid "Kubelet version" msgstr "Версия Kubelet" @@ -1358,7 +1370,7 @@ msgstr "Макс. подов" msgid "Max Unavailable" msgstr "" -#: src/renderer/components/input/input.validators.ts:35 +#: src/renderer/components/input/input.validators.ts:41 msgid "Maximum length is {maxLength}" msgstr "Максимальная длина {maxLength}" @@ -1434,7 +1446,7 @@ msgstr "Мин. подов" msgid "Minimize" msgstr "Минимизировать" -#: src/renderer/components/input/input.validators.ts:30 +#: src/renderer/components/input/input.validators.ts:36 msgid "Minimum length is {minLength}" msgstr "Минимальная длина {minLength}" @@ -1498,7 +1510,7 @@ msgstr "Установки" #: src/renderer/components/+workloads-pods/pods.tsx:74 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:50 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40 -#: src/renderer/components/+workspaces/workspaces.tsx:117 +#: src/renderer/components/+workspaces/workspaces.tsx:130 #: src/renderer/components/dock/edit-resource.tsx:90 #: src/renderer/components/kube-object/kube-object-meta.tsx:20 msgid "Name" @@ -1566,7 +1578,7 @@ msgstr "Namespaces" msgid "Namespaces: {0}" msgstr "Namespaces: {0}" -#: src/renderer/components/+preferences/preferences.tsx:157 +#: src/renderer/components/+preferences/preferences.tsx:164 msgid "Needed with some corporate proxies that do certificate re-writing." msgstr "" @@ -1627,7 +1639,7 @@ msgstr "Нет доступных нод." #~ msgid "No contexts available or they already added" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:275 +#: src/renderer/components/+add-cluster/add-cluster.tsx:260 msgid "No contexts available or they have been added already" msgstr "" @@ -1743,7 +1755,7 @@ msgid "Organization" msgstr "Организация" #: src/renderer/components/+workloads/workloads.tsx:29 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:35 +#: src/renderer/components/+workloads-overview/overview-statuses.tsx:45 msgid "Overview" msgstr "Обзор" @@ -1759,7 +1771,7 @@ msgstr "Параллелизм" msgid "Parameters" msgstr "Параметры" -#: src/renderer/components/+add-cluster/add-cluster.tsx:245 +#: src/renderer/components/+add-cluster/add-cluster.tsx:230 msgid "Paste as text" msgstr "" @@ -1849,7 +1861,6 @@ msgstr "Командная строка пода" #: src/renderer/components/+workloads/workloads.tsx:37 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47 #: src/renderer/components/+workloads-deployments/deployments.tsx:60 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:42 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:89 #: src/renderer/components/+workloads-pods/pods.tsx:73 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:52 @@ -1900,7 +1911,7 @@ msgstr "" #~ msgid "Pro-Tip: paste kubeconfig to collect available contexts" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:263 +#: src/renderer/components/+add-cluster/add-cluster.tsx:248 msgid "Pro-Tip: paste kubeconfig to get available contexts" msgstr "" @@ -1908,7 +1919,7 @@ msgstr "" #~ msgid "Pro-Tip: paste kubeconfig to parse available contexts" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:254 +#: src/renderer/components/+add-cluster/add-cluster.tsx:239 msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" msgstr "" @@ -1925,11 +1936,11 @@ msgstr "" msgid "Provisioner" msgstr "Комиссия" -#: src/renderer/components/+preferences/preferences.tsx:150 +#: src/renderer/components/+preferences/preferences.tsx:140 msgid "Proxy is used only for non-cluster communication." msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:308 +#: src/renderer/components/+add-cluster/add-cluster.tsx:293 msgid "Proxy settings" msgstr "" @@ -2009,10 +2020,10 @@ msgstr "Установка: {0}" msgid "Releases" msgstr "Релизы" -#: src/renderer/components/+preferences/preferences.tsx:139 +#: src/renderer/components/+preferences/preferences.tsx:152 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 -#: src/renderer/components/cluster-manager/clusters-menu.tsx:80 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:76 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:82 #: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:85 @@ -2023,7 +2034,7 @@ msgstr "Удалить" msgid "Remove <0>{releaseNames}?" msgstr "Удалить <0>{releaseNames}?" -#: src/renderer/components/+workspaces/workspaces.tsx:51 +#: src/renderer/components/+workspaces/workspaces.tsx:52 msgid "Remove Workspace" msgstr "" @@ -2051,7 +2062,7 @@ msgstr "Удалить выбранные элементы ({0})" msgid "Remove {resourceKind} <0>{resourceName}?" msgstr "Удалить {resourceKind} <0>{resourceName}?" -#: src/renderer/components/+preferences/preferences.tsx:114 +#: src/renderer/components/+preferences/preferences.tsx:122 msgid "Removing helm branch <0>{0} has failed: {1}" msgstr "" @@ -2075,7 +2086,7 @@ msgstr "Реплики" msgid "Repo/Name" msgstr "Репозиторий/Имя" -#: src/renderer/components/+preferences/preferences.tsx:133 +#: src/renderer/components/+preferences/preferences.tsx:146 msgid "Repositories" msgstr "" @@ -2110,7 +2121,7 @@ msgstr "" msgid "Required field" msgstr "Обязательное поле" -#: src/renderer/components/+add-cluster/add-cluster.tsx:250 +#: src/renderer/components/+add-cluster/add-cluster.tsx:235 #: src/renderer/components/item-object-list/page-filters-list.tsx:31 msgid "Reset" msgstr "Сбросить" @@ -2253,7 +2264,7 @@ msgstr "" #: src/renderer/components/+config-maps/config-map-details.tsx:78 #: src/renderer/components/+config-secrets/secret-details.tsx:97 #: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216 -#: src/renderer/components/+workspaces/workspaces.tsx:120 +#: src/renderer/components/+workspaces/workspaces.tsx:132 #: src/renderer/components/dock/edit-resource.tsx:88 msgid "Save" msgstr "Сохранить" @@ -2342,13 +2353,13 @@ msgstr "Выберите квоту..." #~ msgid "Select context(s)" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:272 -#~ msgid "Select contexts" -#~ msgstr "" +#: src/renderer/components/+add-cluster/add-cluster.tsx:257 +msgid "Select contexts" +msgstr "" #: src/renderer/components/+add-cluster/add-cluster.tsx:272 -msgid "Select contexts (available: {0})" -msgstr "" +#~ msgid "Select contexts (available: {0})" +#~ msgstr "" #: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76 @@ -2372,7 +2383,7 @@ msgstr "" #~ msgid "Select kubeconfig" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:244 +#: src/renderer/components/+add-cluster/add-cluster.tsx:229 msgid "Select kubeconfig file" msgstr "" @@ -2400,7 +2411,7 @@ msgstr "Выбрать сервисные аккаунты" #~ msgid "Selected contexts ({0}): <0>{1}" #~ msgstr "" -#: src/renderer/components/+add-cluster/add-cluster.tsx:271 +#: src/renderer/components/+add-cluster/add-cluster.tsx:256 msgid "Selected contexts: <0>{0}" msgstr "" @@ -2504,7 +2515,6 @@ msgid "Stateful Sets" msgstr "" #: src/renderer/components/+workloads/workloads.tsx:61 -#: src/renderer/components/+workloads-overview/overview-statuses.tsx:52 msgid "StatefulSets" msgstr "StatefulSets" @@ -2606,11 +2616,11 @@ msgstr "TLS" msgid "Taints" msgstr "Метки блокировки" -#: src/renderer/components/+preferences/preferences.tsx:161 +#: src/renderer/components/+preferences/preferences.tsx:168 msgid "Telemetry & Usage Tracking" msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:164 +#: src/renderer/components/+preferences/preferences.tsx:171 msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgstr "" @@ -2630,15 +2640,19 @@ msgstr "Для контейнера нет логов." msgid "There are no logs available." msgstr "Логи отсутствуют." -#: src/renderer/components/input/input.validators.ts:5 +#: src/renderer/components/input/input.validators.ts:6 msgid "This field is required" msgstr "Это обязательное поле" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:106 +#: src/renderer/components/input/input.validators.ts:31 +msgid "This field must be a valid path" +msgstr "" + +#: src/renderer/components/+landing-page/landing-page.tsx:25 msgid "This is the quick launch menu." msgstr "" -#: src/renderer/components/+preferences/preferences.tsx:156 +#: src/renderer/components/+preferences/preferences.tsx:163 msgid "This will make Lens to trust ANY certificate authority without any validations." msgstr "" @@ -2662,13 +2676,13 @@ msgstr "Толерантности" msgid "Transmit" msgstr "Транзит" -#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:107 +#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:106 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80 msgid "Trigger" msgstr "" -#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103 +#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:102 msgid "Trigger CronJob <0>{cronjobName}" msgstr "" @@ -2691,7 +2705,7 @@ msgstr "" msgid "Type" msgstr "Тип" -#: src/renderer/components/+preferences/preferences.tsx:148 +#: src/renderer/components/+preferences/preferences.tsx:138 msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)" msgstr "" @@ -2828,11 +2842,11 @@ msgstr "Ожидание запуска сервисов" msgid "Warnings: {0}" msgstr "Предупреждения: {0}" -#: src/renderer/components/+landing-page/landing-page.tsx:20 +#: src/renderer/components/+landing-page/landing-page.tsx:34 msgid "Welcome!" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:79 +#: src/renderer/components/+workspaces/workspaces.tsx:88 msgid "What is a Workspace?" msgstr "" @@ -2845,19 +2859,19 @@ msgid "Workloads" msgstr "Ресурсы" #: src/renderer/components/+workspaces/workspace-menu.tsx:39 -#: src/renderer/components/+workspaces/workspaces.tsx:91 +#: src/renderer/components/+workspaces/workspaces.tsx:100 msgid "Workspaces" msgstr "" -#: src/renderer/components/+workspaces/workspaces.tsx:81 +#: src/renderer/components/+workspaces/workspaces.tsx:90 msgid "Workspaces are used to organize number of clusters into logical groups." msgstr "" -#: src/renderer/components/input/input.validators.ts:10 +#: src/renderer/components/input/input.validators.ts:11 msgid "Wrong email format" msgstr "Неверный формат электронной почты" -#: src/renderer/components/input/input.validators.ts:25 +#: src/renderer/components/input/input.validators.ts:26 msgid "Wrong url format" msgstr "Неверный url формат" @@ -2916,7 +2930,7 @@ msgstr "" msgid "never" msgstr "" -#: src/renderer/components/cluster-manager/clusters-menu.tsx:121 +#: src/renderer/components/cluster-manager/clusters-menu.tsx:133 msgid "new" msgstr "" diff --git a/src/renderer/components/+workloads-daemonsets/daemonsets.store.ts b/src/renderer/components/+workloads-daemonsets/daemonsets.store.ts index 3e9acdd036..dd59aad8ff 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonsets.store.ts +++ b/src/renderer/components/+workloads-daemonsets/daemonsets.store.ts @@ -11,11 +11,9 @@ export class DaemonSetStore extends KubeObjectStore { @observable metrics: IPodMetrics = null; - loadMetrics(daemonSet: DaemonSet) { + async loadMetrics(daemonSet: DaemonSet) { const pods = this.getChildPods(daemonSet); - return podsApi.getMetrics(pods, daemonSet.getNs(), "").then(metrics => - this.metrics = metrics - ); + this.metrics = await podsApi.getMetrics(pods, daemonSet.getNs(), ""); } getChildPods(daemonSet: DaemonSet): Pod[] { diff --git a/src/renderer/components/+workloads-deployments/deployments.store.ts b/src/renderer/components/+workloads-deployments/deployments.store.ts index 59acd6c100..07fc095629 100644 --- a/src/renderer/components/+workloads-deployments/deployments.store.ts +++ b/src/renderer/components/+workloads-deployments/deployments.store.ts @@ -16,11 +16,9 @@ export class DeploymentStore extends KubeObjectStore { ], "desc"); } - loadMetrics(deployment: Deployment) { + async loadMetrics(deployment: Deployment) { const pods = this.getChildPods(deployment); - return podsApi.getMetrics(pods, deployment.getNs(), "").then(metrics => - this.metrics = metrics - ); + this.metrics = await podsApi.getMetrics(pods, deployment.getNs(), ""); } getStatuses(deployments?: Deployment[]) { diff --git a/src/renderer/components/+workloads-overview/overview-statuses.tsx b/src/renderer/components/+workloads-overview/overview-statuses.tsx index be770cd39a..ca6ad0d9ba 100644 --- a/src/renderer/components/+workloads-overview/overview-statuses.tsx +++ b/src/renderer/components/+workloads-overview/overview-statuses.tsx @@ -5,72 +5,54 @@ import { observer } from "mobx-react"; import { Trans } from "@lingui/macro"; import { OverviewWorkloadStatus } from "./overview-workload-status"; import { Link } from "react-router-dom"; -import { cronJobsURL, daemonSetsURL, deploymentsURL, jobsURL, podsURL, statefulSetsURL } from "../+workloads"; -import { podsStore } from "../+workloads-pods/pods.store"; -import { deploymentStore } from "../+workloads-deployments/deployments.store"; -import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store"; -import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store"; -import { jobStore } from "../+workloads-jobs/job.store"; -import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; +import { workloadURL, workloadStores } from "../+workloads"; import { namespaceStore } from "../+namespaces/namespace.store"; import { PageFiltersList } from "../item-object-list/page-filters-list"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select"; -import { isAllowedResource } from "../../../common/rbac"; +import { isAllowedResource, KubeResource } from "../../../common/rbac"; +import { ResourceNames } from "../../../renderer/utils/rbac"; +import { autobind } from "../../utils"; +import { _i18n } from "../../i18n"; + +const resources: KubeResource[] = [ + "pods", + "deployments", + "statefulsets", + "daemonsets", + "jobs", + "cronjobs", +] @observer export class OverviewStatuses extends React.Component { + @autobind() + renderWorkload(resource: KubeResource): React.ReactElement { + const store = workloadStores[resource]; + const items = store.getAllByNs(namespaceStore.contextNs) + return ( +
+
+ {ResourceNames[resource]} ({items.length}) +
+ +
+ ) + } + render() { - const { contextNs } = namespaceStore; - const pods = isAllowedResource("pods") ? podsStore.getAllByNs(contextNs) : []; - const deployments = isAllowedResource("deployments") ? deploymentStore.getAllByNs(contextNs) : []; - const statefulSets = isAllowedResource("statefulsets") ? statefulSetStore.getAllByNs(contextNs) : []; - const daemonSets = isAllowedResource("daemonsets") ? daemonSetStore.getAllByNs(contextNs) : []; - const jobs = isAllowedResource("jobs") ? jobStore.getAllByNs(contextNs) : []; - const cronJobs = isAllowedResource("cronjobs") ? cronJobStore.getAllByNs(contextNs) : []; + const workloads = resources + .filter(isAllowedResource) + .map(this.renderWorkload); + return (
Overview
- +
- +
- {isAllowedResource("pods") && -
-
Pods ({pods.length})
- -
- } - {isAllowedResource("deployments") && -
-
Deployments ({deployments.length})
- -
- } - {isAllowedResource("statefulsets") && -
-
StatefulSets ({statefulSets.length})
- -
- } - {isAllowedResource("daemonsets") && -
-
DaemonSets ({daemonSets.length})
- -
- } - {isAllowedResource("jobs") && -
-
Jobs ({jobs.length})
- -
- } - {isAllowedResource("cronjobs") && -
-
CronJobs ({cronJobs.length})
- -
- } + {workloads}
) diff --git a/src/renderer/components/+workloads-replicasets/replicasets.store.ts b/src/renderer/components/+workloads-replicasets/replicasets.store.ts index cf658e36f7..3b91999699 100644 --- a/src/renderer/components/+workloads-replicasets/replicasets.store.ts +++ b/src/renderer/components/+workloads-replicasets/replicasets.store.ts @@ -10,11 +10,9 @@ export class ReplicaSetStore extends KubeObjectStore { api = replicaSetApi @observable metrics: IPodMetrics = null; - loadMetrics(replicaSet: ReplicaSet) { + async loadMetrics(replicaSet: ReplicaSet) { const pods = this.getChildPods(replicaSet); - return podsApi.getMetrics(pods, replicaSet.getNs(), "").then(metrics => - this.metrics = metrics - ); + this.metrics = await podsApi.getMetrics(pods, replicaSet.getNs(), ""); } getChildPods(replicaSet: ReplicaSet) { diff --git a/src/renderer/components/+workloads-statefulsets/statefulset.store.ts b/src/renderer/components/+workloads-statefulsets/statefulset.store.ts index 5119ce6e00..5bca0a19b6 100644 --- a/src/renderer/components/+workloads-statefulsets/statefulset.store.ts +++ b/src/renderer/components/+workloads-statefulsets/statefulset.store.ts @@ -10,11 +10,9 @@ export class StatefulSetStore extends KubeObjectStore { api = statefulSetApi @observable metrics: IPodMetrics = null; - loadMetrics(statefulSet: StatefulSet) { + async loadMetrics(statefulSet: StatefulSet) { const pods = this.getChildPods(statefulSet); - return podsApi.getMetrics(pods, statefulSet.getNs(), "").then(metrics => - this.metrics = metrics - ); + this.metrics = await podsApi.getMetrics(pods, statefulSet.getNs(), ""); } getChildPods(statefulSet: StatefulSet) { diff --git a/src/renderer/components/+workloads/index.ts b/src/renderer/components/+workloads/index.ts index 300747c6af..1f2ae11149 100644 --- a/src/renderer/components/+workloads/index.ts +++ b/src/renderer/components/+workloads/index.ts @@ -1,3 +1,3 @@ export * from "./workloads.route" export * from "./workloads" - +export * from "./workloads.stores" diff --git a/src/renderer/components/+workloads/workloads.route.ts b/src/renderer/components/+workloads/workloads.route.ts index 94a7afd6ba..5586102faf 100644 --- a/src/renderer/components/+workloads/workloads.route.ts +++ b/src/renderer/components/+workloads/workloads.route.ts @@ -1,6 +1,7 @@ import { RouteProps } from "react-router" import { Workloads } from "./workloads"; import { buildURL, IURLParams } from "../../navigation"; +import { KubeResource } from "../../../common/rbac"; export const workloadsRoute: RouteProps = { get path() { @@ -62,3 +63,12 @@ export const daemonSetsURL = buildURL(daemonSetsRoute.pa export const statefulSetsURL = buildURL(statefulSetsRoute.path) export const jobsURL = buildURL(jobsRoute.path) export const cronJobsURL = buildURL(cronJobsRoute.path) + +export const workloadURL: Partial>> = { + "pods": podsURL, + "deployments": deploymentsURL, + "daemonsets": daemonSetsURL, + "statefulsets": statefulSetsURL, + "jobs": jobsURL, + "cronjobs": cronJobsURL, +} diff --git a/src/renderer/components/+workloads/workloads.stores.ts b/src/renderer/components/+workloads/workloads.stores.ts new file mode 100644 index 0000000000..998075140d --- /dev/null +++ b/src/renderer/components/+workloads/workloads.stores.ts @@ -0,0 +1,17 @@ +import { KubeObjectStore } from "../../kube-object.store"; +import { podsStore } from "../+workloads-pods/pods.store"; +import { deploymentStore } from "../+workloads-deployments/deployments.store"; +import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store"; +import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store"; +import { jobStore } from "../+workloads-jobs/job.store"; +import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; +import { KubeResource } from "../../../common/rbac"; + +export const workloadStores: Partial> = { + "pods": podsStore, + "deployments": deploymentStore, + "daemonsets": daemonSetStore, + "statefulsets": statefulSetStore, + "jobs": jobStore, + "cronjobs": cronJobStore, +} diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index 0f647ced6d..16c5f0e9d0 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -19,6 +19,8 @@ export abstract class KubeObjectStore extends ItemSt kubeWatchApi.addListener(this, this.onWatchApiEvent); } + getStatuses?(items: T[]): Record; + getAllByNs(namespace: string | string[], strict = false): T[] { const namespaces: string[] = [].concat(namespace); if (namespaces.length) { diff --git a/src/renderer/utils/rbac.ts b/src/renderer/utils/rbac.ts new file mode 100644 index 0000000000..9f4ba97943 --- /dev/null +++ b/src/renderer/utils/rbac.ts @@ -0,0 +1,28 @@ +import { KubeResource } from "../../common/rbac"; +import { _i18n } from "../i18n"; + +export const ResourceNames: Record = { + "namespaces": _i18n._("Namespaces"), + "nodes": _i18n._("Nodes"), + "events": _i18n._("Events"), + "resourcequotas": _i18n._("Resource Quotas"), + "services": _i18n._("Services"), + "secrets": _i18n._("Secrets"), + "configmaps": _i18n._("Config Maps"), + "ingresses": _i18n._("Ingresses"), + "networkpolicies": _i18n._("Network Policies"), + "persistentvolumes": _i18n._("Persistent Volumes"), + "storageclasses": _i18n._("Storage Classes"), + "pods": _i18n._("Pods"), + "daemonsets": _i18n._("Daemon Sets"), + "deployments": _i18n._("Deployments"), + "statefulsets": _i18n._("Stateful Sets"), + "replicasets": _i18n._("Replica Sets"), + "jobs": _i18n._("Jobs"), + "cronjobs": _i18n._("Cron Jobs"), + "endpoints": _i18n._("Endpoints"), + "customresourcedefinitions": _i18n._("Custom Resource Definitions"), + "horizontalpodautoscalers": _i18n._("Horizontal Pod Autoscalers"), + "podsecuritypolicies": _i18n._("Pod Security Policies"), + "poddisruptionbudgets": _i18n._("Pod Disruption Budgets"), +} From c049918d25543759872d00314c315f0722c4a28b Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 30 Sep 2020 14:02:18 +0300 Subject: [PATCH 22/25] Add support for Docker Enterprise Container Cloud metrics (#998) * Add support for Docker Enterprise Container Cloud metrics Signed-off-by: Lauri Nevala --- src/common/prometheus-providers.ts | 3 +- src/main/prometheus/stacklight.ts | 83 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/main/prometheus/stacklight.ts diff --git a/src/common/prometheus-providers.ts b/src/common/prometheus-providers.ts index 87f081b94e..2c6e6fac8e 100644 --- a/src/common/prometheus-providers.ts +++ b/src/common/prometheus-providers.ts @@ -1,9 +1,10 @@ import { PrometheusLens } from "../main/prometheus/lens"; import { PrometheusHelm } from "../main/prometheus/helm"; import { PrometheusOperator } from "../main/prometheus/operator"; +import { PrometheusStacklight } from "../main/prometheus/stacklight"; import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry"; -[PrometheusLens, PrometheusHelm, PrometheusOperator].forEach(providerClass => { +[PrometheusLens, PrometheusHelm, PrometheusOperator, PrometheusStacklight].forEach(providerClass => { const provider = new providerClass() PrometheusProviderRegistry.registerProvider(provider.id, provider) }); diff --git a/src/main/prometheus/stacklight.ts b/src/main/prometheus/stacklight.ts new file mode 100644 index 0000000000..4cb946c81d --- /dev/null +++ b/src/main/prometheus/stacklight.ts @@ -0,0 +1,83 @@ +import { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry"; +import { CoreV1Api } from "@kubernetes/client-node"; +import logger from "../logger" + +export class PrometheusStacklight implements PrometheusProvider { + id = "stacklight" + name = "Stacklight" + rateAccuracy = "1m" + + public async getPrometheusService(client: CoreV1Api): Promise { + try { + const resp = await client.readNamespacedService("prometheus-server", "stacklight") + const service = resp.body + return { + id: this.id, + namespace: service.metadata.namespace, + service: service.metadata.name, + port: service.spec.ports[0].port + } + } catch(error) { + logger.warn(`PrometheusLens: failed to list services: ${error.response.body.message}`) + } + } + + public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { + switch(opts.category) { + case 'cluster': + return { + memoryUsage: ` + sum( + node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) + ) by (kubernetes_name) + `.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`), + memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`, + memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`, + memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`, + cpuUsage: `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`, + cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`, + cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`, + cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`, + podUsage: `sum(kubelet_running_pod_count{instance=~"${opts.nodes}"})`, + podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`, + fsSize: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`, + fsUsage: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)` + } + case 'nodes': + return { + memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`, + memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`, + cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`, + cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`, + fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`, + fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)` + } + case 'pods': + return { + cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, + cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, + cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, + memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, + memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, + memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, + fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, + networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, + networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})` + } + case 'pvc': + return { + diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`, + diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)` + } + case 'ingress': + const bytesSent = (ingress: string, statuses: string) => + `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)` + return { + bytesSentSuccess: bytesSent(opts.igress, "^2\\\\d*"), + bytesSentFailure: bytesSent(opts.ingres, "^5\\\\d*"), + requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`, + responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)` + } + } + } +} From 8823c6f5f288125d59cb5f4636480e3e979d4f7a Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Wed, 30 Sep 2020 14:08:42 +0300 Subject: [PATCH 23/25] Terminal: set NO_PROXY env for localhost communication (#982) * terminal: set NO_PROXY env for localhost communication Signed-off-by: Jari Kolehmainen Co-authored-by: Lauri Nevala --- src/main/kubectl.ts | 11 +++++++++++ src/main/shell-session.ts | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 1e57e29d5d..672d93cf65 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -283,6 +283,12 @@ export class Kubectl { bashScript += "fi\n" bashScript += `export PATH="${helmPath}:${kubectlPath}:$PATH"\n` bashScript += "export KUBECONFIG=\"$tempkubeconfig\"\n" + + bashScript += "NO_PROXY=\",${NO_PROXY:-localhost},\"\n" + bashScript += "NO_PROXY=\"${NO_PROXY//,localhost,/,}\"\n" + bashScript += "NO_PROXY=\"${NO_PROXY//,127.0.0.1,/,}\"\n" + bashScript += "NO_PROXY=\"localhost,127.0.0.1${NO_PROXY%,}\"\n" + bashScript += "export NO_PROXY\n" bashScript += "unset tempkubeconfig\n" await fsPromises.writeFile(bashScriptPath, bashScript.toString(), { mode: 0o644 }) @@ -308,6 +314,11 @@ export class Kubectl { zshScript += "d=${d/#:/}\n" zshScript += "export PATH=\"$helmpath:$kubectlpath:${d/%:/}\"\n" zshScript += "export KUBECONFIG=\"$tempkubeconfig\"\n" + zshScript += "NO_PROXY=\",${NO_PROXY:-localhost},\"\n" + zshScript += "NO_PROXY=\"${NO_PROXY//,localhost,/,}\"\n" + zshScript += "NO_PROXY=\"${NO_PROXY//,127.0.0.1,/,}\"\n" + zshScript += "NO_PROXY=\"localhost,127.0.0.1${NO_PROXY%,}\"\n" + zshScript += "export NO_PROXY\n" zshScript += "unset tempkubeconfig\n" zshScript += "unset OLD_ZDOTDIR\n" await fsPromises.writeFile(zshScriptPath, zshScript.toString(), { mode: 0o644 }) diff --git a/src/main/shell-session.ts b/src/main/shell-session.ts index 1d9d722f57..962074c803 100644 --- a/src/main/shell-session.ts +++ b/src/main/shell-session.ts @@ -125,6 +125,8 @@ export class ShellSession extends EventEmitter { if (this.preferences.httpsProxy) { env["HTTPS_PROXY"] = this.preferences.httpsProxy } + const no_proxy = ["localhost", "127.0.0.1", env["NO_PROXY"]] + env["NO_PROXY"] = no_proxy.filter(address => !!address).join() if (env.DEBUG) { // do not pass debug option to bash delete env["DEBUG"] } From f596b3c45ce43727ef01f840157b80b24121683a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=A0tiller?= Date: Wed, 30 Sep 2020 13:56:22 +0200 Subject: [PATCH 24/25] Fix CPU/Memory usage metrics when using prometheus operator. (#632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cluster CPU and Memory usage cannot be sum by node as it is rendered as single cluster metrics. Without this changes, the cluster graphs (standard and pie) shows data only from first (or latest) nodes. For pod specific metrics, when we use prometheus operator the `container_` metics are doubled. Restric search to get those have image tag. Signed-off-by: Jakub Štiller Co-authored-by: Jakub Štiller --- src/main/prometheus/operator.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/prometheus/operator.ts b/src/main/prometheus/operator.ts index 37efa64b98..3d335ff554 100644 --- a/src/main/prometheus/operator.ts +++ b/src/main/prometheus/operator.ts @@ -37,19 +37,19 @@ export class PrometheusOperator implements PrometheusProvider { memoryUsage: ` sum( node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) - ) by (node) + ) `.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`), - memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`, - memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`, - memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`, - cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}) by (node)`, - cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`, - cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`, - cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`, + memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"})`, + memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"})`, + memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"})`, + cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`, + cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`, + cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"})`, + cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"})`, podUsage: `sum(kubelet_running_pod_count{node=~"${opts.nodes}"})`, - podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`, - fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}) by (node)`, - fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}) by (node)` + podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"})`, + fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`, + fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})` } case 'nodes': return { @@ -62,10 +62,10 @@ export class PrometheusOperator implements PrometheusProvider { } case 'pods': return { - cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, + cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, - memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, + memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, From 3d0b843891b490661a937ecfbc95941856ba564a Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 30 Sep 2020 16:54:21 +0300 Subject: [PATCH 25/25] Preventing drawer close on secret's click (#1003) Signed-off-by: Alex Andreev --- .../components/+workloads-pods/pod-container-env.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/+workloads-pods/pod-container-env.tsx b/src/renderer/components/+workloads-pods/pod-container-env.tsx index 7da7d7e03d..fb779a9eaa 100644 --- a/src/renderer/components/+workloads-pods/pod-container-env.tsx +++ b/src/renderer/components/+workloads-pods/pod-container-env.tsx @@ -113,7 +113,8 @@ const SecretKey = (props: SecretKeyProps) => { const [loading, setLoading] = useState(false) const [secret, setSecret] = useState() - const showKey = async () => { + const showKey = async (evt: React.MouseEvent) => { + evt.preventDefault() setLoading(true) const secret = await secretsStore.load({ name, namespace }); setLoading(false) @@ -126,7 +127,7 @@ const SecretKey = (props: SecretKeyProps) => { return ( <> - secretKeyRef({name}.{key})  + secretKeyRef({name}.{key})