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.removedClusters.replace(removedClusters);
|
||||
}
|
||||
|
||||
@ -131,6 +131,7 @@ app
|
||||
event.preventDefault();
|
||||
|
||||
// Broadcast "open-url" events to renderer
|
||||
console.log(`broadcasting: ${rawUrl}`);
|
||||
broadcastMessage(lensProtocolChannel, { rawUrl });
|
||||
});
|
||||
|
||||
|
||||
@ -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];
|
||||
for (const [key, value] of chunk(req.rawHeaders, 2)) {
|
||||
if (LensProxy.ProxyFilterHeaders.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key !== "Host" && key !== "Authorization") {
|
||||
proxySocket.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}\r\n`);
|
||||
}
|
||||
proxySocket.write(`${key}: ${value}\r\n`);
|
||||
}
|
||||
|
||||
proxySocket.write("\r\n");
|
||||
proxySocket.write(head);
|
||||
});
|
||||
@ -182,6 +185,7 @@ export class LensProxy {
|
||||
const cluster = this.clusterManager.getClusterForRequest(req);
|
||||
|
||||
if (cluster) {
|
||||
if (cluster.initialized) {
|
||||
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
|
||||
|
||||
if (proxyTarget) {
|
||||
@ -190,6 +194,11 @@ export class LensProxy {
|
||||
|
||||
return proxy.web(req, res, proxyTarget);
|
||||
}
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
|
||||
return res.end("Not initialized");
|
||||
}
|
||||
}
|
||||
this.router.route(cluster, req, res);
|
||||
}
|
||||
|
||||
@ -60,19 +60,23 @@ interface ExtensionUrlMatch {
|
||||
export class LensProtocolRouter extends Singleton {
|
||||
private extentionRoutes = new Map<ExtensionId, 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}`;
|
||||
|
||||
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<void> {
|
||||
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);
|
||||
|
||||
// TODO: After installation we can continue to route to the extension..
|
||||
// but this is difficult, since the promise resolves before extension installation is complete.
|
||||
if (!await this.missingExtensionHandler(name)) {
|
||||
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<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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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<void> {
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
navigate(extensionsURL());
|
||||
await installFromNpm(name);
|
||||
} catch (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.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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -108,25 +108,28 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
* @param params request parameters described in IPodLogsQuery interface
|
||||
* @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 { selectedContainer, previous } = data;
|
||||
const pod = new Pod(data.pod);
|
||||
const namespace = pod.getNs();
|
||||
const name = pod.getName();
|
||||
|
||||
return podsApi.getLogs({ namespace, name }, {
|
||||
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
|
||||
}).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;
|
||||
});
|
||||
} catch (err) {
|
||||
// ignore error
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -145,7 +148,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
||||
* @returns {number} Length of log lines
|
||||
*/
|
||||
@computed
|
||||
get lines() {
|
||||
get lines(): number {
|
||||
const id = dockStore.selectedTabId;
|
||||
const logs = this.logs.get(id);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user