From b67575d6f0c8b54deb76aa547e5941233a98756b Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 11 Sep 2020 13:30:24 +0300 Subject: [PATCH 01/28] Fix proxy kubeconfig file permissions (#857) Signed-off-by: Lauri Nevala --- src/main/kubeconfig-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kubeconfig-manager.ts b/src/main/kubeconfig-manager.ts index 5f75899389..f7d85e3a24 100644 --- a/src/main/kubeconfig-manager.ts +++ b/src/main/kubeconfig-manager.ts @@ -62,7 +62,7 @@ export class KubeconfigManager { // write const configYaml = dumpConfigYaml(proxyConfig); fs.ensureDir(path.dirname(tempFile)); - fs.writeFileSync(tempFile, configYaml); + fs.writeFileSync(tempFile, configYaml, { mode: 0o600 }); this.tempFile = tempFile; logger.debug(`Created temp kubeconfig "${contextName}" at "${tempFile}": \n${configYaml}`); return tempFile; From 77895d40b05f9371229d73f5c81f9c29428e8e56 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 11 Sep 2020 14:11:36 +0300 Subject: [PATCH 02/28] Move verbose log lines to silly level (#859) Signed-off-by: Lauri Nevala --- src/common/base-store.ts | 4 ++-- src/common/cluster-store.ts | 2 +- src/common/ipc.ts | 2 +- src/main/cluster.ts | 2 +- src/main/logger.ts | 6 ++++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 146a81af36..0b77a370b1 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -77,7 +77,7 @@ export class BaseStore extends Singleton { ); if (ipcMain) { const callback = (event: IpcMainEvent, model: T) => { - logger.debug(`[STORE]: SYNC ${this.name} from renderer`, { model }); + logger.silly(`[STORE]: SYNC ${this.name} from renderer`, { model }); this.onSync(model); }; ipcMain.on(this.syncChannel, callback); @@ -85,7 +85,7 @@ export class BaseStore extends Singleton { } if (ipcRenderer) { const callback = (event: IpcRendererEvent, model: T) => { - logger.debug(`[STORE]: SYNC ${this.name} from main`, { model }); + logger.silly(`[STORE]: SYNC ${this.name} from main`, { model }); this.onSync(model); }; ipcRenderer.on(this.syncChannel, callback); diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 4fb4d15aea..a7cd5cc3c5 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -76,7 +76,7 @@ export class ClusterStore extends BaseStore { if (ipcRenderer) { ipcRenderer.on("cluster:state", (event, model: ClusterState) => { this.applyWithoutSync(() => { - logger.debug(`[CLUSTER-STORE]: received push-state at ${location.host}`, model); + logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host}`, model); this.getById(model.id)?.updateModel(model); }) }) diff --git a/src/common/ipc.ts b/src/common/ipc.ts index b6b49aee51..97c4dd05cd 100644 --- a/src/common/ipc.ts +++ b/src/common/ipc.ts @@ -68,7 +68,7 @@ export function broadcastIpc({ channel, frameId, frameOnly, webContentId, filter } views.forEach(webContent => { const type = webContent.getType(); - logger.debug(`[IPC]: broadcasting "${channel}" to ${type}=${webContent.id}`, { args }); + logger.silly(`[IPC]: broadcasting "${channel}" to ${type}=${webContent.id}`, { args }); if (!frameOnly) { webContent.send(channel, ...args); } diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 80bd208a40..a4ba94a75f 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -396,7 +396,7 @@ export class Cluster implements ClusterModel { } pushState = (state = this.getState()): ClusterState => { - logger.debug(`[CLUSTER]: push-state`, state); + logger.silly(`[CLUSTER]: push-state`, state); broadcastIpc({ channel: "cluster:state", frameId: this.frameId, diff --git a/src/main/logger.ts b/src/main/logger.ts index 4068aaacd2..f4e2707c27 100644 --- a/src/main/logger.ts +++ b/src/main/logger.ts @@ -2,14 +2,16 @@ import { app, remote } from "electron"; import winston from "winston" import { isDebugging } from "../common/vars"; +const logLevel = process.env.LOG_LEVEL ? process.env.LOG_LEVEL : isDebugging ? "debug" : "info" + const consoleOptions: winston.transports.ConsoleTransportOptions = { handleExceptions: false, - level: isDebugging ? "debug" : "info", + level: logLevel, } const fileOptions: winston.transports.FileTransportOptions = { handleExceptions: false, - level: isDebugging ? "debug" : "info", + level: logLevel, filename: "lens.log", dirname: (app ?? remote?.app)?.getPath("logs"), maxsize: 16 * 1024, From 9d9867f67404c093619925cf6c65d9ed7051f658 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 11 Sep 2020 14:24:24 +0300 Subject: [PATCH 03/28] Add path to auth proxy url if present in cluster url (#856) Signed-off-by: Lauri Nevala --- src/main/context-handler.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index 32e933d8d4..a3cf6185dd 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -70,7 +70,8 @@ export class ContextHandler { async resolveAuthProxyUrl() { const proxyPort = await this.ensurePort(); - return `http://127.0.0.1:${proxyPort}`; + const path = this.clusterUrl.path !== "/" ? this.clusterUrl.path : "" + return `http://127.0.0.1:${proxyPort}${path}`; } async getApiTarget(isWatchRequest = false): Promise { @@ -88,7 +89,7 @@ export class ContextHandler { protected async newApiTarget(timeout: number): Promise { const proxyUrl = await this.resolveAuthProxyUrl(); return { - target: proxyUrl + this.clusterUrl.path, + target: proxyUrl, changeOrigin: true, timeout: timeout, headers: { From cc973bb9ef2834375811af8ecc7a1aff76374bd4 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 11 Sep 2020 17:02:56 +0300 Subject: [PATCH 04/28] fix isPath message (#860) Signed-off-by: Jari Kolehmainen --- src/renderer/components/input/input.validators.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/input/input.validators.ts b/src/renderer/components/input/input.validators.ts index 4e18468da9..611394134f 100644 --- a/src/renderer/components/input/input.validators.ts +++ b/src/renderer/components/input/input.validators.ts @@ -44,7 +44,7 @@ export const isUrl: Validator = { export const isPath: Validator = { condition: ({ type }) => type === "text", - message: () => _i18n._(t`This field must be a path to an existing file`), + message: () => _i18n._(t`This field must be a valid path`), validate: value => !value || fse.pathExistsSync(value), } @@ -73,4 +73,4 @@ export const accountId: Validator = { export const conditionalValidators = [ isRequired, isEmail, isNumber, isUrl, minLength, maxLength -]; \ No newline at end of file +]; From c6b3738a7530ae267754243d67f2a19437197840 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Sat, 12 Sep 2020 16:35:04 +0300 Subject: [PATCH 05/28] App reload in cluster settings (#858) * Passing extra routes where to find clusterId Signed-off-by: Alex Andreev * Cluster settings refactorings Signed-off-by: Alex Andreev * Clean up Signed-off-by: Alex Andreev --- .../+cluster-settings/cluster-settings.tsx | 11 ++-- .../cluster-manager/cluster-view.route.ts | 6 +-- .../cluster-manager/clusters-menu.tsx | 50 +++++++++++-------- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/renderer/components/+cluster-settings/cluster-settings.tsx b/src/renderer/components/+cluster-settings/cluster-settings.tsx index 085ebdd37a..0cec665538 100644 --- a/src/renderer/components/+cluster-settings/cluster-settings.tsx +++ b/src/renderer/components/+cluster-settings/cluster-settings.tsx @@ -9,11 +9,16 @@ import { General } from "./general"; import { WizardLayout } from "../layout/wizard-layout"; import { ClusterIcon } from "../cluster-icon"; import { Icon } from "../icon"; -import { getMatchedCluster } from "../cluster-manager/cluster-view.route"; import { navigate } from "../../navigation"; +import { IClusterSettingsRouteParams } from "./cluster-settings.route"; +import { clusterStore } from "../../../common/cluster-store"; +import { RouteComponentProps } from "react-router"; + +interface Props extends RouteComponentProps { +} @observer -export class ClusterSettings extends React.Component { +export class ClusterSettings extends React.Component { async componentDidMount() { window.addEventListener('keydown', this.onEscapeKey); } @@ -34,7 +39,7 @@ export class ClusterSettings extends React.Component { } render() { - const cluster = getMatchedCluster(); + const cluster = clusterStore.getById(this.props.match.params.clusterId); if (!cluster) return null; const header = ( <> diff --git a/src/renderer/components/cluster-manager/cluster-view.route.ts b/src/renderer/components/cluster-manager/cluster-view.route.ts index e1fb0d1f54..82e99497d5 100644 --- a/src/renderer/components/cluster-manager/cluster-view.route.ts +++ b/src/renderer/components/cluster-manager/cluster-view.route.ts @@ -3,7 +3,6 @@ import { ipcRenderer } from "electron"; import { matchPath, RouteProps } from "react-router"; import { buildURL, navigation } from "../../navigation"; import { clusterStore } from "../../../common/cluster-store"; -import { clusterSettingsRoute } from "../+cluster-settings/cluster-settings.route"; export interface IClusterViewRouteParams { clusterId: string; @@ -19,10 +18,7 @@ export const clusterViewURL = buildURL(clusterViewRoute export function getMatchedClusterId(): string { const matched = matchPath(navigation.location.pathname, { exact: true, - path: [ - clusterViewRoute.path, - clusterSettingsRoute.path, - ].flat(), + path: clusterViewRoute.path }) if (matched) { return matched.params.clusterId; diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index b8d7a232b6..323c28dd18 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -4,7 +4,6 @@ import React from "react"; import { observer } from "mobx-react"; import { _i18n } from "../../i18n"; import { t, Trans } from "@lingui/macro"; -import type { Cluster } from "../../../main/cluster"; import { userStore } from "../../../common/user-store"; import { ClusterId, clusterStore } from "../../../common/cluster-store"; import { workspaceStore } from "../../../common/workspace-store"; @@ -19,8 +18,9 @@ import { landingURL } from "../+landing-page"; import { Tooltip } from "../tooltip"; import { ConfirmDialog } from "../confirm-dialog"; import { clusterIpc } from "../../../common/cluster-ipc"; -import { clusterViewURL, getMatchedClusterId } from "./cluster-view.route"; +import { clusterViewURL } from "./cluster-view.route"; import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd"; +import type { Cluster } from "../../../main/cluster"; interface Props { className?: IClassName; @@ -35,6 +35,7 @@ export class ClustersMenu extends React.Component { addCluster = () => { navigate(addClusterURL()); + clusterStore.setActive(null); } showContextMenu = (cluster: Cluster) => { @@ -44,6 +45,7 @@ export class ClustersMenu extends React.Component { menu.append(new MenuItem({ label: _i18n._(t`Settings`), click: () => { + clusterStore.setActive(cluster.id); navigate(clusterSettingsURL({ params: { clusterId: cluster.id @@ -57,6 +59,7 @@ export class ClustersMenu extends React.Component { click: async () => { if (clusterStore.isActive(cluster.id)) { navigate(landingURL()); + clusterStore.setActive(null); } await clusterIpc.disconnect.invokeFromRenderer(cluster.id); } @@ -112,26 +115,29 @@ export class ClustersMenu extends React.Component { ref={provided.innerRef} {...provided.droppableProps} > - {clusters.map((cluster, index) => ( - - {(provided: DraggableProvided) => ( -
- this.showCluster(cluster.id)} - onContextMenu={() => this.showContextMenu(cluster)} - /> -
- )} -
- ))} + {clusters.map((cluster, index) => { + const isActive = cluster.id === clusterStore.activeClusterId; + return ( + + {(provided: DraggableProvided) => ( +
+ this.showCluster(cluster.id)} + onContextMenu={() => this.showContextMenu(cluster)} + /> +
+ )} +
+ )} + )} {provided.placeholder} )} From d370805d0135f238dd49dc5fff63a02760a368bb Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Mon, 14 Sep 2020 10:08:30 +0300 Subject: [PATCH 06/28] Release v3.6.0-rc.3 (#864) * v3.6.0-rc.3 Signed-off-by: Lauri Nevala --- package.json | 2 +- static/RELEASE_NOTES.md | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4aa73fdacc..015ad64ecc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "3.6.0-rc.2", + "version": "3.6.0-rc.3", "main": "static/build/main.js", "copyright": "© 2020, Mirantis, Inc.", "license": "MIT", diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md index 64f4c386a1..4f7d9c0389 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,7 +2,17 @@ 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.0-rc.2 (current version) +## 3.6.0-rc.3 (current version) +- Fix Dialog Esc keypress behavior +- Set new workspace name restrictions +- Fix cluster's apiUrl +- Fix: Cluster dashboard not rendered +- Fix proxy kubeconfig file permissions +- Move verbose log lines to silly level +- Add path to auth proxy url if present in cluster url +- Fix path validation message + +## 3.6.0-rc.2 - Refresh input values on cluster change - Update logo - Fix margins in cluster menu From 6246a3034fb6cbec2f9ccd319d55e7ca30986446 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Mon, 14 Sep 2020 13:57:23 +0300 Subject: [PATCH 07/28] v3.6.0 (#870) Signed-off-by: Lauri Nevala --- package.json | 2 +- static/RELEASE_NOTES.md | 66 ++++++++++++++++++----------------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 015ad64ecc..e1b1d07a12 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "3.6.0-rc.3", + "version": "3.6.0", "main": "static/build/main.js", "copyright": "© 2020, Mirantis, Inc.", "license": "MIT", diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md index 4f7d9c0389..41fe32d01e 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,47 +2,15 @@ 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.0-rc.3 (current version) -- Fix Dialog Esc keypress behavior -- Set new workspace name restrictions -- Fix cluster's apiUrl -- Fix: Cluster dashboard not rendered -- Fix proxy kubeconfig file permissions -- Move verbose log lines to silly level -- Add path to auth proxy url if present in cluster url -- Fix path validation message - -## 3.6.0-rc.2 -- Refresh input values on cluster change -- Update logo -- Fix margins in cluster menu - -## 3.6.0-rc.1 +## 3.6.0 (current version) - Allow user to configure directory where Kubectl binaries are downloaded - Allow user to configure path to Kubectl binary, instead of using bundled Kubectl -- Log application logs also to log file -- Restrict file permissions to only the user for pasted kubeconfigs -- Close Preferences and Cluster Setting on Esc keypress +- Allow user to select Kubeconfig from filesystem +- Show the path of the cluster's Kubeconfig in cluster settings +- Store reference to added Kubeconfig files +- Update logo - Update Kubectl versions used with Lens - Update Helm binary version -- Fix: Update CRD api to use preferred version and implement v1 differences -- Fix: Allow to drag and drop cluster icons -- Fix: Wider version select box for Helm chart installation -- Fix: Reload only active dashboard view, not the whole app window -- Fix cluster icon margins -- Fix: Reconnect non-accessible clusters on reconnect -- Fix: Bundle Kubectl and Helm binaries -- Fix: Remove double copyright - -## 3.6.0-beta.2 -- Fix: too narrow sidebar without clusters -- Fix app crash when iterating Events without 'kind' property defined -- Detect non-functional bundled kubectl - -## 3.6.0-beta.1 -- Allow user to select Kubeconfig from filesystem -- Store reference to added Kubeconfig files -- Show the path of the cluster's Kubeconfig in cluster settings - Add support for PodDisruptionBudgets - Add port-forwarding for containers in pod - Add shortcut keys to menu items @@ -52,6 +20,30 @@ Here you can find description of changes we've built into each release. While we - Allow to trigger cronjobs - Show devtools in menu - Open last active cluster as default +- Log application logs also to log file +- Fix Dialog Esc keypress behavior +- Set new workspace name restrictions +- Fix cluster's apiUrl +- Fix: Cluster dashboard not rendered +- Fix app reload in cluster settings +- Fix proxy kubeconfig file permissions +- Move verbose log lines to silly level +- Add path to auth proxy url if present in cluster url +- Fix path validation message +- Fix: Refresh input values on cluster change +- Fix margins in cluster menu +- Restrict file permissions to only the user for pasted kubeconfigs +- Close Preferences and Cluster Setting on Esc keypress +- Fix: Update CRD api to use preferred version and implement v1 differences +- Fix: Allow to drag and drop cluster icons +- Fix: Wider version select box for Helm chart installation +- Fix: Reload only active dashboard view, not the whole app window +- Fix cluster icon margins +- Fix: Reconnect non-accessible clusters on reconnect +- Fix: Remove double copyright +- Fix: too narrow sidebar without clusters +- Fix app crash when iterating Events without 'kind' property defined +- Detect non-functional bundled kubectl - Fix format duration rounding days error - Handle unsupported resources properly after they've been created from editor - Fix CRD api parsing From 1e96c5a2d4ba4bd7e2a58559873160b5b606a871 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 15 Sep 2020 09:07:16 -0400 Subject: [PATCH 08/28] fix placement of the add new cluster number (#876) Signed-off-by: Sebastian Malton Co-authored-by: Sebastian Malton --- src/renderer/components/cluster-manager/clusters-menu.scss | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/cluster-manager/clusters-menu.scss b/src/renderer/components/cluster-manager/clusters-menu.scss index 8bebccdee6..42fb5ec588 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.scss +++ b/src/renderer/components/cluster-manager/clusters-menu.scss @@ -49,12 +49,11 @@ .Badge { $boxSize: 17px; - $offset: -7px; position: absolute; + bottom: 0px; + transform: translateX(-50%) translateY(50%); font-size: $font-size-small; - right: $offset; - bottom: $offset; line-height: $boxSize; min-width: $boxSize; min-height: $boxSize; @@ -67,4 +66,4 @@ pointer-events: none; } } -} \ No newline at end of file +} From ec4d859ef7bbcb792d80dd4b72a4c9b5ba0af165 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 16 Sep 2020 16:11:40 +0300 Subject: [PATCH 09/28] Remove extra refreshEvents polling (#892) Signed-off-by: Lauri Nevala --- src/main/cluster.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/cluster.ts b/src/main/cluster.ts index a4ba94a75f..f1e59aa355 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -111,12 +111,10 @@ export class Cluster implements ClusterModel { protected bindEvents() { logger.info(`[CLUSTER]: bind events`, this.getMeta()); const refreshTimer = setInterval(() => this.online && this.refresh(), 30000); // every 30s - const refreshEventsTimer = setInterval(() => this.online && this.refreshEvents(), 3000); // every 3s this.eventDisposers.push( reaction(this.getState, this.pushState), () => clearInterval(refreshTimer), - () => clearInterval(refreshEventsTimer), ); } From c2afd026cc7a5459514460fdc67f661b6775b5c9 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Wed, 16 Sep 2020 20:54:36 +0300 Subject: [PATCH 10/28] Inject Host header to k8s client requests (#887) Signed-off-by: Jari Kolehmainen --- package.json | 2 + src/main/cluster-manager.ts | 2 +- src/main/cluster.ts | 2 +- src/main/kubeconfig-manager.ts | 8 +++- src/main/lens-proxy.ts | 67 ++++++++++++++++++++------------- yarn.lock | 69 +++++++++++++++++++++++++++++++++- 6 files changed, 118 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index e1b1d07a12..fc793860a4 100644 --- a/package.json +++ b/package.json @@ -204,6 +204,7 @@ "semver": "^7.3.2", "serializr": "^2.0.3", "shell-env": "^3.0.0", + "spdy": "^4.0.2", "tar": "^6.0.2", "tcp-port-used": "^1.0.1", "tempy": "^0.5.0", @@ -248,6 +249,7 @@ "@types/request-promise-native": "^1.0.17", "@types/semver": "^7.2.0", "@types/shelljs": "^0.8.8", + "@types/spdy": "^3.4.4", "@types/tcp-port-used": "^1.0.0", "@types/tempy": "^0.3.0", "@types/terser-webpack-plugin": "^3.0.0", diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index ff5eaae32d..302317e52e 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -44,7 +44,7 @@ export class ClusterManager { // lens-server is connecting to 127.0.0.1:/ if (req.headers.host.startsWith("127.0.0.1")) { const clusterId = req.url.split("/")[1] - const cluster = clusterStore.getById(clusterId) + cluster = clusterStore.getById(clusterId) if (cluster) { // we need to swap path prefix so that request is proxied to kube api req.url = req.url.replace(`/${clusterId}`, apiKubePrefix) diff --git a/src/main/cluster.ts b/src/main/cluster.ts index f1e59aa355..48bf01ee02 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -92,7 +92,7 @@ export class Cluster implements ClusterModel { async init(port: number) { try { this.contextHandler = new ContextHandler(this); - this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler); + this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler, port); this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`; this.initialized = true; logger.info(`[CLUSTER]: "${this.contextName}" init success`, { diff --git a/src/main/kubeconfig-manager.ts b/src/main/kubeconfig-manager.ts index f7d85e3a24..59a47c3fbc 100644 --- a/src/main/kubeconfig-manager.ts +++ b/src/main/kubeconfig-manager.ts @@ -11,7 +11,7 @@ export class KubeconfigManager { protected configDir = app.getPath("temp") protected tempFile: string; - constructor(protected cluster: Cluster, protected contextHandler: ContextHandler) { + constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) { this.init(); } @@ -28,6 +28,10 @@ export class KubeconfigManager { return this.tempFile; } + protected resolveProxyUrl() { + return `http://127.0.0.1:${this.port}/${this.cluster.id}` + } + /** * Creates new "temporary" kubeconfig that point to the kubectl-proxy. * This way any user of the config does not need to know anything about the auth etc. details. @@ -42,7 +46,7 @@ export class KubeconfigManager { clusters: [ { name: contextName, - server: await contextHandler.resolveAuthProxyUrl(), + server: this.resolveProxyUrl(), skipTLSVerify: undefined, } ], diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index 7cc4584750..73abd97dff 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -1,13 +1,14 @@ import net from "net"; import http from "http"; +import spdy from "spdy"; import httpProxy from "http-proxy"; import url from "url"; import * as WebSocket from "ws" +import { apiPrefix, apiKubePrefix } from "../common/vars" import { openShell } from "./node-shell-session"; import { Router } from "./router" import { ClusterManager } from "./cluster-manager" import { ContextHandler } from "./context-handler"; -import { apiKubePrefix } from "../common/vars"; import logger from "./logger" export class LensProxy { @@ -40,37 +41,49 @@ export class LensProxy { protected buildCustomProxy(): http.Server { const proxy = this.createProxy(); - const customProxy = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { - this.handleRequest(proxy, req, res); - }); - customProxy.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { - this.handleWsUpgrade(req, socket, head) - }); - customProxy.on("error", (err) => { + const spdyProxy = spdy.createServer({ + spdy: { + plain: true, + connection: { + autoSpdy31: true + } + } + }, (req: http.IncomingMessage, res: http.ServerResponse) => { + this.handleRequest(proxy, req, res) + }) + spdyProxy.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { + 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() + } + } + }) + spdyProxy.on("error", (err) => { logger.error("proxy error", err) - }); - return customProxy; + }) + return spdyProxy + } + + protected async handleSpdyProxy(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, "") + 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() + } } protected createProxy(): httpProxy { const proxy = httpProxy.createProxyServer(); - proxy.on("proxyRes", (proxyRes, req, res) => { - if (req.method !== "GET") { - return; - } - if (proxyRes.statusCode === 502) { - const cluster = this.clusterManager.getClusterForRequest(req) - const proxyError = cluster?.contextHandler.proxyLastError; - if (proxyError) { - return res.writeHead(502).end(proxyError); - } - } - const reqId = this.getRequestId(req); - if (this.retryCounters.has(reqId)) { - logger.debug(`Resetting proxy retry cache for url: ${reqId}`); - this.retryCounters.delete(reqId) - } - }) proxy.on("error", (error, req, res, target) => { if (this.closed) { return; diff --git a/yarn.lock b/yarn.lock index c7d43c4472..300efc58ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2156,6 +2156,13 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== +"@types/spdy@^3.4.4": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@types/spdy/-/spdy-3.4.4.tgz#3282fd4ad8c4603aa49f7017dd520a08a345b2bc" + integrity sha512-N9LBlbVRRYq6HgYpPkqQc3a9HJ/iEtVZToW6xlTtJiMhmRJ7jJdV7TaZQJw/Ve/1ePUsQiCTDc4JMuzzag94GA== + dependencies: + "@types/node" "*" + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -5922,6 +5929,11 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + handlebars@^4.7.6: version "4.7.6" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" @@ -6108,6 +6120,16 @@ hosted-git-info@^3.0.4: dependencies: lru-cache "^5.1.1" +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -6170,6 +6192,11 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" @@ -8636,6 +8663,11 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + oidc-token-hash@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-3.0.2.tgz#5bd4716cc48ad433f4e4e99276811019b165697e" @@ -9753,7 +9785,7 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.6.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -10284,6 +10316,11 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -10593,6 +10630,29 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + spectron@11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/spectron/-/spectron-11.0.0.tgz#79d785e6b8898638e77b5186711e3910ed4ca09b" @@ -11783,6 +11843,13 @@ watchpack@^1.6.1: chokidar "^3.4.0" watchpack-chokidar2 "^2.0.0" +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" From 09074e764c2b7570f9fbe1789a585c38a0ad143f Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 16 Sep 2020 21:44:08 +0300 Subject: [PATCH 11/28] Fix windows installer when app directory removed manually (#893) * Fix windows installer when app directory removed manually Signed-off-by: Lauri Nevala --- build/installer.nsh | 12 ++++++++++++ package.json | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 build/installer.nsh diff --git a/build/installer.nsh b/build/installer.nsh new file mode 100644 index 0000000000..8ff35b5e4f --- /dev/null +++ b/build/installer.nsh @@ -0,0 +1,12 @@ +!macro customInit + ; Workaround for installer handing when the app directory is removed manually + ${ifNot} ${FileExists} "$INSTDIR" + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\{${UNINSTALL_APP_KEY}}" + ${EndIf} + + ; Workaround for the old-format uninstall registry key (some people report it causes hangups, too) + ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_APP_KEY}" "QuietUninstallString" + StrCmp $0 "" proceed 0 + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_APP_KEY}" + proceed: +!macroend \ No newline at end of file diff --git a/package.json b/package.json index fc793860a4..a87ced4460 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,9 @@ } ] }, + "nsis": { + "include": "build/installer.nsh" + }, "publish": [ { "provider": "github", From 61b1c9c5e9d5866273930f4647446af0fb0eb31c Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 16 Sep 2020 23:26:50 +0300 Subject: [PATCH 12/28] v3.6.1 (#899) Signed-off-by: Lauri Nevala --- package.json | 2 +- static/RELEASE_NOTES.md | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a87ced4460..34bf4e9987 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "3.6.0", + "version": "3.6.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 41fe32d01e..3ba1ed3989 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,7 +2,16 @@ Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights! -## 3.6.0 (current version) +## 3.6.1 (current version) +- Inject Host header to k8s client requests +- Remove extra refreshEvents polling +- Fix windows installer when app directory removed manually + +**Known issues** + +- Kubectl exec command does not work in terminal against clusters that are behind a load balancer and require Host header in request, for example Rancher clusters. + +## 3.6.0 - Allow user to configure directory where Kubectl binaries are downloaded - Allow user to configure path to Kubectl binary, instead of using bundled Kubectl - Allow user to select Kubeconfig from filesystem @@ -64,7 +73,7 @@ Here you can find description of changes we've built into each release. While we ## 3.5.2 - Fix application not opening properly in some cases by catching and logging error from shell sync. -## 3.5.1 (current version) +## 3.5.1 - Fix kubernetes api requests to work with non-"namespaces" pathnames - Fix: Handle invalid metrics responses properly - Fix: Display namespace defined in kubeconfig always in the namespace selector From e5a365d00e8a239e8da3134dfd5136c5953a32c1 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 16 Sep 2020 23:43:03 +0300 Subject: [PATCH 13/28] Fix ws upgrade for terminal session (#900) Signed-off-by: Lauri Nevala --- src/main/lens-proxy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index 73abd97dff..765b3f4d1a 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -52,7 +52,7 @@ export class LensProxy { this.handleRequest(proxy, req, res) }) spdyProxy.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { - if (req.url.startsWith(`${apiPrefix}/?`)) { + if (req.url.startsWith(`${apiPrefix}?`)) { this.handleWsUpgrade(req, socket, head) } else { if (req.headers.upgrade?.startsWith("SPDY")) { From f5d41c645cdb23a8e64fcb8ef40cebdcd648c719 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Thu, 17 Sep 2020 09:26:38 +0300 Subject: [PATCH 14/28] Release v3.6.2 (#901) Signed-off-by: Lauri Nevala --- package.json | 2 +- static/RELEASE_NOTES.md | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 34bf4e9987..534ef7fff9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "3.6.1", + "version": "3.6.2", "main": "static/build/main.js", "copyright": "© 2020, Mirantis, Inc.", "license": "MIT", diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md index 3ba1ed3989..c7a4d29f6f 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,7 +2,14 @@ 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.1 (current version) +## 3.6.2 (current version) +- Fix terminal connection opening + +**Known issues** + +- Kubectl exec command does not work in terminal against clusters that are behind a load balancer and require Host header in request, for example Rancher clusters. + +## 3.6.1 - Inject Host header to k8s client requests - Remove extra refreshEvents polling - Fix windows installer when app directory removed manually From 0fedba5b669472aaa8e73ae3e77ea5a5be45cbda Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Thu, 17 Sep 2020 16:06:11 +0300 Subject: [PATCH 15/28] Fix ingress points rendering if no data available (#906) Signed-off-by: Lauri Nevala --- src/renderer/components/+network-ingresses/ingress-details.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/+network-ingresses/ingress-details.tsx b/src/renderer/components/+network-ingresses/ingress-details.tsx index b7fd0d01d0..e42dafabb7 100644 --- a/src/renderer/components/+network-ingresses/ingress-details.tsx +++ b/src/renderer/components/+network-ingresses/ingress-details.tsx @@ -67,7 +67,7 @@ export class IngressDetails extends React.Component { } renderIngressPoints(ingressPoints: ILoadBalancerIngress[]) { - if (ingressPoints.length === 0) return null + if (!ingressPoints || ingressPoints.length === 0) return null return (
From e84082dc6de77f0fba757dd13d46e0701afb5734 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Fri, 18 Sep 2020 01:55:05 -0400 Subject: [PATCH 16/28] Reduce the main BrowserWindow minimum size (#916) Signed-off-by: Jim Ehrismann --- src/main/window-manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index be5a95e47a..348d5bbd3f 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -23,8 +23,8 @@ export class WindowManager { this.mainView = new BrowserWindow({ x, y, width, height, show: false, - minWidth: 900, - minHeight: 760, + minWidth: 700, // accommodate 800 x 600 display minimum + minHeight: 500, // accommodate 800 x 600 display minimum titleBarStyle: "hidden", backgroundColor: "#1e2124", webPreferences: { From d09a118b0c69ee4adab90eb40f2a1ce25546ef5c Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 18 Sep 2020 02:32:56 -0400 Subject: [PATCH 17/28] Fix integration tests not deselecting dropdown (#917) * fix integration tests not deselecting dropdown Signed-off-by: Sebastian Malton --- integration/specs/app_spec.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/integration/specs/app_spec.ts b/integration/specs/app_spec.ts index c3e4044d3b..c69e24cf64 100644 --- a/integration/specs/app_spec.ts +++ b/integration/specs/app_spec.ts @@ -20,12 +20,13 @@ describe("app start", () => { await app.client.click("div.Select__control") await app.client.waitUntilTextExists("div", "minikube") await app.client.click("div.minikube") + await app.client.click("div.Select__control") await app.client.click("button.primary") } const waitForMinikubeDashboard = async (app: Application) => { await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started") - let windowCount = await app.client.getWindowCount() + await app.client.getWindowCount() await app.client.waitForExist(`iframe[name="minikube"]`) await app.client.frame("minikube") await app.client.waitUntilTextExists("span.link-text", "Cluster") @@ -35,10 +36,8 @@ describe("app start", () => { app = util.setup() await app.start() await app.client.waitUntilWindowLoaded() - let windowCount = await app.client.getWindowCount() - while (windowCount > 1) { // Wait for splash screen to be closed - windowCount = await app.client.getWindowCount() - } + // Wait for splash screen to be closed + while (await app.client.getWindowCount() > 1); await app.client.windowByIndex(0) await app.client.waitUntilWindowLoaded() }, 20000) @@ -48,7 +47,7 @@ describe("app start", () => { }) it('allows to add a cluster', async () => { - const status = spawnSync("minikube status", {shell: true}) + const status = spawnSync("minikube status", { shell: true }) if (status.status !== 0) { console.warn("minikube not running, skipping test") return @@ -61,7 +60,7 @@ describe("app start", () => { }) it('allows to create a pod', async () => { - const status = spawnSync("minikube status", {shell: true}) + const status = spawnSync("minikube status", { shell: true }) if (status.status !== 0) { console.warn("minikube not running, skipping test") return From 9a9f50ab346da4ca923c663b17b52eaa22f27a9a Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 18 Sep 2020 02:33:43 -0400 Subject: [PATCH 18/28] add a zero width space to Badges to fix double click highlighting issue (#914) * add a zero width space to Badges to fix double click highlighting issue Signed-off-by: Sebastian Malton --- src/renderer/components/badge/badge.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/badge/badge.tsx b/src/renderer/components/badge/badge.tsx index b51746a871..eca3c94063 100644 --- a/src/renderer/components/badge/badge.tsx +++ b/src/renderer/components/badge/badge.tsx @@ -13,11 +13,18 @@ interface Props extends React.HTMLAttributes, TooltipDecoratorProps { export class Badge extends React.Component { render() { const { className, label, small, children, ...elemProps } = this.props; - return ( + return <> {label} {children} - ); + { /** + * This is a zero-width-space. It makes there be a word seperation + * between adjacent Badge's because 's are ignored for browers' + * word detection algorithmns use for determining the extent of the + * text to highlight on multi-click sequences. + */} + ​ + } } From 3f8a2b09a0dd8045df3ced898055085c89d5e68b Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 18 Sep 2020 12:33:01 +0300 Subject: [PATCH 19/28] Fix service account's imagePullSecrets rendering if secret is not found (#908) * Fix service account's imagePullSecrets rendering if secret is not found Signed-off-by: Lauri Nevala --- .../service-accounts-details.tsx | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/+user-management-service-accounts/service-accounts-details.tsx b/src/renderer/components/+user-management-service-accounts/service-accounts-details.tsx index cb500469f3..f54fb4632f 100644 --- a/src/renderer/components/+user-management-service-accounts/service-accounts-details.tsx +++ b/src/renderer/components/+user-management-service-accounts/service-accounts-details.tsx @@ -15,6 +15,7 @@ import { getDetailsUrl } from "../../navigation"; import { KubeObjectDetailsProps } from "../kube-object"; import { apiManager } from "../../api/api-manager"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import { Icon } from "../icon"; interface Props extends KubeObjectDetailsProps { } @@ -22,21 +23,25 @@ interface Props extends KubeObjectDetailsProps { @observer export class ServiceAccountsDetails extends React.Component { @observable secrets: Secret[]; + @observable imagePullSecrets: Secret[]; @disposeOnUnmount loadSecrets = autorun(async () => { this.secrets = null; + this.imagePullSecrets = null; const { object: serviceAccount } = this.props; if (!serviceAccount) { return; } const namespace = serviceAccount.getNs(); const secrets = serviceAccount.getSecrets().map(({ name }) => { - const secret = secretsStore.getByName(name, namespace); - if (!secret) return secretsStore.load({ name, namespace }); - return secret; + return secretsStore.load({ name, namespace }); }); this.secrets = await Promise.all(secrets); + const imagePullSecrets = serviceAccount.getImagePullSecrets().map(async({ name }) => { + return secretsStore.load({ name, namespace }).catch(_err => { return this.generateDummySecretObject(name) }); + }); + this.imagePullSecrets = await Promise.all(imagePullSecrets) }) renderSecrets() { @@ -49,15 +54,46 @@ export class ServiceAccountsDetails extends React.Component { ) } + renderImagePullSecrets() { + const { imagePullSecrets } = this; + if (!imagePullSecrets) { + return + } + return this.renderSecretLinks(imagePullSecrets) + } + renderSecretLinks(secrets: Secret[]) { - return secrets.map(secret => { + return secrets.map((secret) => { + if (secret.getId() === null) { + return ( +
+ {secret.getName()} + Secret is not found} + /> +
+ ) + } return ( {secret.getName()} ) - } - ) + }) + } + + generateDummySecretObject(name: string) { + return new Secret({ + apiVersion: "v1", + kind: "Secret", + metadata: { + name: name, + uid: null, + selfLink: null, + resourceVersion: null + } + }) } render() { @@ -69,9 +105,7 @@ export class ServiceAccountsDetails extends React.Component { secret.getNs() == serviceAccount.getNs() && secret.getAnnotations().some(annot => annot == `kubernetes.io/service-account.name: ${serviceAccount.getName()}`) ) - const imagePullSecrets = serviceAccount.getImagePullSecrets().map(({ name }) => - secretsStore.getByName(name, serviceAccount.getNs()) - ) + const imagePullSecrets = serviceAccount.getImagePullSecrets() return (
@@ -83,7 +117,7 @@ export class ServiceAccountsDetails extends React.Component { } {imagePullSecrets.length > 0 && ImagePullSecrets} className="links"> - {this.renderSecretLinks(imagePullSecrets)} + {this.renderImagePullSecrets()} } From bddc6b33e369b620e5f4f005ea543110a333aad9 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 18 Sep 2020 15:36:36 +0300 Subject: [PATCH 20/28] Release v3.6.3 (#925) Signed-off-by: Lauri Nevala --- package.json | 2 +- static/RELEASE_NOTES.md | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 534ef7fff9..1cfb3b2247 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "3.6.2", + "version": "3.6.3", "main": "static/build/main.js", "copyright": "© 2020, Mirantis, Inc.", "license": "MIT", diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md index c7a4d29f6f..4c968e3206 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,7 +2,17 @@ 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.2 (current version) +## 3.6.3 (current version) +- Fix app crash on certain situations when opening ingress details +- Reduce app minimum size to support >= 800 x 600 resolution displays +- Fix app crash when service account has imagePullSecrets defined but the actual secret is missing +- Fix words in labels to be selectable either by hovering or double-clicking + +**Known issues** + +- Kubectl exec command does not work in terminal against clusters that are behind a load balancer and require Host header in request, for example Rancher clusters. + +## 3.6.2 - Fix terminal connection opening **Known issues** From 299eceaee4621a0bfeb663c0a87a400efee31c6d Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Mon, 21 Sep 2020 13:20:50 +0300 Subject: [PATCH 21/28] Optimise cluster activate and refresh (#938) Signed-off-by: Lauri Nevala --- src/common/cluster-ipc.ts | 8 +++++++ src/main/cluster.ts | 15 ++++++++----- src/main/shell-session.ts | 2 +- .../+cluster-settings/cluster-settings.tsx | 22 +++++++++++++++++-- .../cluster-manager/cluster-status.tsx | 6 ++--- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/common/cluster-ipc.ts b/src/common/cluster-ipc.ts index b8f9f5f84b..f48ce0f9c4 100644 --- a/src/common/cluster-ipc.ts +++ b/src/common/cluster-ipc.ts @@ -14,6 +14,14 @@ export const clusterIpc = { }, }), + refresh: createIpcChannel({ + channel: "cluster:refresh", + handle: (clusterId: ClusterId) => { + const cluster = clusterStore.getById(clusterId); + if (cluster) return cluster.refresh(); + }, + }), + disconnect: createIpcChannel({ channel: "cluster:disconnect", handle: (clusterId: ClusterId) => { diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 48bf01ee02..9fc9172296 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -133,7 +133,13 @@ export class Cluster implements ClusterModel { if (this.disconnected || (!init && !this.accessible)) { await this.reconnect(); } - await this.refresh(); + await this.refreshConnectionStatus() + if (this.accessible) { + await this.refreshAllowedResources() + this.ready = true + this.kubeCtl = new Kubectl(this.version) + this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard + } return this.pushState(); } @@ -159,15 +165,14 @@ export class Cluster implements ClusterModel { @action async refresh() { logger.info(`[CLUSTER]: refresh`, this.getMeta()); - await this.refreshConnectionStatus(); // refresh "version", "online", etc. + await this.whenInitialized; + await this.refreshConnectionStatus(); if (this.accessible) { - this.kubeCtl = new Kubectl(this.version) this.distribution = this.detectKubernetesDistribution(this.version) const [features, isAdmin, nodesCount] = await Promise.all([ getFeatures(this), this.isClusterAdmin(), this.getNodeCount(), - this.kubeCtl.ensureKubectl() ]); this.features = features; this.isAdmin = isAdmin; @@ -176,8 +181,8 @@ export class Cluster implements ClusterModel { this.refreshEvents(), this.refreshAllowedResources(), ]); - this.ready = true } + this.pushState(); } @action diff --git a/src/main/shell-session.ts b/src/main/shell-session.ts index 704d97382a..1d9d722f57 100644 --- a/src/main/shell-session.ts +++ b/src/main/shell-session.ts @@ -39,7 +39,7 @@ export class ShellSession extends EventEmitter { public async open() { this.kubectlBinDir = await this.kubectl.binDir() const pathFromPreferences = userStore.preferences.kubectlBinariesPath || Kubectl.bundledKubectlPath - this.kubectlPathDir = userStore.preferences.downloadKubectlBinaries ? await this.kubectl.binDir() : path.dirname(pathFromPreferences) + this.kubectlPathDir = userStore.preferences.downloadKubectlBinaries ? this.kubectlBinDir : path.dirname(pathFromPreferences) this.helmBinDir = helmCli.getBinaryDir() const env = await this.getCachedShellEnv() const shell = env.PTYSHELL diff --git a/src/renderer/components/+cluster-settings/cluster-settings.tsx b/src/renderer/components/+cluster-settings/cluster-settings.tsx index 0cec665538..b284ed9c88 100644 --- a/src/renderer/components/+cluster-settings/cluster-settings.tsx +++ b/src/renderer/components/+cluster-settings/cluster-settings.tsx @@ -1,11 +1,12 @@ import "./cluster-settings.scss"; import React from "react"; -import { observer } from "mobx-react"; +import { observer, disposeOnUnmount } from "mobx-react"; import { Features } from "./features"; import { Removal } from "./removal"; import { Status } from "./status"; import { General } from "./general"; +import { Cluster } from "../../../main/cluster"; import { WizardLayout } from "../layout/wizard-layout"; import { ClusterIcon } from "../cluster-icon"; import { Icon } from "../icon"; @@ -13,14 +14,25 @@ import { navigate } from "../../navigation"; import { IClusterSettingsRouteParams } from "./cluster-settings.route"; import { clusterStore } from "../../../common/cluster-store"; import { RouteComponentProps } from "react-router"; +import { clusterIpc } from "../../../common/cluster-ipc"; +import { autorun } from "mobx"; interface Props extends RouteComponentProps { } @observer export class ClusterSettings extends React.Component { + get cluster(): Cluster { + return clusterStore.getById(this.props.match.params.clusterId); + } + async componentDidMount() { window.addEventListener('keydown', this.onEscapeKey); + disposeOnUnmount(this, + autorun(() => { + this.refreshCluster(); + }) + ) } componentWillUnmount() { @@ -34,12 +46,18 @@ export class ClusterSettings extends React.Component { } } + refreshCluster = () => { + if(this.cluster) { + clusterIpc.refresh.invokeFromRenderer(this.cluster.id); + } + } + close() { navigate("/"); } render() { - const cluster = clusterStore.getById(this.props.match.params.clusterId); + const cluster = this.cluster if (!cluster) return null; const header = ( <> diff --git a/src/renderer/components/cluster-manager/cluster-status.tsx b/src/renderer/components/cluster-manager/cluster-status.tsx index b0fae210dd..846b26a504 100644 --- a/src/renderer/components/cluster-manager/cluster-status.tsx +++ b/src/renderer/components/cluster-manager/cluster-status.tsx @@ -39,7 +39,7 @@ export class ClusterStatus extends React.Component { }); }) if (this.cluster.disconnected) { - await this.refreshCluster(); + await this.activateCluster(); } } @@ -47,13 +47,13 @@ export class ClusterStatus extends React.Component { ipcRenderer.removeAllListeners(`kube-auth:${this.props.clusterId}`); } - refreshCluster = async () => { + activateCluster = async () => { await clusterIpc.activate.invokeFromRenderer(this.props.clusterId); } reconnect = async () => { this.isReconnecting = true; - await this.refreshCluster(); + await this.activateCluster(); this.isReconnecting = false; } From 25aac57a868b90f67216b6422d236b8132f19d3f Mon Sep 17 00:00:00 2001 From: Yangjun Wang Date: Mon, 21 Sep 2020 15:08:44 +0300 Subject: [PATCH 22/28] fix bug - deleted namespace does not get auto unselected (#930) * fix bug - deleted namespace does not get auto unselected Signed-off-by: Yangjun Wang --- src/renderer/components/+namespaces/namespace.store.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/components/+namespaces/namespace.store.ts b/src/renderer/components/+namespaces/namespace.store.ts index 624c823a52..c9f190be97 100644 --- a/src/renderer/components/+namespaces/namespace.store.ts +++ b/src/renderer/components/+namespaces/namespace.store.ts @@ -87,6 +87,11 @@ export class NamespaceStore extends KubeObjectStore { super.reset(); this.contextNs.clear(); } + + async remove(item: Namespace) { + await super.remove(item); + this.contextNs.remove(item.getName()); + } } export const namespaceStore = new NamespaceStore(); From 44190c7321534e52a94911d5eb0d39a0a1744cad Mon Sep 17 00:00:00 2001 From: Yangjun Wang Date: Mon, 21 Sep 2020 16:37:20 +0300 Subject: [PATCH 23/28] Get focus to dock tab content after resize (#935) Signed-off-by: Yangjun Wang Co-authored-by: Yangjun Wang --- src/renderer/components/dock/editor-panel.tsx | 1 + src/renderer/components/dock/terminal.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/renderer/components/dock/editor-panel.tsx b/src/renderer/components/dock/editor-panel.tsx index bf57eb10ca..c52630a89d 100644 --- a/src/renderer/components/dock/editor-panel.tsx +++ b/src/renderer/components/dock/editor-panel.tsx @@ -48,6 +48,7 @@ export class EditorPanel extends React.Component { onResize = () => { this.editor.resize(); + this.editor.focus(); } onCursorPosChange = (pos: Ace.Point) => { diff --git a/src/renderer/components/dock/terminal.ts b/src/renderer/components/dock/terminal.ts index f0685c0255..f02fd478f2 100644 --- a/src/renderer/components/dock/terminal.ts +++ b/src/renderer/components/dock/terminal.ts @@ -152,6 +152,7 @@ export class Terminal { onResize = () => { if (!this.isActive) return; this.fitLazy(); + this.focus(); } onActivate = () => { From 78ab6f016b6ccd6df244c9dc7b3e1706e4266343 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Mon, 21 Sep 2020 16:47:51 +0300 Subject: [PATCH 24/28] Fix background of What's New page with white theme (#940) Signed-off-by: Lauri Nevala --- .../components/+whats-new/whats-new.scss | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/+whats-new/whats-new.scss b/src/renderer/components/+whats-new/whats-new.scss index 9eef62cc13..8aa37fdf66 100644 --- a/src/renderer/components/+whats-new/whats-new.scss +++ b/src/renderer/components/+whats-new/whats-new.scss @@ -1,10 +1,24 @@ .WhatsNew { $spacing: $padding * 2; - background: $mainBackground url(../../components/icon/crane.svg) no-repeat; - background-position: 0 35%; - background-size: 85%; - background-clip: content-box; + &::after { + content: ""; + background: url(../../components/icon/crane.svg) no-repeat; + background-position: 0 35%; + background-size: 85%; + background-clip: content-box; + opacity: .75; + top: 0; + left: 0; + bottom: 0; + right: 0; + position: absolute; + z-index: -1; + + .theme-light & { + opacity: 0.2; + } + } .logo { width: 200px; From 9a10db837e5df23beb33f8b5583c69b7496f317e Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 21 Sep 2020 14:47:44 -0400 Subject: [PATCH 25/28] fix Notifications blocking items not visually under them from being interacted with (#915) * fix Notifications blocking items not visually under them from being interacted with Signed-off-by: Sebastian Malton --- src/renderer/components/dock/info-panel.tsx | 18 +++++++++--------- .../notifications/notifications.scss | 5 +++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/renderer/components/dock/info-panel.tsx b/src/renderer/components/dock/info-panel.tsx index 69e79ad8de..39d998d9d4 100644 --- a/src/renderer/components/dock/info-panel.tsx +++ b/src/renderer/components/dock/info-panel.tsx @@ -62,14 +62,13 @@ export class InfoPanel extends Component { this.error = ""; this.waiting = true; try { - this.result = await this.props.submit().finally(() => { - this.waiting = false; - }); + this.result = await this.props.submit() if (showNotifications) Notifications.ok(this.result); } catch (error) { this.error = error.toString(); if (showNotifications) Notifications.error(this.error); - throw error; + } finally { + this.waiting = false } } @@ -91,12 +90,13 @@ export class InfoPanel extends Component { <> {result && (
- {result} + + {result}
)} {errorInfo && (
- + {errorInfo}
)} @@ -114,9 +114,9 @@ export class InfoPanel extends Component { {controls}
- {waiting ? <> {submittingMessage} : this.renderInfo()} + {waiting ? <> {submittingMessage} : this.renderInfo()}
-