mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
add continuation after extension install, add notification of install
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
0a44260cd5
commit
b6a17eb1be
@ -337,7 +337,14 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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.clusters.replace(newClusters);
|
||||||
this.removedClusters.replace(removedClusters);
|
this.removedClusters.replace(removedClusters);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,6 +131,7 @@ app
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// Broadcast "open-url" events to renderer
|
// Broadcast "open-url" events to renderer
|
||||||
|
console.log(`broadcasting: ${rawUrl}`);
|
||||||
broadcastMessage(lensProtocolChannel, { rawUrl });
|
broadcastMessage(lensProtocolChannel, { rawUrl });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { Router } from "./router";
|
|||||||
import { ClusterManager } from "./cluster-manager";
|
import { ClusterManager } from "./cluster-manager";
|
||||||
import { ContextHandler } from "./context-handler";
|
import { ContextHandler } from "./context-handler";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
import { chunk } from "lodash";
|
||||||
|
|
||||||
export class LensProxy {
|
export class LensProxy {
|
||||||
protected origin: string;
|
protected origin: string;
|
||||||
@ -65,10 +66,11 @@ export class LensProxy {
|
|||||||
return spdyProxy;
|
return spdyProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly ProxyFilterHeaders = new Set(["Host", "Authorization"]);
|
||||||
protected async handleProxyUpgrade(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);
|
const cluster = this.clusterManager.getClusterForRequest(req);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster?.initialized) {
|
||||||
const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "");
|
const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "");
|
||||||
const apiUrl = url.parse(cluster.apiUrl);
|
const apiUrl = url.parse(cluster.apiUrl);
|
||||||
const pUrl = url.parse(proxyUrl);
|
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(`${req.method} ${pUrl.path} HTTP/1.1\r\n`);
|
||||||
proxySocket.write(`Host: ${apiUrl.host}\r\n`);
|
proxySocket.write(`Host: ${apiUrl.host}\r\n`);
|
||||||
|
|
||||||
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
for (const [key, value] of chunk(req.rawHeaders, 2)) {
|
||||||
const key = req.rawHeaders[i];
|
if (LensProxy.ProxyFilterHeaders.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (key !== "Host" && key !== "Authorization") {
|
proxySocket.write(`${key}: ${value}\r\n`);
|
||||||
proxySocket.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}\r\n`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proxySocket.write("\r\n");
|
proxySocket.write("\r\n");
|
||||||
proxySocket.write(head);
|
proxySocket.write(head);
|
||||||
});
|
});
|
||||||
@ -182,6 +185,7 @@ export class LensProxy {
|
|||||||
const cluster = this.clusterManager.getClusterForRequest(req);
|
const cluster = this.clusterManager.getClusterForRequest(req);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
|
if (cluster.initialized) {
|
||||||
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
|
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
|
||||||
|
|
||||||
if (proxyTarget) {
|
if (proxyTarget) {
|
||||||
@ -190,6 +194,11 @@ export class LensProxy {
|
|||||||
|
|
||||||
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);
|
this.router.route(cluster, req, res);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,19 +60,23 @@ interface ExtensionUrlMatch {
|
|||||||
export class LensProtocolRouter extends Singleton {
|
export class LensProtocolRouter extends Singleton {
|
||||||
private extentionRoutes = new Map<ExtensionId, Map<string, RouteHandler>>();
|
private extentionRoutes = new Map<ExtensionId, Map<string, RouteHandler>>();
|
||||||
private internalRoutes = new Map<string, RouteHandler>();
|
private internalRoutes = new Map<string, RouteHandler>();
|
||||||
private missingExtensionHandler?: (name: string) => Promise<void>;
|
|
||||||
|
|
||||||
|
private missingExtensionHandler?: (name: string) => Promise<boolean>;
|
||||||
|
|
||||||
|
private static readonly LoggingPrefix = "[PROTOCOL ROUTER]";
|
||||||
private static ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}/:${EXTENSION_NAME_MATCH}`;
|
private static ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}/:${EXTENSION_NAME_MATCH}`;
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
subscribeToBroadcast(lensProtocolChannel, ((_event, { rawUrl }) => {
|
subscribeToBroadcast(lensProtocolChannel, ((_event, { rawUrl }) => {
|
||||||
|
console.log(`receiving: ${rawUrl}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.route(Url(rawUrl, true));
|
this.route(Url(rawUrl, true));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof RoutingError) {
|
if (error instanceof RoutingError) {
|
||||||
logger.error(`[PROTOCOL ROUTER]: ${error}`, { url: error.url });
|
logger.error(`${LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url });
|
||||||
} else {
|
} else {
|
||||||
logger.error(`[PROTOCOL ROUTER]: ${error}`, { rawUrl });
|
logger.error(`${LensProtocolRouter.LoggingPrefix}: ${error}`, { rawUrl });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -81,7 +85,7 @@ export class LensProtocolRouter extends Singleton {
|
|||||||
/**
|
/**
|
||||||
* route
|
* route
|
||||||
*/
|
*/
|
||||||
public route(url: Url): void {
|
public async route(url: Url): Promise<void> {
|
||||||
if (url.protocol.toLowerCase() !== "lens:") {
|
if (url.protocol.toLowerCase() !== "lens:") {
|
||||||
throw new RoutingError(RoutingErrorType.INVALID_PROTOCOL, url);
|
throw new RoutingError(RoutingErrorType.INVALID_PROTOCOL, url);
|
||||||
}
|
}
|
||||||
@ -90,8 +94,7 @@ export class LensProtocolRouter extends Singleton {
|
|||||||
case "internal":
|
case "internal":
|
||||||
return this._route(this.internalRoutes, url);
|
return this._route(this.internalRoutes, url);
|
||||||
case "extension":
|
case "extension":
|
||||||
// Possible rejected promise is ignored
|
return this._routeToExtension(url);
|
||||||
this._routeToExtension(url);
|
|
||||||
default:
|
default:
|
||||||
throw new RoutingError(RoutingErrorType.INVALID_HOST, url);
|
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 { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params;
|
||||||
const name = `${publisher}/${partialName}`;
|
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 (!routes) {
|
||||||
if (this.missingExtensionHandler) {
|
if (this.missingExtensionHandler) {
|
||||||
await this.missingExtensionHandler(name);
|
if (!await this.missingExtensionHandler(name)) {
|
||||||
|
|
||||||
// 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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
routes = this.extentionRoutes.get(name);
|
||||||
|
|
||||||
|
if (!routes) {
|
||||||
|
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but has no routes`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new RoutingError(RoutingErrorType.MISSING_EXTENSION, url);
|
throw new RoutingError(RoutingErrorType.MISSING_EXTENSION, url);
|
||||||
}
|
}
|
||||||
@ -146,6 +155,8 @@ export class LensProtocolRouter extends Singleton {
|
|||||||
throw new RoutingError(RoutingErrorType.NO_HANDLER, url);
|
throw new RoutingError(RoutingErrorType.NO_HANDLER, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(`${LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`);
|
||||||
|
|
||||||
const [match, handler] = route;
|
const [match, handler] = route;
|
||||||
|
|
||||||
delete match.params[EXTENSION_NAME_MATCH];
|
delete match.params[EXTENSION_NAME_MATCH];
|
||||||
@ -174,7 +185,11 @@ export class LensProtocolRouter extends Singleton {
|
|||||||
this.extentionRoutes.get(id).set(urlSchema, handler);
|
this.extentionRoutes.get(id).set(urlSchema, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onMissingExtension(handler: (name: string) => Promise<void>) {
|
/**
|
||||||
|
* 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<boolean>) {
|
||||||
this.missingExtensionHandler = handler;
|
this.missingExtensionHandler = handler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import logger from "../../../main/logger";
|
import logger from "../../../main/logger";
|
||||||
import { RouteParams } from "../../../main/protocol-handler";
|
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<void> {
|
export async function installExtension(params: RouteParams): Promise<void> {
|
||||||
const { name } = params.search;
|
const { name } = params.search;
|
||||||
@ -9,7 +10,9 @@ export async function installExtension(params: RouteParams): Promise<void> {
|
|||||||
return void logger.info("installExtension handler: missing 'name' from search params");
|
return void logger.info("installExtension handler: missing 'name' from search params");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
navigate(extensionsURL());
|
||||||
await installFromNpm(name);
|
await installFromNpm(name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[PH - Install Extension]: failed to install from NPM", error);
|
logger.error("[PH - Install Extension]: failed to install from NPM", error);
|
||||||
|
|||||||
@ -45,12 +45,15 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
lensProtocolRouter.init();
|
lensProtocolRouter.init();
|
||||||
lensProtocolRouter.onMissingExtension(async name => {
|
lensProtocolRouter.onMissingExtension(async name => {
|
||||||
if (!extensionLoader.isInstalled(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
|
await installFromNpm(name);
|
||||||
return installFromNpm(name);
|
|
||||||
|
return true;
|
||||||
} else {
|
} 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();
|
protocolEndpoints.registerHandlers();
|
||||||
|
|||||||
@ -165,7 +165,7 @@ async function requestInstall(init: InstallRequest | InstallRequest[]) {
|
|||||||
|
|
||||||
if (!folderExists) {
|
if (!folderExists) {
|
||||||
// auto-install extension if not yet exists
|
// auto-install extension if not yet exists
|
||||||
unpackExtension(install);
|
return unpackExtension(install);
|
||||||
} else {
|
} else {
|
||||||
// If we show the confirmation dialog, we stop the install spinner until user clicks ok
|
// If we show the confirmation dialog, we stop the install spinner until user clicks ok
|
||||||
// and the install continues
|
// and the install continues
|
||||||
@ -210,7 +210,11 @@ async function unpackExtension({ fileName, tempFile, manifest: { name, version }
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// extract to temp folder first
|
// 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 fse.ensureDir(unpackingTempFolder);
|
||||||
await extractTar(tempFile, { cwd: unpackingTempFolder });
|
await extractTar(tempFile, { cwd: unpackingTempFolder });
|
||||||
|
|
||||||
@ -237,8 +241,12 @@ async function unpackExtension({ fileName, tempFile, manifest: { name, version }
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// clean up
|
// clean up
|
||||||
fse.remove(unpackingTempFolder).catch(Function);
|
try {
|
||||||
fse.unlink(tempFile).catch(Function);
|
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) {
|
export async function installFromNpm(packageName: string) {
|
||||||
const tarballUrl = await extensionLoader.getNpmPackageTarballUrl(packageName);
|
const tarballUrl = await extensionLoader.getNpmPackageTarballUrl(packageName);
|
||||||
|
|
||||||
|
Notifications.info(`Installing ${packageName}`);
|
||||||
|
|
||||||
return installFromUrlOrPath(tarballUrl);
|
return installFromUrlOrPath(tarballUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -108,25 +108,28 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
* @param params request parameters described in IPodLogsQuery interface
|
* @param params request parameters described in IPodLogsQuery interface
|
||||||
* @returns {Promise} A fetch request promise
|
* @returns {Promise} A fetch request promise
|
||||||
*/
|
*/
|
||||||
loadLogs = async (tabId: TabId, params: Partial<IPodLogsQuery>) => {
|
loadLogs = async (tabId: TabId, params: Partial<IPodLogsQuery>): Promise<string[]> => {
|
||||||
const data = this.getData(tabId);
|
const data = this.getData(tabId);
|
||||||
const { selectedContainer, previous } = data;
|
const { selectedContainer, previous } = data;
|
||||||
const pod = new Pod(data.pod);
|
const pod = new Pod(data.pod);
|
||||||
const namespace = pod.getNs();
|
const namespace = pod.getNs();
|
||||||
const name = pod.getName();
|
const name = pod.getName();
|
||||||
|
|
||||||
return podsApi.getLogs({ namespace, name }, {
|
try {
|
||||||
|
const result = await podsApi.getLogs({ namespace, name }, {
|
||||||
...params,
|
...params,
|
||||||
timestamps: true, // Always setting timestampt to separate old logs from new ones
|
timestamps: true, // Always setting timestampt to separate old logs from new ones
|
||||||
container: selectedContainer.name,
|
container: selectedContainer.name,
|
||||||
previous
|
previous
|
||||||
}).then(result => {
|
});
|
||||||
const logs = [...result.split("\n")]; // Transform them into array
|
const logs = result.split(/(\r?\n)+/g);
|
||||||
|
|
||||||
logs.pop(); // Remove last empty element
|
logs.pop();// Remove last empty element
|
||||||
|
|
||||||
return logs;
|
return logs;
|
||||||
});
|
} catch (err) {
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,7 +148,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
* @returns {number} Length of log lines
|
* @returns {number} Length of log lines
|
||||||
*/
|
*/
|
||||||
@computed
|
@computed
|
||||||
get lines() {
|
get lines(): number {
|
||||||
const id = dockStore.selectedTabId;
|
const id = dockStore.selectedTabId;
|
||||||
const logs = this.logs.get(id);
|
const logs = this.logs.get(id);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user