diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index d8bd28f1e8..d4f90b1b0e 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -337,7 +337,14 @@ export class ClusterStore extends BaseStore { } }); - this.activeCluster = newClusters.has(activeCluster) ? activeCluster : null; + const curActiveCluster = newClusters.get(activeCluster); + + if (!curActiveCluster || curActiveCluster.workspace !== workspaceStore.currentWorkspaceId) { + this.activeCluster = null; + } else { + this.activeCluster = activeCluster; + } + this.clusters.replace(newClusters); this.removedClusters.replace(removedClusters); } diff --git a/src/main/index.ts b/src/main/index.ts index 58c2b29ad2..8e524fa843 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -131,6 +131,7 @@ app event.preventDefault(); // Broadcast "open-url" events to renderer + console.log(`broadcasting: ${rawUrl}`); broadcastMessage(lensProtocolChannel, { rawUrl }); }); diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index 5770429a7e..679c848620 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -10,6 +10,7 @@ import { Router } from "./router"; import { ClusterManager } from "./cluster-manager"; import { ContextHandler } from "./context-handler"; import logger from "./logger"; +import { chunk } from "lodash"; export class LensProxy { protected origin: string; @@ -65,10 +66,11 @@ export class LensProxy { return spdyProxy; } + private static readonly ProxyFilterHeaders = new Set(["Host", "Authorization"]); protected async handleProxyUpgrade(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) { const cluster = this.clusterManager.getClusterForRequest(req); - if (cluster) { + if (cluster?.initialized) { const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, ""); const apiUrl = url.parse(cluster.apiUrl); const pUrl = url.parse(proxyUrl); @@ -79,13 +81,14 @@ export class LensProxy { 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`); + for (const [key, value] of chunk(req.rawHeaders, 2)) { + if (LensProxy.ProxyFilterHeaders.has(key)) { + continue; } + + proxySocket.write(`${key}: ${value}\r\n`); } + proxySocket.write("\r\n"); proxySocket.write(head); }); @@ -182,13 +185,19 @@ export class LensProxy { const cluster = this.clusterManager.getClusterForRequest(req); if (cluster) { - const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler); + if (cluster.initialized) { + const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler); - if (proxyTarget) { - // allow to fetch apis in "clusterId.localhost:port" from "localhost:port" - res.setHeader("Access-Control-Allow-Origin", this.origin); + if (proxyTarget) { + // allow to fetch apis in "clusterId.localhost:port" from "localhost:port" + res.setHeader("Access-Control-Allow-Origin", this.origin); - return proxy.web(req, res, proxyTarget); + return proxy.web(req, res, proxyTarget); + } + } else { + res.statusCode = 404; + + return res.end("Not initialized"); } } this.router.route(cluster, req, res); diff --git a/src/main/protocol-handler/router.ts b/src/main/protocol-handler/router.ts index dedcd2e1e1..dd20c9fac8 100644 --- a/src/main/protocol-handler/router.ts +++ b/src/main/protocol-handler/router.ts @@ -60,19 +60,23 @@ interface ExtensionUrlMatch { export class LensProtocolRouter extends Singleton { private extentionRoutes = new Map>(); private internalRoutes = new Map(); - private missingExtensionHandler?: (name: string) => Promise; + private missingExtensionHandler?: (name: string) => Promise; + + private static readonly LoggingPrefix = "[PROTOCOL ROUTER]"; private static ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}/:${EXTENSION_NAME_MATCH}`; public init() { subscribeToBroadcast(lensProtocolChannel, ((_event, { rawUrl }) => { + console.log(`receiving: ${rawUrl}`); + try { this.route(Url(rawUrl, true)); } catch (error) { if (error instanceof RoutingError) { - logger.error(`[PROTOCOL ROUTER]: ${error}`, { url: error.url }); + logger.error(`${LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url }); } else { - logger.error(`[PROTOCOL ROUTER]: ${error}`, { rawUrl }); + logger.error(`${LensProtocolRouter.LoggingPrefix}: ${error}`, { rawUrl }); } } })); @@ -81,7 +85,7 @@ export class LensProtocolRouter extends Singleton { /** * route */ - public route(url: Url): void { + public async route(url: Url): Promise { if (url.protocol.toLowerCase() !== "lens:") { throw new RoutingError(RoutingErrorType.INVALID_PROTOCOL, url); } @@ -90,8 +94,7 @@ export class LensProtocolRouter extends Singleton { case "internal": return this._route(this.internalRoutes, url); case "extension": - // Possible rejected promise is ignored - this._routeToExtension(url); + return this._routeToExtension(url); default: throw new RoutingError(RoutingErrorType.INVALID_HOST, url); @@ -108,17 +111,23 @@ export class LensProtocolRouter extends Singleton { const { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params; const name = `${publisher}/${partialName}`; - logger.info(`[PROTOCOL ROUTER] Extension ${name} matched`); + logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched`); - const routes = this.extentionRoutes.get(name); + let routes = this.extentionRoutes.get(name); if (!routes) { if (this.missingExtensionHandler) { - await this.missingExtensionHandler(name); + if (!await this.missingExtensionHandler(name)) { + return; + } - // TODO: After installation we can continue to route to the extension.. - // but this is difficult, since the promise resolves before extension installation is complete. - return; + routes = this.extentionRoutes.get(name); + + if (!routes) { + logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but has no routes`); + + return; + } } else { throw new RoutingError(RoutingErrorType.MISSING_EXTENSION, url); } @@ -146,6 +155,8 @@ export class LensProtocolRouter extends Singleton { throw new RoutingError(RoutingErrorType.NO_HANDLER, url); } + logger.info(`${LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); + const [match, handler] = route; delete match.params[EXTENSION_NAME_MATCH]; @@ -174,7 +185,11 @@ export class LensProtocolRouter extends Singleton { this.extentionRoutes.get(id).set(urlSchema, handler); } - public onMissingExtension(handler: (name: string) => Promise) { + /** + * onMissingExtension registers the handler for when an extension is missing + * @param handler If the called handler resolves to true then the routes will be tried again + */ + public onMissingExtension(handler: (name: string) => Promise) { this.missingExtensionHandler = handler; } } diff --git a/src/renderer/api/protocol-endpoints/install-extension.ts b/src/renderer/api/protocol-endpoints/install-extension.ts index 3b787e68f9..cd925b03e9 100644 --- a/src/renderer/api/protocol-endpoints/install-extension.ts +++ b/src/renderer/api/protocol-endpoints/install-extension.ts @@ -1,15 +1,18 @@ import logger from "../../../main/logger"; import { RouteParams } from "../../../main/protocol-handler"; -import { installFromNpm } from "../../components/+extensions"; +import { extensionsURL, installFromNpm } from "../../components/+extensions"; +import { navigate } from "../../navigation"; export async function installExtension(params: RouteParams): Promise { const { name } = params.search; - + if (!name) { return void logger.info("installExtension handler: missing 'name' from search params"); } + try { + navigate(extensionsURL()); await installFromNpm(name); } catch (error) { logger.error("[PH - Install Extension]: failed to install from NPM", error); diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index c04361f6d5..e4d9d99251 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -45,12 +45,15 @@ export async function bootstrap(App: AppComponent) { lensProtocolRouter.init(); lensProtocolRouter.onMissingExtension(async name => { if (!extensionLoader.isInstalled(name)) { - logger.info(`[PROTOCOL ROUTER] Extension ${name} not installed, installing..`); + logger.info(`[PROTOCOL ROUTER]: Extension ${name} not installed, installing..`); - // TODO: This actually resolves before the extension installation is complete - return installFromNpm(name); + await installFromNpm(name); + + return true; } else { - logger.info(`[PROTOCOL ROUTER] Extension already installed, but route is missing.`); + logger.info(`[PROTOCOL ROUTER]: Extension already installed, but route is missing.`); + + return false; } }); protocolEndpoints.registerHandlers(); diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 60d6d2aced..33ef9ed6e9 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -165,7 +165,7 @@ async function requestInstall(init: InstallRequest | InstallRequest[]) { if (!folderExists) { // auto-install extension if not yet exists - unpackExtension(install); + return unpackExtension(install); } else { // If we show the confirmation dialog, we stop the install spinner until user clicks ok // and the install continues @@ -210,7 +210,11 @@ async function unpackExtension({ fileName, tempFile, manifest: { name, version } try { // extract to temp folder first - await fse.remove(unpackingTempFolder).catch(Function); + try { + await fse.remove(unpackingTempFolder); + } catch (err) { + // ignore error + } await fse.ensureDir(unpackingTempFolder); await extractTar(tempFile, { cwd: unpackingTempFolder }); @@ -237,8 +241,12 @@ async function unpackExtension({ fileName, tempFile, manifest: { name, version } } } finally { // clean up - fse.remove(unpackingTempFolder).catch(Function); - fse.unlink(tempFile).catch(Function); + try { + await fse.remove(unpackingTempFolder); + await fse.unlink(tempFile); + } catch (err) { + // ignore error + } } } @@ -352,6 +360,8 @@ async function installFromSelectFileDialog() { export async function installFromNpm(packageName: string) { const tarballUrl = await extensionLoader.getNpmPackageTarballUrl(packageName); + Notifications.info(`Installing ${packageName}`); + return installFromUrlOrPath(tarballUrl); } diff --git a/src/renderer/components/dock/pod-logs.store.ts b/src/renderer/components/dock/pod-logs.store.ts index 057b8eea15..d0cdfd105b 100644 --- a/src/renderer/components/dock/pod-logs.store.ts +++ b/src/renderer/components/dock/pod-logs.store.ts @@ -108,25 +108,28 @@ export class PodLogsStore extends DockTabStore { * @param params request parameters described in IPodLogsQuery interface * @returns {Promise} A fetch request promise */ - loadLogs = async (tabId: TabId, params: Partial) => { + loadLogs = async (tabId: TabId, params: Partial): Promise => { const data = this.getData(tabId); const { selectedContainer, previous } = data; const pod = new Pod(data.pod); const namespace = pod.getNs(); const name = pod.getName(); - return podsApi.getLogs({ namespace, name }, { - ...params, - timestamps: true, // Always setting timestampt to separate old logs from new ones - container: selectedContainer.name, - previous - }).then(result => { - const logs = [...result.split("\n")]; // Transform them into array + try { + const result = await podsApi.getLogs({ namespace, name }, { + ...params, + timestamps: true, // Always setting timestampt to separate old logs from new ones + container: selectedContainer.name, + previous + }); + const logs = result.split(/(\r?\n)+/g); - logs.pop(); // Remove last empty element + logs.pop();// Remove last empty element return logs; - }); + } catch (err) { + // ignore error + } }; /** @@ -145,7 +148,7 @@ export class PodLogsStore extends DockTabStore { * @returns {number} Length of log lines */ @computed - get lines() { + get lines(): number { const id = dockStore.selectedTabId; const logs = this.logs.get(id);