mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into show-entity-kind-on-browse-all
This commit is contained in:
commit
5808b2fba3
@ -282,7 +282,7 @@
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@types/react-select": "^3.0.13",
|
||||
"@types/react-select": "3.1.2",
|
||||
"@types/react-table": "^7.7.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/readable-stream": "^2.3.9",
|
||||
@ -334,7 +334,7 @@
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-mock-extended": "^1.0.10",
|
||||
"make-plural": "^6.2.2",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-loader": "^0.6.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"nodemon": "^2.0.4",
|
||||
@ -349,7 +349,7 @@
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-refresh": "^0.9.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-select": "^3.1.0",
|
||||
"react-select": "3.1.1",
|
||||
"react-select-event": "^5.1.0",
|
||||
"react-table": "^7.7.0",
|
||||
"react-window": "^1.8.5",
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
import { match, matchPath } from "react-router";
|
||||
import { countBy } from "lodash";
|
||||
import { Singleton } from "../utils";
|
||||
import { iter, Singleton } from "../utils";
|
||||
import { pathToRegexp } from "path-to-regexp";
|
||||
import logger from "../../main/logger";
|
||||
import type Url from "url-parse";
|
||||
@ -35,7 +35,8 @@ import type { RouteHandler, RouteParams } from "../../extensions/registries/prot
|
||||
export const ProtocolHandlerIpcPrefix = "protocol-handler";
|
||||
|
||||
export const ProtocolHandlerInternal = `${ProtocolHandlerIpcPrefix}:internal`;
|
||||
export const ProtocolHandlerExtension= `${ProtocolHandlerIpcPrefix}:extension`;
|
||||
export const ProtocolHandlerExtension = `${ProtocolHandlerIpcPrefix}:extension`;
|
||||
export const ProtocolHandlerInvalid = `${ProtocolHandlerIpcPrefix}:invalid`;
|
||||
|
||||
/**
|
||||
* These two names are long and cumbersome by design so as to decrease the chances
|
||||
@ -47,6 +48,34 @@ export const ProtocolHandlerExtension= `${ProtocolHandlerIpcPrefix}:extension`;
|
||||
export const EXTENSION_PUBLISHER_MATCH = "LENS_INTERNAL_EXTENSION_PUBLISHER_MATCH";
|
||||
export const EXTENSION_NAME_MATCH = "LENS_INTERNAL_EXTENSION_NAME_MATCH";
|
||||
|
||||
/**
|
||||
* Returned from routing attempts
|
||||
*/
|
||||
export enum RouteAttempt {
|
||||
/**
|
||||
* A handler was found in the set of registered routes
|
||||
*/
|
||||
MATCHED = "matched",
|
||||
/**
|
||||
* A handler was not found within the set of registered routes
|
||||
*/
|
||||
MISSING = "missing",
|
||||
/**
|
||||
* The extension that was matched in the route was not activated
|
||||
*/
|
||||
MISSING_EXTENSION = "no-extension",
|
||||
}
|
||||
|
||||
export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: RouteAttempt): RouteAttempt {
|
||||
switch (mainAttempt) {
|
||||
case RouteAttempt.MATCHED:
|
||||
return RouteAttempt.MATCHED;
|
||||
case RouteAttempt.MISSING:
|
||||
case RouteAttempt.MISSING_EXTENSION:
|
||||
return rendererAttempt;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class LensProtocolRouter extends Singleton {
|
||||
// Map between path schemas and the handlers
|
||||
protected internalRoutes = new Map<string, RouteHandler>();
|
||||
@ -56,11 +85,12 @@ export abstract class LensProtocolRouter extends Singleton {
|
||||
static readonly ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}(\@[A-Za-z0-9_]+)?/:${EXTENSION_NAME_MATCH}`;
|
||||
|
||||
/**
|
||||
*
|
||||
* Attempts to route the given URL to all internal routes that have been registered
|
||||
* @param url the parsed URL that initiated the `lens://` protocol
|
||||
* @returns true if a route has been found
|
||||
*/
|
||||
protected _routeToInternal(url: Url): void {
|
||||
this._route(Array.from(this.internalRoutes.entries()), url);
|
||||
protected _routeToInternal(url: Url): RouteAttempt {
|
||||
return this._route(this.internalRoutes.entries(), url);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,7 +99,7 @@ export abstract class LensProtocolRouter extends Singleton {
|
||||
* @param routes the array of path schemas, handler pairs to match against
|
||||
* @param url the url (in its current state)
|
||||
*/
|
||||
protected _findMatchingRoute(routes: [string, RouteHandler][], url: Url): null | [match<Record<string, string>>, RouteHandler] {
|
||||
protected _findMatchingRoute(routes: Iterable<[string, RouteHandler]>, url: Url): null | [match<Record<string, string>>, RouteHandler] {
|
||||
const matches: [match<Record<string, string>>, RouteHandler][] = [];
|
||||
|
||||
for (const [schema, handler] of routes) {
|
||||
@ -96,7 +126,7 @@ export abstract class LensProtocolRouter extends Singleton {
|
||||
* @param routes the array of (path schemas, handler) pairs to match against
|
||||
* @param url the url (in its current state)
|
||||
*/
|
||||
protected _route(routes: [string, RouteHandler][], url: Url, extensionName?: string): void {
|
||||
protected _route(routes: Iterable<[string, RouteHandler]>, url: Url, extensionName?: string): RouteAttempt {
|
||||
const route = this._findMatchingRoute(routes, url);
|
||||
|
||||
if (!route) {
|
||||
@ -106,7 +136,9 @@ export abstract class LensProtocolRouter extends Singleton {
|
||||
data.extensionName = extensionName;
|
||||
}
|
||||
|
||||
return void logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data);
|
||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data);
|
||||
|
||||
return RouteAttempt.MISSING;
|
||||
}
|
||||
|
||||
const [match, handler] = route;
|
||||
@ -121,6 +153,8 @@ export abstract class LensProtocolRouter extends Singleton {
|
||||
}
|
||||
|
||||
handler(params);
|
||||
|
||||
return RouteAttempt.MATCHED;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,23 +208,22 @@ export abstract class LensProtocolRouter extends Singleton {
|
||||
* Note: this function modifies its argument, do not reuse
|
||||
* @param url the protocol request URI that was "open"-ed
|
||||
*/
|
||||
protected async _routeToExtension(url: Url): Promise<void> {
|
||||
protected async _routeToExtension(url: Url): Promise<RouteAttempt> {
|
||||
const extension = await this._findMatchingExtensionByName(url);
|
||||
|
||||
if (typeof extension === "string") {
|
||||
// failed to find an extension, it returned its name
|
||||
return;
|
||||
return RouteAttempt.MISSING_EXTENSION;
|
||||
}
|
||||
|
||||
// remove the extension name from the path name so we don't need to match on it anymore
|
||||
url.set("pathname", url.pathname.slice(extension.name.length + 1));
|
||||
|
||||
const handlers = extension
|
||||
.protocolHandlers
|
||||
.map<[string, RouteHandler]>(({ pathSchema, handler }) => [pathSchema, handler]);
|
||||
|
||||
try {
|
||||
this._route(handlers, url, extension.name);
|
||||
const handlers = iter.map(extension.protocolHandlers, ({ pathSchema, handler }) => [pathSchema, handler] as [string, RouteHandler]);
|
||||
|
||||
return this._route(handlers, url, extension.name);
|
||||
} catch (error) {
|
||||
if (error instanceof RoutingError) {
|
||||
error.extensionName = extension.name;
|
||||
|
||||
@ -53,6 +53,7 @@ export * from "../../renderer/components/stepper";
|
||||
export * from "../../renderer/components/wizard";
|
||||
export * from "../../renderer/components/+workloads-pods/pod-details-list";
|
||||
export * from "../../renderer/components/+namespaces/namespace-select";
|
||||
export * from "../../renderer/components/+namespaces/namespace-select-filter";
|
||||
export * from "../../renderer/components/layout/sub-title";
|
||||
export * from "../../renderer/components/input/search-input";
|
||||
export * from "../../renderer/components/chart/bar-chart";
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
import * as tempy from "tempy";
|
||||
import fse from "fs-extra";
|
||||
import * as yaml from "js-yaml";
|
||||
import { promiseExec} from "../promise-exec";
|
||||
import { promiseExec } from "../promise-exec";
|
||||
import { helmCli } from "./helm-cli";
|
||||
import type { Cluster } from "../cluster";
|
||||
import { toCamelCase } from "../../common/utils/camelCase";
|
||||
@ -39,7 +39,7 @@ export async function listReleases(pathToKubeconfig: string, namespace?: string)
|
||||
return output;
|
||||
}
|
||||
output.forEach((release: any, index: number) => {
|
||||
output[index] = toCamelCase(release);
|
||||
output[index] = toCamelCase(release);
|
||||
});
|
||||
|
||||
return output;
|
||||
@ -49,9 +49,9 @@ export async function listReleases(pathToKubeconfig: string, namespace?: string)
|
||||
}
|
||||
|
||||
|
||||
export async function installChart(chart: string, values: any, name: string | undefined, namespace: string, version: string, pathToKubeconfig: string){
|
||||
export async function installChart(chart: string, values: any, name: string | undefined, namespace: string, version: string, pathToKubeconfig: string) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const fileName = tempy.file({name: "values.yaml"});
|
||||
const fileName = tempy.file({ name: "values.yaml" });
|
||||
|
||||
await fse.writeFile(fileName, yaml.safeDump(values));
|
||||
|
||||
@ -81,7 +81,7 @@ export async function installChart(chart: string, values: any, name: string | un
|
||||
|
||||
export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, cluster: Cluster) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const fileName = tempy.file({name: "values.yaml"});
|
||||
const fileName = tempy.file({ name: "values.yaml" });
|
||||
|
||||
await fse.writeFile(fileName, yaml.safeDump(values));
|
||||
|
||||
@ -119,7 +119,7 @@ export async function getRelease(name: string, namespace: string, cluster: Clust
|
||||
export async function deleteRelease(name: string, namespace: string, pathToKubeconfig: string) {
|
||||
try {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||
const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||
|
||||
return stdout;
|
||||
} catch ({ stderr }) {
|
||||
@ -173,8 +173,8 @@ async function getResources(name: string, namespace: string, cluster: Cluster) {
|
||||
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`);
|
||||
|
||||
return stdout;
|
||||
return JSON.parse(stdout).items;
|
||||
} catch {
|
||||
return { stdout: JSON.stringify({ items: [] }) };
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,8 +93,11 @@ if (!app.requestSingleInstanceLock()) {
|
||||
|
||||
for (const arg of process.argv) {
|
||||
if (arg.toLowerCase().startsWith("lens://")) {
|
||||
lprm.route(arg)
|
||||
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg }));
|
||||
try {
|
||||
lprm.route(arg);
|
||||
} catch (error) {
|
||||
logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,8 +107,11 @@ app.on("second-instance", (event, argv) => {
|
||||
|
||||
for (const arg of argv) {
|
||||
if (arg.toLowerCase().startsWith("lens://")) {
|
||||
lprm.route(arg)
|
||||
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg }));
|
||||
try {
|
||||
lprm.route(arg);
|
||||
} catch (error) {
|
||||
logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,9 +258,10 @@ autoUpdater.on("before-quit-for-update", () => blockQuit = false);
|
||||
app.on("will-quit", (event) => {
|
||||
// Quit app on Cmd+Q (MacOS)
|
||||
logger.info("APP:QUIT");
|
||||
appEventBus.emit({name: "app", action: "close"});
|
||||
appEventBus.emit({ name: "app", action: "close" });
|
||||
ClusterManager.getInstance(false)?.stop(); // close cluster connections
|
||||
KubeconfigSyncManager.getInstance(false)?.stopSync();
|
||||
LensProtocolRouterMain.getInstance(false)?.cleanup();
|
||||
cleanup();
|
||||
|
||||
if (blockQuit) {
|
||||
@ -268,10 +275,11 @@ app.on("open-url", (event, rawUrl) => {
|
||||
// lens:// protocol handler
|
||||
event.preventDefault();
|
||||
|
||||
LensProtocolRouterMain
|
||||
.getInstance()
|
||||
.route(rawUrl)
|
||||
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl }));
|
||||
try {
|
||||
LensProtocolRouterMain.getInstance().route(rawUrl);
|
||||
} catch (error) {
|
||||
logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -23,7 +23,7 @@ import * as uuid from "uuid";
|
||||
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
import { ProtocolHandlerExtension, ProtocolHandlerInternal } from "../../../common/protocol-handler";
|
||||
import { noop } from "../../../common/utils";
|
||||
import { delay, noop } from "../../../common/utils";
|
||||
import { LensExtension } from "../../../extensions/main-api";
|
||||
import { ExtensionLoader } from "../../../extensions/extension-loader";
|
||||
import { ExtensionsStore } from "../../../extensions/extensions-store";
|
||||
@ -56,27 +56,27 @@ describe("protocol router tests", () => {
|
||||
LensProtocolRouterMain.resetInstance();
|
||||
});
|
||||
|
||||
it("should throw on non-lens URLS", async () => {
|
||||
it("should throw on non-lens URLS", () => {
|
||||
try {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
|
||||
expect(await lpr.route("https://google.ca")).toBeUndefined();
|
||||
expect(lpr.route("https://google.ca")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw when host not internal or extension", async () => {
|
||||
it("should throw when host not internal or extension", () => {
|
||||
try {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
|
||||
expect(await lpr.route("lens://foobar")).toBeUndefined();
|
||||
expect(lpr.route("lens://foobar")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it.only("should not throw when has valid host", async () => {
|
||||
it("should not throw when has valid host", async () => {
|
||||
const extId = uuid.v4();
|
||||
const ext = new LensExtension({
|
||||
id: extId,
|
||||
@ -102,38 +102,39 @@ describe("protocol router tests", () => {
|
||||
lpr.addInternalHandler("/", noop);
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://app")).toBeUndefined();
|
||||
expect(lpr.route("lens://app")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(throwIfDefined(error)).not.toThrow();
|
||||
}
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined();
|
||||
expect(lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(throwIfDefined(error)).not.toThrow();
|
||||
}
|
||||
|
||||
expect(broadcastMessage).toHaveBeenNthCalledWith(1, ProtocolHandlerInternal, "lens://app/");
|
||||
expect(broadcastMessage).toHaveBeenNthCalledWith(2, ProtocolHandlerExtension, "lens://extension/@mirantis/minikube");
|
||||
await delay(50);
|
||||
expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app/", "matched");
|
||||
expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched");
|
||||
});
|
||||
|
||||
it("should call handler if matches", async () => {
|
||||
it("should call handler if matches", () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called = false;
|
||||
|
||||
lpr.addInternalHandler("/page", () => { called = true; });
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://app/page")).toBeUndefined();
|
||||
expect(lpr.route("lens://app/page")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(throwIfDefined(error)).not.toThrow();
|
||||
}
|
||||
|
||||
expect(called).toBe(true);
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page", "matched");
|
||||
});
|
||||
|
||||
it("should call most exact handler", async () => {
|
||||
it("should call most exact handler", () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called: any = 0;
|
||||
|
||||
@ -141,13 +142,13 @@ describe("protocol router tests", () => {
|
||||
lpr.addInternalHandler("/page/:id", params => { called = params.pathname.id; });
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://app/page/foo")).toBeUndefined();
|
||||
expect(lpr.route("lens://app/page/foo")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(throwIfDefined(error)).not.toThrow();
|
||||
}
|
||||
|
||||
expect(called).toBe("foo");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo", "matched");
|
||||
});
|
||||
|
||||
it("should call most exact handler for an extension", async () => {
|
||||
@ -180,13 +181,14 @@ describe("protocol router tests", () => {
|
||||
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined();
|
||||
expect(lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(throwIfDefined(error)).not.toThrow();
|
||||
}
|
||||
|
||||
await delay(50);
|
||||
expect(called).toBe("foob");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched");
|
||||
});
|
||||
|
||||
it("should work with non-org extensions", async () => {
|
||||
@ -245,13 +247,15 @@ describe("protocol router tests", () => {
|
||||
(ExtensionsStore.getInstance() as any).state.set("icecream", { enabled: true, name: "icecream" });
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://extension/icecream/page")).toBeUndefined();
|
||||
expect(lpr.route("lens://extension/icecream/page")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(throwIfDefined(error)).not.toThrow();
|
||||
}
|
||||
|
||||
await delay(50);
|
||||
|
||||
expect(called).toBe(1);
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched");
|
||||
});
|
||||
|
||||
it("should throw if urlSchema is invalid", () => {
|
||||
@ -260,7 +264,7 @@ describe("protocol router tests", () => {
|
||||
expect(() => lpr.addInternalHandler("/:@", noop)).toThrowError();
|
||||
});
|
||||
|
||||
it("should call most exact handler with 3 found handlers", async () => {
|
||||
it("should call most exact handler with 3 found handlers", () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called: any = 0;
|
||||
|
||||
@ -270,16 +274,16 @@ describe("protocol router tests", () => {
|
||||
lpr.addInternalHandler("/page/bar", () => { called = 4; });
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined();
|
||||
expect(lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(throwIfDefined(error)).not.toThrow();
|
||||
}
|
||||
|
||||
expect(called).toBe(3);
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched");
|
||||
});
|
||||
|
||||
it("should call most exact handler with 2 found handlers", async () => {
|
||||
it("should call most exact handler with 2 found handlers", () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called: any = 0;
|
||||
|
||||
@ -288,12 +292,12 @@ describe("protocol router tests", () => {
|
||||
lpr.addInternalHandler("/page/bar", () => { called = 4; });
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined();
|
||||
expect(lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(throwIfDefined(error)).not.toThrow();
|
||||
}
|
||||
|
||||
expect(called).toBe(1);
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat");
|
||||
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched");
|
||||
});
|
||||
});
|
||||
|
||||
@ -25,6 +25,8 @@ import Url from "url-parse";
|
||||
import type { LensExtension } from "../../extensions/lens-extension";
|
||||
import { broadcastMessage } from "../../common/ipc";
|
||||
import { observable, when, makeObservable } from "mobx";
|
||||
import { ProtocolHandlerInvalid, RouteAttempt } from "../../common/protocol-handler";
|
||||
import { disposer } from "../../common/utils";
|
||||
|
||||
export interface FallbackHandler {
|
||||
(name: string): Promise<boolean>;
|
||||
@ -36,19 +38,25 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
@observable rendererLoaded = false;
|
||||
@observable extensionsLoaded = false;
|
||||
|
||||
protected disposers = disposer();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
public cleanup() {
|
||||
this.disposers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most specific registered handler, if it exists, and invoke it.
|
||||
*
|
||||
* This will send an IPC message to the renderer router to do the same
|
||||
* in the renderer.
|
||||
*/
|
||||
public async route(rawUrl: string): Promise<void> {
|
||||
public route(rawUrl: string) {
|
||||
try {
|
||||
const url = new Url(rawUrl, true);
|
||||
|
||||
@ -60,16 +68,18 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
|
||||
switch (url.host) {
|
||||
case "app":
|
||||
return this._routeToInternal(url);
|
||||
this._routeToInternal(url);
|
||||
break;
|
||||
case "extension":
|
||||
await when(() => this.extensionsLoaded);
|
||||
|
||||
return this._routeToExtension(url);
|
||||
this.disposers.push(when(() => this.extensionsLoaded, () => this._routeToExtension(url)));
|
||||
break;
|
||||
default:
|
||||
throw new proto.RoutingError(proto.RoutingErrorType.INVALID_HOST, url);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
broadcastMessage(ProtocolHandlerInvalid, error.toString(), rawUrl);
|
||||
|
||||
if (error instanceof proto.RoutingError) {
|
||||
logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url });
|
||||
} else {
|
||||
@ -102,17 +112,16 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
return "";
|
||||
}
|
||||
|
||||
protected async _routeToInternal(url: Url): Promise<void> {
|
||||
protected _routeToInternal(url: Url): RouteAttempt {
|
||||
const rawUrl = url.toString(); // for sending to renderer
|
||||
const attempt = super._routeToInternal(url);
|
||||
|
||||
super._routeToInternal(url);
|
||||
this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt)));
|
||||
|
||||
await when(() => this.rendererLoaded);
|
||||
|
||||
return broadcastMessage(proto.ProtocolHandlerInternal, rawUrl);
|
||||
return attempt;
|
||||
}
|
||||
|
||||
protected async _routeToExtension(url: Url): Promise<void> {
|
||||
protected async _routeToExtension(url: Url): Promise<RouteAttempt> {
|
||||
const rawUrl = url.toString(); // for sending to renderer
|
||||
|
||||
/**
|
||||
@ -122,10 +131,11 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||
* Note: this needs to clone the url because _routeToExtension modifies its
|
||||
* argument.
|
||||
*/
|
||||
await super._routeToExtension(new Url(url.toString(), true));
|
||||
await when(() => this.rendererLoaded);
|
||||
const attempt = await super._routeToExtension(new Url(url.toString(), true));
|
||||
|
||||
return broadcastMessage(proto.ProtocolHandlerExtension, rawUrl);
|
||||
this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt)));
|
||||
|
||||
return attempt;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -28,6 +28,7 @@ import type { ItemObject } from "../../item.store";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { JsonApiData } from "../json-api";
|
||||
import { buildURLPositional } from "../../../common/utils/buildUrl";
|
||||
import type { KubeJsonApiData } from "../kube-json-api";
|
||||
|
||||
interface IReleasePayload {
|
||||
name: string;
|
||||
@ -46,7 +47,7 @@ interface IReleasePayload {
|
||||
}
|
||||
|
||||
interface IReleaseRawDetails extends IReleasePayload {
|
||||
resources: string;
|
||||
resources: KubeJsonApiData[];
|
||||
}
|
||||
|
||||
export interface IReleaseDetails extends IReleasePayload {
|
||||
@ -102,10 +103,8 @@ export async function listReleases(namespace?: string): Promise<HelmRelease[]> {
|
||||
|
||||
export async function getRelease(name: string, namespace: string): Promise<IReleaseDetails> {
|
||||
const path = endpoint({ name, namespace });
|
||||
|
||||
const details = await apiBase.get<IReleaseRawDetails>(path);
|
||||
const items: KubeObject[] = JSON.parse(details.resources).items;
|
||||
const resources = items.map(item => KubeObject.create(item));
|
||||
const { resources: rawResources, ...details } = await apiBase.get<IReleaseRawDetails>(path);
|
||||
const resources = rawResources.map(KubeObject.create);
|
||||
|
||||
return {
|
||||
...details,
|
||||
@ -194,7 +193,7 @@ export class HelmRelease implements ItemObject {
|
||||
getChart(withVersion = false) {
|
||||
let chart = this.chart;
|
||||
|
||||
if(!withVersion && this.getVersion() != "" ) {
|
||||
if (!withVersion && this.getVersion() != "") {
|
||||
const search = new RegExp(`-${this.getVersion()}`);
|
||||
|
||||
chart = chart.replace(search, "");
|
||||
|
||||
@ -97,7 +97,7 @@ export class KubeObject<Metadata extends IKubeObjectMetadata = IKubeObjectMetada
|
||||
status?: Status;
|
||||
spec?: Spec;
|
||||
|
||||
static create(data: any) {
|
||||
static create(data: KubeJsonApiData) {
|
||||
return new KubeObject(data);
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ export class KubeObject<Metadata extends IKubeObjectMetadata = IKubeObjectMetada
|
||||
);
|
||||
}
|
||||
|
||||
static isJsonApiDataList<T>(object: unknown, verifyItem:(val: unknown) => val is T): object is KubeJsonApiDataList<T> {
|
||||
static isJsonApiDataList<T>(object: unknown, verifyItem: (val: unknown) => val is T): object is KubeJsonApiDataList<T> {
|
||||
return (
|
||||
isObject(object)
|
||||
&& hasTypedProperty(object, "kind", isString)
|
||||
|
||||
@ -33,7 +33,7 @@ import { namespaceStore } from "./namespace.store";
|
||||
|
||||
import type { SelectOption, SelectProps } from "../select";
|
||||
|
||||
const Placeholder = observer((props: PlaceholderProps<any>) => {
|
||||
const Placeholder = observer((props: PlaceholderProps<any, boolean>) => {
|
||||
const getPlaceholder = (): React.ReactNode => {
|
||||
const namespaces = namespaceStore.contextNamespaces;
|
||||
|
||||
|
||||
@ -30,20 +30,26 @@ import { ClusterStore } from "../../../common/cluster-store";
|
||||
import { requestMain } from "../../../common/ipc";
|
||||
import { clusterActivateHandler } from "../../../common/cluster-ipc";
|
||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { getMatchedClusterId, navigate } from "../../navigation";
|
||||
import { navigate } from "../../navigation";
|
||||
import { catalogURL } from "../+catalog/catalog.route";
|
||||
import type { RouteComponentProps } from "react-router-dom";
|
||||
import type { IClusterViewRouteParams } from "./cluster-view.route";
|
||||
|
||||
interface Props extends RouteComponentProps<IClusterViewRouteParams> {
|
||||
}
|
||||
|
||||
|
||||
@observer
|
||||
export class ClusterView extends React.Component {
|
||||
export class ClusterView extends React.Component<Props> {
|
||||
private store = ClusterStore.getInstance();
|
||||
|
||||
constructor(props: {}) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
get clusterId() {
|
||||
return getMatchedClusterId();
|
||||
@computed get clusterId() {
|
||||
return this.props.match.params.clusterId;
|
||||
}
|
||||
|
||||
@computed get cluster(): Cluster | undefined {
|
||||
@ -71,14 +77,13 @@ export class ClusterView extends React.Component {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
|
||||
reaction(() => this.isReady, (ready) => {
|
||||
if (ready) {
|
||||
refreshViews(this.clusterId); // show cluster's view (iframe)
|
||||
} else if (hasLoadedView(this.clusterId)) {
|
||||
reaction(() => [this.cluster?.ready, this.cluster?.disconnected], (values) => {
|
||||
const disconnected = values[1];
|
||||
|
||||
if (hasLoadedView(this.clusterId) && disconnected) {
|
||||
refreshViews();
|
||||
navigate(catalogURL()); // redirect to catalog when active cluster get disconnected/not available
|
||||
}
|
||||
}, {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@ -87,7 +92,7 @@ export class ClusterView extends React.Component {
|
||||
const { clusterId, cluster, isReady } = this;
|
||||
|
||||
if (cluster && !isReady) {
|
||||
return <ClusterStatus clusterId={clusterId} className="box center"/>;
|
||||
return <ClusterStatus key={clusterId} clusterId={clusterId} className="box center"/>;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -56,8 +56,13 @@ export async function initView(clusterId: ClusterId) {
|
||||
parentElem.appendChild(iframe);
|
||||
|
||||
logger.info(`[LENS-VIEW]: waiting cluster to be ready, clusterId=${clusterId}`);
|
||||
await cluster.whenReady;
|
||||
await autoCleanOnRemove(clusterId, iframe);
|
||||
|
||||
try {
|
||||
await when(() => cluster.ready, { timeout: 5_000 }); // we cannot wait forever because cleanup would be blocked for broken cluster connections
|
||||
logger.info(`[LENS-VIEW]: cluster is ready, clusterId=${clusterId}`);
|
||||
} finally {
|
||||
await autoCleanOnRemove(clusterId, iframe);
|
||||
}
|
||||
}
|
||||
|
||||
export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) {
|
||||
|
||||
@ -37,7 +37,7 @@ export class Notifications extends React.Component {
|
||||
static ok(message: NotificationMessage) {
|
||||
notificationsStore.add({
|
||||
message,
|
||||
timeout: 2500,
|
||||
timeout: 2_500,
|
||||
status: NotificationStatus.OK
|
||||
});
|
||||
}
|
||||
@ -45,12 +45,19 @@ export class Notifications extends React.Component {
|
||||
static error(message: NotificationMessage, customOpts: Partial<Notification> = {}) {
|
||||
notificationsStore.add({
|
||||
message,
|
||||
timeout: 10000,
|
||||
timeout: 10_000,
|
||||
status: NotificationStatus.ERROR,
|
||||
...customOpts
|
||||
});
|
||||
}
|
||||
|
||||
static shortInfo(message: NotificationMessage, customOpts: Partial<Notification> = {}) {
|
||||
this.info(message, {
|
||||
timeout: 5_000,
|
||||
...customOpts
|
||||
});
|
||||
}
|
||||
|
||||
static info(message: NotificationMessage, customOpts: Partial<Notification> = {}) {
|
||||
return notificationsStore.add({
|
||||
status: NotificationStatus.INFO,
|
||||
|
||||
@ -27,7 +27,7 @@ import React, { ReactNode } from "react";
|
||||
import { computed, makeObservable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { boundMethod, cssNames } from "../../utils";
|
||||
import ReactSelect, { ActionMeta, components, Props as ReactSelectProps, Styles } from "react-select";
|
||||
import ReactSelect, { ActionMeta, components, OptionTypeBase, Props as ReactSelectProps, Styles } from "react-select";
|
||||
import Creatable, { CreatableProps } from "react-select/creatable";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
|
||||
@ -43,7 +43,7 @@ export interface SelectOption<T = any> {
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SelectProps<T = any> extends ReactSelectProps<T>, CreatableProps<T> {
|
||||
export interface SelectProps<T = any> extends ReactSelectProps<T, boolean>, CreatableProps<T, boolean> {
|
||||
value?: T;
|
||||
themeName?: "dark" | "light" | "outlined" | "lens";
|
||||
menuClass?: string;
|
||||
@ -69,7 +69,7 @@ export class Select extends React.Component<SelectProps> {
|
||||
return this.props.themeName || ThemeStore.getInstance().activeTheme.type;
|
||||
}
|
||||
|
||||
private styles: Styles = {
|
||||
private styles: Styles<OptionTypeBase, boolean> = {
|
||||
menuPortal: styles => ({
|
||||
...styles,
|
||||
zIndex: "auto"
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { addClusterURL } from "../components/+add-cluster";
|
||||
import { catalogURL } from "../components/+catalog";
|
||||
import { attemptInstallByInfo, extensionsURL } from "../components/+extensions";
|
||||
@ -30,6 +31,7 @@ import { entitySettingsURL } from "../components/+entity-settings";
|
||||
import { catalogEntityRegistry } from "../api/catalog-entity-registry";
|
||||
import { ClusterStore } from "../../common/cluster-store";
|
||||
import { EXTENSION_NAME_MATCH, EXTENSION_PUBLISHER_MATCH, LensProtocolRouter } from "../../common/protocol-handler";
|
||||
import { Notifications } from "../components/notifications";
|
||||
|
||||
export function bindProtocolAddRouteHandlers() {
|
||||
LensProtocolRouterRenderer
|
||||
@ -37,7 +39,16 @@ export function bindProtocolAddRouteHandlers() {
|
||||
.addInternalHandler("/preferences", ({ search: { highlight }}) => {
|
||||
navigate(preferencesURL({ fragment: highlight }));
|
||||
})
|
||||
.addInternalHandler("/", () => {
|
||||
.addInternalHandler("/", ({ tail }) => {
|
||||
if (tail) {
|
||||
Notifications.shortInfo(
|
||||
<p>
|
||||
Unknown Action for <code>lens://app/{tail}</code>.{" "}
|
||||
Are you on the latest version?
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
navigate(catalogURL());
|
||||
})
|
||||
.addInternalHandler("/landing", () => {
|
||||
@ -59,7 +70,11 @@ export function bindProtocolAddRouteHandlers() {
|
||||
if (entity) {
|
||||
navigate(entitySettingsURL({ params: { entityId } }));
|
||||
} else {
|
||||
console.log("[APP-HANDLER]: catalog entity with given ID does not exist", { entityId });
|
||||
Notifications.shortInfo(
|
||||
<p>
|
||||
Unknown catalog entity <code>{entityId}</code>.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
})
|
||||
// Handlers below are deprecated and only kept for backward compact purposes
|
||||
@ -69,7 +84,11 @@ export function bindProtocolAddRouteHandlers() {
|
||||
if (cluster) {
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
} else {
|
||||
console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId });
|
||||
Notifications.shortInfo(
|
||||
<p>
|
||||
Unknown catalog entity <code>{clusterId}</code>.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
})
|
||||
.addInternalHandler("/cluster/:clusterId/settings", ({ pathname: { clusterId } }) => {
|
||||
@ -78,7 +97,11 @@ export function bindProtocolAddRouteHandlers() {
|
||||
if (cluster) {
|
||||
navigate(entitySettingsURL({ params: { entityId: clusterId } }));
|
||||
} else {
|
||||
console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId });
|
||||
Notifications.shortInfo(
|
||||
<p>
|
||||
Unknown catalog entity <code>{clusterId}</code>.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
})
|
||||
.addInternalHandler("/extensions", () => {
|
||||
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import * as proto from "../../common/protocol-handler";
|
||||
import logger from "../../main/logger";
|
||||
import Url from "url-parse";
|
||||
import { boundMethod } from "../utils";
|
||||
|
||||
export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
|
||||
/**
|
||||
* This function is needed to be called early on in the renderers lifetime.
|
||||
*/
|
||||
public init(): void {
|
||||
ipcRenderer
|
||||
.on(proto.ProtocolHandlerInternal, this.ipcInternalHandler)
|
||||
.on(proto.ProtocolHandlerExtension, this.ipcExtensionHandler);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
private ipcInternalHandler(event: Electron.IpcRendererEvent, ...args: any[]): void {
|
||||
if (args.length !== 1) {
|
||||
return void logger.warn(`${proto.LensProtocolRouter.LoggingPrefix}: unexpected number of args`, { args });
|
||||
}
|
||||
|
||||
const [rawUrl] = args;
|
||||
const url = new Url(rawUrl, true);
|
||||
|
||||
this._routeToInternal(url);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
private ipcExtensionHandler(event: Electron.IpcRendererEvent, ...args: any[]): void {
|
||||
if (args.length !== 1) {
|
||||
return void logger.warn(`${proto.LensProtocolRouter.LoggingPrefix}: unexpected number of args`, { args });
|
||||
}
|
||||
|
||||
const [rawUrl] = args;
|
||||
const url = new Url(rawUrl, true);
|
||||
|
||||
this._routeToExtension(url);
|
||||
}
|
||||
}
|
||||
119
src/renderer/protocol-handler/router.tsx
Normal file
119
src/renderer/protocol-handler/router.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { ipcRenderer } from "electron";
|
||||
import * as proto from "../../common/protocol-handler";
|
||||
import Url from "url-parse";
|
||||
import { onCorrect } from "../../common/ipc";
|
||||
import { foldAttemptResults, ProtocolHandlerInvalid, RouteAttempt } from "../../common/protocol-handler";
|
||||
import { Notifications } from "../components/notifications";
|
||||
|
||||
function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] {
|
||||
if (args.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof args[0] !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (args[1]) {
|
||||
case RouteAttempt.MATCHED:
|
||||
case RouteAttempt.MISSING:
|
||||
case RouteAttempt.MISSING_EXTENSION:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
|
||||
/**
|
||||
* This function is needed to be called early on in the renderers lifetime.
|
||||
*/
|
||||
public init(): void {
|
||||
onCorrect({
|
||||
channel: proto.ProtocolHandlerInternal,
|
||||
source: ipcRenderer,
|
||||
verifier: verifyIpcArgs,
|
||||
listener: (event, rawUrl, mainAttemptResult) => {
|
||||
const rendererAttempt = this._routeToInternal(new Url(rawUrl, true));
|
||||
|
||||
if (foldAttemptResults(mainAttemptResult, rendererAttempt) === RouteAttempt.MISSING) {
|
||||
Notifications.shortInfo(
|
||||
<p>
|
||||
Unknown action <code>{rawUrl}</code>.{" "}
|
||||
Are you on the latest version?
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
onCorrect({
|
||||
channel: proto.ProtocolHandlerExtension,
|
||||
source: ipcRenderer,
|
||||
verifier: verifyIpcArgs,
|
||||
listener: async (event, rawUrl, mainAttemptResult) => {
|
||||
const rendererAttempt = await this._routeToExtension(new Url(rawUrl, true));
|
||||
|
||||
switch (foldAttemptResults(mainAttemptResult, rendererAttempt)) {
|
||||
case RouteAttempt.MISSING:
|
||||
Notifications.shortInfo(
|
||||
<p>
|
||||
Unknown action <code>{rawUrl}</code>.{" "}
|
||||
Are you on the latest version of the extension?
|
||||
</p>
|
||||
);
|
||||
break;
|
||||
case RouteAttempt.MISSING_EXTENSION:
|
||||
Notifications.shortInfo(
|
||||
<p>
|
||||
Missing extension for action <code>{rawUrl}</code>.{" "}
|
||||
Not able to find extension in our known list.{" "}
|
||||
Try installing it manually.
|
||||
</p>
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
onCorrect({
|
||||
channel: ProtocolHandlerInvalid,
|
||||
source: ipcRenderer,
|
||||
listener: (event, error, rawUrl) => {
|
||||
Notifications.error((
|
||||
<>
|
||||
<p>
|
||||
Failed to route <code>{rawUrl}</code>.
|
||||
</p>
|
||||
<p>
|
||||
<b>Error:</b> {error}
|
||||
</p>
|
||||
</>
|
||||
));
|
||||
},
|
||||
verifier: (args): args is [string, string] => {
|
||||
return args.length === 2 && typeof args[0] === "string";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
56
yarn.lock
56
yarn.lock
@ -1624,10 +1624,10 @@
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-select@^3.0.13":
|
||||
version "3.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.13.tgz#b1a05eae0f65fb4f899b4db1f89b8420cb9f3656"
|
||||
integrity sha512-JxmSArGgzAOtb37+Jz2+3av8rVmp/3s3DGwlcP+g59/a3owkiuuU4/Jajd+qA32beDPHy4gJR2kkxagPY3j9kg==
|
||||
"@types/react-select@3.1.2":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.1.2.tgz#38627df4b49be9b28f800ed72b35d830369a624b"
|
||||
integrity sha512-ygvR/2FL87R2OLObEWFootYzkvm67LRA+URYEAcBuvKk7IXmdsnIwSGm60cVXGaqkJQHozb2Cy1t94tCYb6rJA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@types/react-dom" "*"
|
||||
@ -9711,14 +9711,13 @@ mini-create-react-context@^0.4.0:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
tiny-warning "^1.0.3"
|
||||
|
||||
mini-css-extract-plugin@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e"
|
||||
integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==
|
||||
mini-css-extract-plugin@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.0.tgz#b4db2525af2624899ed64a23b0016e0036411893"
|
||||
integrity sha512-nPFKI7NSy6uONUo9yn2hIfb9vyYvkFu95qki0e21DQ9uaqNKDP15DGpK0KnV6wDroWxPHtExrdEwx/yDQ8nVRw==
|
||||
dependencies:
|
||||
loader-utils "^1.1.0"
|
||||
normalize-url "1.9.1"
|
||||
schema-utils "^1.0.0"
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
webpack-sources "^1.1.0"
|
||||
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
@ -10266,16 +10265,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
normalize-url@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
|
||||
integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=
|
||||
dependencies:
|
||||
object-assign "^4.0.1"
|
||||
prepend-http "^1.0.0"
|
||||
query-string "^4.1.0"
|
||||
sort-keys "^1.0.0"
|
||||
|
||||
normalize-url@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
|
||||
@ -11593,7 +11582,7 @@ prelude-ls@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
|
||||
|
||||
prepend-http@^1.0.0, prepend-http@^1.0.1:
|
||||
prepend-http@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||
@ -11876,14 +11865,6 @@ qs@~6.5.2:
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
query-string@^4.1.0:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
|
||||
integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s=
|
||||
dependencies:
|
||||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
query-string@^5.0.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
|
||||
@ -12078,10 +12059,10 @@ react-select-event@^5.1.0:
|
||||
dependencies:
|
||||
"@testing-library/dom" ">=7"
|
||||
|
||||
react-select@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"
|
||||
integrity sha512-wBFVblBH1iuCBprtpyGtd1dGMadsG36W5/t2Aj8OE6WbByDg5jIFyT7X5gT+l0qmT5TqWhxX+VsKJvCEl2uL9g==
|
||||
react-select@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.1.tgz#156a5b4a6c22b1e3d62a919cb1fd827adb4060bc"
|
||||
integrity sha512-HjC6jT2BhUxbIbxMZWqVcDibrEpdUJCfGicN0MMV+BQyKtCaPTgFekKWiOizSCy4jdsLMGjLqcFGJMhVGWB0Dg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@emotion/cache" "^10.0.9"
|
||||
@ -13244,13 +13225,6 @@ socks@~2.3.2:
|
||||
ip "1.1.5"
|
||||
smart-buffer "^4.1.0"
|
||||
|
||||
sort-keys@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
|
||||
integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0=
|
||||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
sort-keys@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user