1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into lens-k8s-proxy-tls

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2022-03-14 15:55:21 +02:00
commit a3b7ebcfc1
54 changed files with 1917 additions and 1020 deletions

View File

@ -265,7 +265,7 @@
"url-parse": "^1.5.10",
"uuid": "^8.3.2",
"win-ca": "^3.4.5",
"winston": "^3.3.3",
"winston": "^3.6.0",
"winston-console-format": "^1.0.8",
"winston-transport-browserconsole": "^1.0.5",
"ws": "^7.5.5"
@ -276,7 +276,7 @@
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@sentry/types": "^6.14.1",
"@sentry/types": "^6.18.2",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^13.5.0",
@ -326,25 +326,26 @@
"@types/tcp-port-used": "^1.0.0",
"@types/tempy": "^0.3.0",
"@types/triple-beam": "^1.3.2",
"@types/url-parse": "^1.4.5",
"@types/url-parse": "^1.4.8",
"@types/uuid": "^8.3.3",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-server": "^4.7.2",
"@types/webpack-env": "^1.16.3",
"@types/webpack-node-externals": "^2.5.3",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.10.1",
"ansi_up": "^5.1.0",
"mock-http": "^1.1.0",
"chart.js": "^2.9.4",
"circular-dependency-plugin": "^5.2.2",
"cli-progress": "^3.10.0",
"color": "^3.2.1",
"concurrently": "^7.0.0",
"css-loader": "^6.5.1",
"css-loader": "^6.7.1",
"deepdash": "^5.3.9",
"dompurify": "^2.3.4",
"electron": "^14.2.4",
"electron-builder": "^22.14.5",
"electron-builder": "^22.14.13",
"electron-notarize": "^0.3.0",
"esbuild": "^0.13.15",
"esbuild-loader": "^2.18.0",
@ -379,11 +380,11 @@
"react-refresh-typescript": "^2.0.3",
"react-router-dom": "^5.3.0",
"react-select": "3.2.0",
"react-select-event": "^5.1.0",
"react-select-event": "^5.3.0",
"react-table": "^7.7.0",
"react-window": "^1.8.6",
"sass": "^1.45.1",
"sass-loader": "^12.4.0",
"sass-loader": "^12.6.0",
"sharp": "^0.29.3",
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.7",
@ -391,7 +392,7 @@
"ts-jest": "26.5.6",
"ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"type-fest": "^1.0.2",
"type-fest": "^1.4.0",
"typed-emitter": "^1.4.0",
"typedoc": "0.22.10",
"typedoc-plugin-markdown": "^3.11.12",

View File

@ -89,8 +89,8 @@ describe("cluster-store", () => {
mainDi = dis.mainDi;
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
mainDi.override(createKubeAuthProxyCertFilesInjectable, () => ({}));
mainDi.override(kubeAuthProxyCaInjectable, () => ({}));
mainDi.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
mainDi.override(kubeAuthProxyCaInjectable, () => ({} as any));
await dis.runSetups();

View File

@ -7,7 +7,7 @@
import moment from "moment";
import { apiBase } from "../index";
import type { IMetricsQuery } from "../../../main/routes/metrics-route";
import type { IMetricsQuery } from "../../../main/routes/metrics/metrics-query";
export interface IMetrics {
status: string;

View File

@ -82,8 +82,8 @@ describe("ContextHandler", () => {
"tmp": {},
});
di.override(createKubeAuthProxyInjectable, () => ({}));
di.override(kubeAuthProxyCaInjectable, () => ({}));
di.override(createKubeAuthProxyInjectable, () => ({} as any));
di.override(kubeAuthProxyCaInjectable, () => ({} as any));
await di.runSetups();

View File

@ -96,8 +96,8 @@ describe("kube auth proxy tests", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(spawnInjectable, () => mockSpawn);
di.override(createKubeAuthProxyCertFilesInjectable, () => ({}));
di.override(kubeAuthProxyCaInjectable, () => ({}));
di.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
di.override(kubeAuthProxyCaInjectable, () => ({} as any));
mockFs(mockMinikubeConfig);

View File

@ -1,63 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { Router } from "../router";
jest.mock("electron", () => ({
app: {
getVersion: () => "99.99.99",
getName: () => "lens",
setName: jest.fn(),
setPath: jest.fn(),
getPath: () => "tmp",
getLocale: () => "en",
setLoginItemSettings: jest.fn(),
},
ipcMain: {
on: jest.fn(),
handle: jest.fn(),
},
}));
describe("Router", () => {
it("blocks path traversal attacks", async () => {
const response: any = {
statusCode: 200,
end: jest.fn(),
};
await (Router as any).handleStaticFile({
params: {
path: "../index.ts",
},
response,
raw: {},
});
expect(response.statusCode).toEqual(404);
});
it("serves files under static root", async () => {
const response: any = {
statusCode: 200,
write: jest.fn(),
setHeader: jest.fn(),
end: jest.fn(),
};
const req: any = {
url: "",
};
await (Router as any).handleStaticFile({
params: {
path: "router.test.ts",
},
response,
raw: { req },
});
expect(response.statusCode).toEqual(200);
});
});

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiRequest, Route } from "../router/router";
import staticFileRouteInjectable from "../routes/static-file-route.injectable";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
jest.mock("electron", () => ({
app: {
getVersion: () => "99.99.99",
getName: () => "lens",
setName: jest.fn(),
setPath: jest.fn(),
getPath: () => "tmp",
getLocale: () => "en",
setLoginItemSettings: jest.fn(),
},
ipcMain: {
on: jest.fn(),
handle: jest.fn(),
},
}));
describe("static-file-route", () => {
let handleStaticFileRoute: Route<Buffer>;
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
await di.runSetups();
handleStaticFileRoute = di.inject(staticFileRouteInjectable);
});
it("blocks path traversal attacks", async () => {
const request = {
params: {
path: "../index.ts",
},
raw: {},
} as LensApiRequest<any>;
const result = await handleStaticFileRoute.handler(request);
expect(result).toEqual({ statusCode: 404 });
});
it("serves files under static root", async () => {
const req: any = {
url: "",
};
const request = {
params: {
path: "router.test.ts",
},
raw: { req },
} as LensApiRequest<any>;
const result = await handleStaticFileRoute.handler(request);
expect(result).toEqual({ statusCode: 404 });
});
});

View File

@ -43,7 +43,7 @@ describe("kubeconfig-sync.source tests", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(kubeAuthProxyCaInjectable, () => Promise.resolve(Buffer.from("ca")));
di.override(createKubeAuthProxyCertFilesInjectable, () => ({}));
di.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
mockFs();

View File

@ -14,9 +14,9 @@ import appNameInjectable from "./app-paths/app-name/app-name.injectable";
import registerChannelInjectable from "./app-paths/register-channel/register-channel.injectable";
import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
import readFileInjectable from "../common/fs/read-file.injectable";
import directoryForBundledBinariesInjectable from "../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable";
import spawnInjectable from "./child-process/spawn.injectable";
import readFileInjectable from "../common/fs/read-file.injectable";
export const getDiForUnitTesting = (
{ doGeneralOverrides } = { doGeneralOverrides: false },
@ -64,7 +64,7 @@ export const getDiForUnitTesting = (
});
di.override(readFileInjectable, () => () => {
throw new Error("Tried to read a file from file system without specifying explicit override.");
throw new Error("Tried to read file from file system without specifying explicit override.");
});
}

View File

@ -90,9 +90,11 @@ export async function upgradeRelease(name: string, chart: string, values: any, n
];
try {
const output = await execHelm(args);
return {
log: await execHelm(args),
release: getRelease(name, namespace, kubeconfigPath, kubectlPath),
log: output,
release: await getRelease(name, namespace, kubeconfigPath, kubectlPath),
};
} finally {
await fse.unlink(valuesFilePath);

View File

@ -8,7 +8,7 @@ import type http from "http";
import spdy from "spdy";
import type httpProxy from "http-proxy";
import { apiPrefix, apiKubePrefix } from "../common/vars";
import type { Router } from "./router";
import type { Router } from "./router/router";
import type { ContextHandler } from "./context-handler/context-handler";
import logger from "./logger";
import { Singleton } from "../common/utils";
@ -84,7 +84,7 @@ export class LensProxy extends Singleton {
if (!cluster) {
logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
return socket.destroy();
}

View File

@ -1,197 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import Call from "@hapi/call";
import Subtext from "@hapi/subtext";
import type http from "http";
import type httpProxy from "http-proxy";
import path from "path";
import { readFile } from "fs-extra";
import type { Cluster } from "../common/cluster/cluster";
import { apiPrefix, appName, publicPath } from "../common/vars";
import { HelmApiRoute, KubeconfigRoute, MetricsRoute, PortForwardRoute, ResourceApplierApiRoute, VersionRoute } from "./routes";
import logger from "./logger";
export interface RouterRequestOpts {
req: http.IncomingMessage;
res: http.ServerResponse;
cluster: Cluster;
params: RouteParams;
url: URL;
}
export interface RouteParams extends Record<string, string> {
path?: string; // *-route
namespace?: string;
service?: string;
account?: string;
release?: string;
repo?: string;
chart?: string;
}
export interface LensApiRequest<P = any> {
path: string;
payload: P;
params: RouteParams;
cluster: Cluster;
response: http.ServerResponse;
query: URLSearchParams;
raw: {
req: http.IncomingMessage;
res: http.ServerResponse;
};
}
function getMimeType(filename: string) {
const mimeTypes: Record<string, string> = {
html: "text/html",
txt: "text/plain",
css: "text/css",
gif: "image/gif",
jpg: "image/jpeg",
png: "image/png",
svg: "image/svg+xml",
js: "application/javascript",
woff2: "font/woff2",
ttf: "font/ttf",
};
return mimeTypes[path.extname(filename).slice(1)] || "text/plain";
}
interface Dependencies {
routePortForward: (request: LensApiRequest) => Promise<void>;
httpProxy?: httpProxy;
}
export class Router {
protected router = new Call.Router();
protected static rootPath = path.resolve(__static);
public constructor(private dependencies: Dependencies) {
this.addRoutes();
}
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
const url = new URL(req.url, "http://localhost");
const path = url.pathname;
const method = req.method.toLowerCase();
const matchingRoute = this.router.route(method, path);
const routeFound = !matchingRoute.isBoom;
if (routeFound) {
const request = await this.getRequest({ req, res, cluster, url, params: matchingRoute.params });
await matchingRoute.route(request);
return true;
}
return false;
}
protected async getRequest(opts: RouterRequestOpts): Promise<LensApiRequest> {
const { req, res, url, cluster, params } = opts;
const { payload } = await Subtext.parse(req, null, {
parse: true,
output: "data",
});
return {
cluster,
path: url.pathname,
raw: {
req, res,
},
response: res,
query: url.searchParams,
payload,
params,
};
}
protected static async handleStaticFile({ params, response }: LensApiRequest): Promise<void> {
let filePath = params.path;
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
const asset = path.join(Router.rootPath, filePath);
const normalizedFilePath = path.resolve(asset);
if (!normalizedFilePath.startsWith(Router.rootPath)) {
response.statusCode = 404;
return response.end();
}
try {
const data = await readFile(asset);
response.setHeader("Content-Type", getMimeType(asset));
response.write(data);
response.end();
} catch (err) {
if (retryCount > 5) {
logger.error("handleStaticFile:", err.toString());
response.statusCode = 404;
return response.end();
}
filePath = `${publicPath}/${appName}.html`;
}
}
}
protected addRoutes() {
// Static assets
if (this.dependencies.httpProxy) {
this.router.add({ method: "get", path: "/{path*}" }, (apiReq: LensApiRequest) => {
const { req, res } = apiReq.raw;
if (req.url === "/" || !req.url.startsWith("/build/")) {
req.url = `${publicPath}/${appName}.html`;
}
this.dependencies.httpProxy.web(req, res, {
target: "http://127.0.0.1:8080",
});
});
} else {
this.router.add({ method: "get", path: "/{path*}" }, Router.handleStaticFile);
}
this.router.add({ method: "get", path: "/version" }, VersionRoute.getVersion);
this.router.add({ method: "get", path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}` }, KubeconfigRoute.routeServiceAccountRoute);
// Metrics API
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, MetricsRoute.routeMetrics);
this.router.add({ method: "get", path: `${apiPrefix}/metrics/providers` }, MetricsRoute.routeMetricsProviders);
// Port-forward API (the container port and local forwarding port are obtained from the query parameters)
this.router.add({ method: "post", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, this.dependencies.routePortForward);
this.router.add({ method: "get", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routeCurrentPortForward);
this.router.add({ method: "delete", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routeCurrentPortForwardStop);
// Helm API
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, HelmApiRoute.listCharts);
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}` }, HelmApiRoute.getChart);
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}/values` }, HelmApiRoute.getChartValues);
this.router.add({ method: "post", path: `${apiPrefix}/v2/releases` }, HelmApiRoute.installChart);
this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, HelmApiRoute.updateRelease);
this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback` }, HelmApiRoute.rollbackRelease);
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace?}` }, HelmApiRoute.listReleases);
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, HelmApiRoute.getRelease);
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/values` }, HelmApiRoute.getReleaseValues);
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/history` }, HelmApiRoute.getReleaseHistory);
this.router.add({ method: "delete", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, HelmApiRoute.deleteRelease);
// Resource Applier API
this.router.add({ method: "post", path: `${apiPrefix}/stack` }, ResourceApplierApiRoute.applyResource);
this.router.add({ method: "patch", path: `${apiPrefix}/stack` }, ResourceApplierApiRoute.patchResource);
}
}

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import Subtext from "@hapi/subtext";
const parseRequestInjectable = getInjectable({
id: "parse-http-request",
instantiate: () => Subtext.parse,
});
export default parseRequestInjectable;

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiResult } from "./router";
export interface LensApiResultContentType {
resultMapper: (result: LensApiResult<any>) => ({
statusCode: number;
content: any;
headers: Record<string, string>;
});
}
const resultMapperFor =
(contentType: string): LensApiResultContentType["resultMapper"] =>
({ response, error, statusCode= error ? 400 : 200, headers = {}}) => ({
statusCode,
content: error || response,
headers: { ...headers, "Content-Type": contentType },
});
export type SupportedFileExtension = "json" | "txt" | "html" | "css" | "gif" | "jpg" | "png" | "svg" | "js" | "woff2" | "ttf";
export const contentTypes: Record<SupportedFileExtension, LensApiResultContentType> = {
json: {
resultMapper: (result) => {
const resultMapper = resultMapperFor("application/json");
const mappedResult = resultMapper(result);
const contentIsObject = typeof mappedResult.content === "object";
const contentIsBuffer = mappedResult.content instanceof Buffer;
const contentShouldBeStringified = contentIsObject && !contentIsBuffer;
const content = contentShouldBeStringified
? JSON.stringify(mappedResult.content)
: mappedResult.content;
return {
...mappedResult,
content,
};
},
},
txt: {
resultMapper: resultMapperFor("text/plain"),
},
html: {
resultMapper: resultMapperFor("text/html"),
},
css: {
resultMapper: resultMapperFor("text/css"),
},
gif: {
resultMapper: resultMapperFor("image/gif"),
},
jpg: {
resultMapper: resultMapperFor("image/jpeg"),
},
png: {
resultMapper: resultMapperFor("image/png"),
},
svg: {
resultMapper: resultMapperFor("image/svg+xml"),
},
js: {
resultMapper: resultMapperFor("application/javascript"),
},
woff2: {
resultMapper: resultMapperFor("font/woff2"),
},
ttf: {
resultMapper: resultMapperFor("font/ttf"),
},
};

View File

@ -2,23 +2,22 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
import httpProxy from "http-proxy";
import { Router } from "../router";
import routePortForwardInjectable
from "../routes/port-forward/route-port-forward/route-port-forward.injectable";
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import { Route, Router } from "./router";
import parseRequestInjectable from "./parse-request.injectable";
export const routeInjectionToken = getInjectionToken<Route<any>>({
id: "route-injection-token",
});
const routerInjectable = getInjectable({
id: "router",
instantiate: (di) => {
const isDevelopment = di.inject(isDevelopmentInjectable);
const proxy = isDevelopment ? httpProxy.createProxy() : undefined;
const routes = di.injectMany(routeInjectionToken);
return new Router({
routePortForward: di.inject(routePortForwardInjectable),
httpProxy: proxy,
return new Router(routes, {
parseRequest: di.inject(parseRequestInjectable),
});
},
});

View File

@ -0,0 +1,294 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import routerInjectable, { routeInjectionToken } from "./router.injectable";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import type { Router, RouteHandler, Route } from "./router";
import type { Cluster } from "../../common/cluster/cluster";
import { Request } from "mock-http";
import { getInjectable } from "@ogre-tools/injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import parseRequestInjectable from "./parse-request.injectable";
import { contentTypes } from "./router-content-types";
import mockFs from "mock-fs";
describe("router", () => {
let router: Router;
let routeHandlerMock: AsyncFnMock<RouteHandler<any>>;
beforeEach(async () => {
routeHandlerMock = asyncFn();
const di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs();
di.override(parseRequestInjectable, () => () => Promise.resolve({ payload: "some-payload" }));
await di.runSetups();
const injectable = getInjectable({
id: "some-route",
instantiate: () => ({
method: "get",
path: "/some-path",
handler: routeHandlerMock,
} as Route<any>),
injectionToken: routeInjectionToken,
});
di.register(injectable);
router = di.inject(routerInjectable);
});
afterEach(() => {
mockFs.restore();
});
describe("when navigating to the route", () => {
let actualPromise: Promise<boolean>;
let clusterStub: Cluster;
let requestStub: Request;
let responseStub: any;
beforeEach(() => {
requestStub = new Request({
url: "/some-path",
method: "get",
headers: {
"content-type": "application/json",
},
});
responseStub = { end: jest.fn(), setHeader: jest.fn(), write: jest.fn(), statusCode: undefined };
clusterStub = {} as Cluster;
actualPromise = router.route(clusterStub, requestStub, responseStub);
});
it("calls handler with the request", () => {
expect(routeHandlerMock).toHaveBeenCalledWith({
cluster: clusterStub,
params: {},
path: "/some-path",
payload: "some-payload",
query: expect.any(URLSearchParams),
raw: { req: requestStub, res: responseStub },
});
});
it("given no content-type is specified, when handler resolves, resolves with JSON", async () => {
await routeHandlerMock.resolve({ response: "some-response-from-route-handler" });
await actualPromise;
expect(responseStub.setHeader.mock.calls).toEqual([
["Content-Type", "application/json"],
]);
});
it("given JSON content-type is specified, when handler resolves with object, resolves with JSON", async () => {
await routeHandlerMock.resolve({ response: { some: "object" }});
await actualPromise;
expect(responseStub.end).toHaveBeenCalledWith('{"some":"object"}');
});
describe("when handler resolves without any result", () => {
beforeEach(async () => {
await routeHandlerMock.resolve(undefined);
await actualPromise;
});
it("resolves as plain text", () => {
expect(responseStub.setHeader.mock.calls).toEqual([["Content-Type", "text/plain"]]);
});
it("resolves with status code for no content", async () => {
expect(responseStub.statusCode).toBe(204);
});
it("resolves without content", async () => {
expect(responseStub.end.mock.calls).toEqual([[]]);
});
});
describe("when handler rejects", () => {
beforeEach(async () => {
await routeHandlerMock.reject(new Error("some-error"));
await actualPromise;
});
it("resolves as plain text", () => {
expect(responseStub.setHeader.mock.calls).toEqual([["Content-Type", "text/plain"]]);
});
it('resolves with "500" status code', () => {
expect(responseStub.statusCode).toBe(500);
});
it("resolves with error", () => {
expect(responseStub.end).toHaveBeenCalledWith("Error: some-error");
});
});
[
{ contentType: "text/plain", contentTypeObject: contentTypes.txt },
{ contentType: "application/json", contentTypeObject: contentTypes.json },
{ contentType: "text/html", contentTypeObject: contentTypes.html },
{ contentType: "text/css", contentTypeObject: contentTypes.css },
{ contentType: "image/gif", contentTypeObject: contentTypes.gif },
{ contentType: "image/jpeg", contentTypeObject: contentTypes.jpg },
{ contentType: "image/png", contentTypeObject: contentTypes.png },
{ contentType: "image/svg+xml", contentTypeObject: contentTypes.svg },
{ contentType: "application/javascript", contentTypeObject: contentTypes.js },
{ contentType: "font/woff2", contentTypeObject: contentTypes.woff2 },
{ contentType: "font/ttf", contentTypeObject: contentTypes.ttf },
].forEach(scenario => {
describe(`given content type is "${scenario.contentType}", when handler resolves with response`, () => {
beforeEach(async () => {
await routeHandlerMock.resolve({ response: "some-response", contentType: scenario.contentTypeObject });
await actualPromise;
});
it("has content type specific headers", () => {
expect(responseStub.setHeader.mock.calls).toEqual([
["Content-Type", scenario.contentType],
]);
});
it("defaults to successful status code", () => {
expect(responseStub.statusCode).toBe(200);
});
it("has response as body", () => {
expect(responseStub.end).toHaveBeenCalledWith("some-response");
});
});
it(`given content type is "${scenario.contentType}", when handler resolves with success and custom status code, defaults to "200" as status code`, async () => {
await routeHandlerMock.resolve({
response: "some-response",
statusCode: 204,
contentType: scenario.contentTypeObject,
});
await actualPromise;
expect(responseStub.statusCode).toBe(204);
});
it(`given content type is "${scenario.contentType}", when handler resolves with success but without status code, defaults to "200" as status code`, async () => {
await routeHandlerMock.resolve({
response: "some-response",
contentType: scenario.contentTypeObject,
});
await actualPromise;
expect(responseStub.statusCode).toBe(200);
});
it(`given content type is "${scenario.contentType}", when handler resolves without response, has no body`, async () => {
await routeHandlerMock.resolve({
response: undefined,
contentType: scenario.contentTypeObject,
});
await actualPromise;
expect(responseStub.end.mock.calls).toEqual([[]]);
});
it(`given content type is "${scenario.contentType}", when handler resolves with error, has error as body`, async () => {
await routeHandlerMock.resolve({
error: "some-error",
contentType: scenario.contentTypeObject,
});
await actualPromise;
expect(responseStub.end).toHaveBeenCalledWith("some-error");
});
it(`given content type is "${scenario.contentType}", when handler resolves with error and status code, has custom status code`, async () => {
await routeHandlerMock.resolve({
error: "some-error",
statusCode: 414,
contentType: scenario.contentTypeObject,
});
await actualPromise;
expect(responseStub.statusCode).toBe(414);
});
it(`given content type is "${scenario.contentType}", when handler resolves with error but without status code, defaults to "400" as status code`, async () => {
await routeHandlerMock.resolve({
error: "some-error",
contentType: scenario.contentTypeObject,
});
await actualPromise;
expect(responseStub.statusCode).toBe(400);
});
it(`given content type is "${scenario.contentType}", when handler resolves custom headers, resolves with content type specific headers and custom headers`, async () => {
await routeHandlerMock.resolve({
response: "irrelevant",
headers: {
"Content-Type": "some-content-type-to-be-overridden",
"Some-Header": "some-header-value",
},
contentType: scenario.contentTypeObject,
});
await actualPromise;
expect(responseStub.setHeader.mock.calls).toEqual([
["Content-Type", scenario.contentType],
["Some-Header", "some-header-value"],
]);
});
describe(`given content type is "${scenario.contentType}", when handler resolves with binary content`, () => {
let responseBufferStub: Buffer;
beforeEach(async () => {
responseBufferStub = Buffer.from("some-binary-content");
await routeHandlerMock.resolve({
response: responseBufferStub,
contentType: scenario.contentTypeObject,
});
await actualPromise;
});
it("writes binary content to response", () => {
expect(responseStub.write).toHaveBeenCalledWith(responseBufferStub);
});
it("does not end with the response", () => {
expect(responseStub.end.mock.calls[0]).toEqual([]);
});
});
});
});
});

196
src/main/router/router.ts Normal file
View File

@ -0,0 +1,196 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import Call from "@hapi/call";
import type http from "http";
import type httpProxy from "http-proxy";
import { toPairs } from "lodash/fp";
import path from "path";
import type { Cluster } from "../../common/cluster/cluster";
import type { LensApiResultContentType } from "./router-content-types";
import { contentTypes } from "./router-content-types";
// TODO: Import causes side effect, sets value for __static
import "../../common/vars";
export interface RouterRequestOpts {
req: http.IncomingMessage;
res: http.ServerResponse;
cluster: Cluster;
params: RouteParams;
url: URL;
}
export interface RouteParams extends Record<string, string> {
path?: string; // *-route
namespace?: string;
service?: string;
account?: string;
release?: string;
repo?: string;
chart?: string;
}
export interface LensApiRequest<P = any> {
path: string;
payload: P;
params: RouteParams;
cluster: Cluster;
query: URLSearchParams;
raw: {
req: http.IncomingMessage;
res: http.ServerResponse;
};
}
interface Dependencies {
parseRequest: (request: http.IncomingMessage, _: null, options: { parse: boolean; output: string }) => Promise<{ payload: any }>;
}
export class Router {
protected router = new Call.Router();
protected static rootPath = path.resolve(__static);
constructor(routes: Route<unknown>[], private dependencies: Dependencies) {
routes.forEach(route => {
this.router.add({ method: route.method, path: route.path }, handleRoute(route));
});
}
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
const url = new URL(req.url, "http://localhost");
const path = url.pathname;
const method = req.method.toLowerCase();
const matchingRoute = this.router.route(method, path);
const routeFound = !matchingRoute.isBoom;
if (routeFound) {
const request = await this.getRequest({ req, res, cluster, url, params: matchingRoute.params });
await matchingRoute.route(request, res);
return true;
}
return false;
}
protected async getRequest(opts: RouterRequestOpts): Promise<LensApiRequest> {
const { req, res, url, cluster, params } = opts;
const { payload } = await this.dependencies.parseRequest(req, null, {
parse: true,
output: "data",
});
return {
cluster,
path: url.pathname,
raw: {
req, res,
},
query: url.searchParams,
payload,
params,
};
}
}
export interface LensApiResult<TResult> {
statusCode?: number;
response?: TResult;
error?: any;
contentType?: LensApiResultContentType;
headers?: { [name: string]: string };
proxy?: httpProxy;
}
export type RouteHandler<TResponse> = (
request: LensApiRequest
) =>
| Promise<LensApiResult<TResponse>>
| Promise<void>
| LensApiResult<TResponse>
| void;
export interface Route<TResponse> {
path: string;
method: "get" | "post" | "put" | "patch" | "delete";
handler: RouteHandler<TResponse>;
}
const handleRoute = (route: Route<unknown>) => async (request: LensApiRequest, response: http.ServerResponse) => {
let result: LensApiResult<any> | void;
const writeServerResponse = writeServerResponseFor(response);
try {
result = await route.handler(request);
} catch(error) {
const mappedResult = contentTypes.txt.resultMapper({
statusCode: 500,
error: error.toString(),
});
writeServerResponse(mappedResult);
return;
}
if (!result) {
const mappedResult = contentTypes.txt.resultMapper({
statusCode: 204,
response: undefined,
});
writeServerResponse(mappedResult);
return;
}
if (result.proxy) {
return;
}
const contentType = result.contentType || contentTypes.json;
const mappedResult = contentType.resultMapper(result);
writeServerResponse(mappedResult);
};
const writeServerResponseFor =
(serverResponse: http.ServerResponse) =>
({
statusCode,
content,
headers,
}: {
statusCode: number;
content: any;
headers: { [name: string]: string };
}) => {
serverResponse.statusCode = statusCode;
const headerPairs = toPairs<string>(headers);
headerPairs.forEach(([name, value]) => {
serverResponse.setHeader(name, value);
});
if (content instanceof Buffer) {
serverResponse.write(content);
serverResponse.end();
return;
}
if (content) {
serverResponse.end(content);
} else {
serverResponse.end();
}
};

View File

@ -1,148 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiRequest } from "../router";
import { helmService } from "../helm/helm-service";
import logger from "../logger";
import { respondJson, respondText } from "../utils/http-responses";
import { getBoolean } from "../utils/parse-query";
export class HelmApiRoute {
static async listCharts(request: LensApiRequest) {
const { response } = request;
const charts = await helmService.listCharts();
respondJson(response, charts);
}
static async getChart(request: LensApiRequest) {
const { params, query, response } = request;
try {
const chart = await helmService.getChart(params.repo, params.chart, query.get("version"));
respondJson(response, chart);
} catch (error) {
respondText(response, error?.toString() || "Error getting chart", 422);
}
}
static async getChartValues(request: LensApiRequest) {
const { params, query, response } = request;
try {
const values = await helmService.getChartValues(params.repo, params.chart, query.get("version"));
respondJson(response, values);
} catch (error) {
respondText(response, error?.toString() || "Error getting chart values", 422);
}
}
static async installChart(request: LensApiRequest) {
const { payload, cluster, response } = request;
try {
const result = await helmService.installChart(cluster, payload);
respondJson(response, result, 201);
} catch (error) {
logger.debug(error);
respondText(response, error?.toString() || "Error installing chart", 422);
}
}
static async updateRelease(request: LensApiRequest) {
const { cluster, params, payload, response } = request;
try {
const result = await helmService.updateRelease(cluster, params.release, params.namespace, payload );
respondJson(response, result);
} catch (error) {
logger.debug(error);
respondText(response, error?.toString() || "Error updating chart", 422);
}
}
static async rollbackRelease(request: LensApiRequest) {
const { cluster, params, payload, response } = request;
try {
await helmService.rollback(cluster, params.release, params.namespace, payload.revision);
response.end();
} catch (error) {
logger.debug(error);
respondText(response, error?.toString() || "Error rolling back chart", 422);
}
}
static async listReleases(request: LensApiRequest) {
const { cluster, params, response } = request;
try {
const result = await helmService.listReleases(cluster, params.namespace);
respondJson(response, result);
} catch(error) {
logger.debug(error);
respondText(response, error?.toString() || "Error listing release", 422);
}
}
static async getRelease(request: LensApiRequest) {
const { cluster, params, response } = request;
try {
const result = await helmService.getRelease(cluster, params.release, params.namespace);
respondJson(response, result);
} catch (error) {
logger.debug(error);
respondText(response, error?.toString() || "Error getting release", 422);
}
}
static async getReleaseValues(request: LensApiRequest) {
const { cluster, params: { namespace, release }, response, query } = request;
const all = getBoolean(query, "all");
try {
const result = await helmService.getReleaseValues(release, { cluster, namespace, all });
respondText(response, result);
} catch (error) {
logger.debug(error);
respondText(response, error?.toString() || "Error getting release values", 422);
}
}
static async getReleaseHistory(request: LensApiRequest) {
const { cluster, params, response } = request;
try {
const result = await helmService.getReleaseHistory(cluster, params.release, params.namespace);
respondJson(response, result);
} catch (error) {
logger.debug(error);
respondText(response, error?.toString() || "Error getting release history", 422);
}
}
static async deleteRelease(request: LensApiRequest) {
const { cluster, params, response } = request;
try {
const result = await helmService.deleteRelease(cluster, params.release, params.namespace);
respondJson(response, result);
} catch (error) {
logger.debug(error);
respondText(response, error?.toString() || "Error deleting release", 422);
}
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { routeInjectionToken } from "../../../router/router.injectable";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { apiPrefix } from "../../../../common/vars";
import type { RawHelmChart } from "../../../../common/k8s-api/endpoints/helm-charts.api";
interface GetChartResponse {
readme: string;
versions: RawHelmChart[];
}
const getChartRouteInjectable = getInjectable({
id: "get-chart-route",
instantiate: (): Route<GetChartResponse> => ({
method: "get",
path: `${apiPrefix}/v2/charts/{repo}/{chart}`,
handler: async ({ params, query }) => ({
response: await helmService.getChart(
params.repo,
params.chart,
query.get("version"),
),
}),
}),
injectionToken: routeInjectionToken,
});
export default getChartRouteInjectable;

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { routeInjectionToken } from "../../../router/router.injectable";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { apiPrefix } from "../../../../common/vars";
const getChartRouteValuesInjectable = getInjectable({
id: "get-chart-route-values",
instantiate: (): Route<string> => ({
method: "get",
path: `${apiPrefix}/v2/charts/{repo}/{chart}/values`,
handler: async ({
params,
query,
}) => ({
response: await helmService.getChartValues(
params.repo,
params.chart,
query.get("version"),
),
}),
}),
injectionToken: routeInjectionToken,
});
export default getChartRouteValuesInjectable;

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { routeInjectionToken } from "../../../router/router.injectable";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { apiPrefix } from "../../../../common/vars";
const listChartsRouteInjectable = getInjectable({
id: "list-charts-route",
instantiate: (): Route<any> => ({
method: "get",
path: `${apiPrefix}/v2/charts`,
handler: async () => ({
response: await helmService.listCharts(),
}),
}),
injectionToken: routeInjectionToken,
});
export default listChartsRouteInjectable;

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { routeInjectionToken } from "../../../router/router.injectable";
import { getInjectable } from "@ogre-tools/injectable";
const deleteReleaseRouteInjectable = getInjectable({
id: "delete-release-route",
instantiate: (): Route<string> => ({
method: "delete",
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
handler: async (request) => {
const { cluster, params } = request;
return {
response: await helmService.deleteRelease(
cluster,
params.release,
params.namespace,
),
};
},
}),
injectionToken: routeInjectionToken,
});
export default deleteReleaseRouteInjectable;

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { routeInjectionToken } from "../../../router/router.injectable";
import { getInjectable } from "@ogre-tools/injectable";
const getReleaseRouteHistoryInjectable = getInjectable({
id: "get-release-history-route",
instantiate: (): Route<any> => ({
method: "get",
path: `${apiPrefix}/v2/releases/{namespace}/{release}/history`,
handler: async (request) => {
const { cluster, params } = request;
return {
response: await helmService.getReleaseHistory(
cluster,
params.release,
params.namespace,
),
};
},
}),
injectionToken: routeInjectionToken,
});
export default getReleaseRouteHistoryInjectable;

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { routeInjectionToken } from "../../../router/router.injectable";
import { getInjectable } from "@ogre-tools/injectable";
const getReleaseRouteInjectable = getInjectable({
id: "get-release-route",
instantiate: (): Route<any> => ({
method: "get",
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
handler: async (request) => {
const { cluster, params } = request;
return {
response: await helmService.getRelease(
cluster,
params.release,
params.namespace,
),
};
},
}),
injectionToken: routeInjectionToken,
});
export default getReleaseRouteInjectable;

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { routeInjectionToken } from "../../../router/router.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { getBoolean } from "../../../utils/parse-query";
import { contentTypes } from "../../../router/router-content-types";
const getReleaseRouteValuesInjectable = getInjectable({
id: "get-release-values-route",
instantiate: (): Route<string> => ({
method: "get",
path: `${apiPrefix}/v2/releases/{namespace}/{release}/values`,
handler: async (request) => {
const { cluster, params: { namespace, release }, query } = request;
const all = getBoolean(query, "all");
return {
response: await helmService.getReleaseValues(release, {
cluster,
namespace,
all,
}),
contentType: contentTypes.txt,
};
},
}),
injectionToken: routeInjectionToken,
});
export default getReleaseRouteValuesInjectable;

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { routeInjectionToken } from "../../../router/router.injectable";
import { getInjectable } from "@ogre-tools/injectable";
interface InstallChartResponse {
log: string;
release: { name: string; namespace: string };
}
const installChartRouteInjectable = getInjectable({
id: "install-chart-route",
instantiate: () : Route<InstallChartResponse> => ({
method: "post",
path: `${apiPrefix}/v2/releases`,
handler: async (request) => {
const { payload, cluster } = request;
return {
response: await helmService.installChart(cluster, payload),
statusCode: 201,
};
},
}),
injectionToken: routeInjectionToken,
});
export default installChartRouteInjectable;

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { routeInjectionToken } from "../../../router/router.injectable";
import { getInjectable } from "@ogre-tools/injectable";
const listReleasesRouteInjectable = getInjectable({
id: "list-releases-route",
instantiate: (): Route<Record<string, any>> => ({
method: "get",
path: `${apiPrefix}/v2/releases/{namespace?}`,
handler: async (request) => {
const { cluster, params } = request;
return {
response: await helmService.listReleases(cluster, params.namespace),
};
},
}),
injectionToken: routeInjectionToken,
});
export default listReleasesRouteInjectable;

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { routeInjectionToken } from "../../../router/router.injectable";
import { getInjectable } from "@ogre-tools/injectable";
const rollbackReleaseRouteInjectable = getInjectable({
id: "rollback-release-route",
instantiate: (): Route<void> => ({
method: "put",
path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback`,
handler: async (request) => {
const { cluster, params, payload } = request;
await helmService.rollback(cluster, params.release, params.namespace, payload.revision);
},
}),
injectionToken: routeInjectionToken,
});
export default rollbackReleaseRouteInjectable;

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiPrefix } from "../../../../common/vars";
import type { Route } from "../../../router/router";
import { helmService } from "../../../helm/helm-service";
import { routeInjectionToken } from "../../../router/router.injectable";
import { getInjectable } from "@ogre-tools/injectable";
interface UpdateReleaseResponse {
log: string;
release: { name: string; namespace: string };
}
const updateReleaseRouteInjectable = getInjectable({
id: "update-release-route",
instantiate: (): Route<UpdateReleaseResponse> => ({
method: "put",
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
handler: async ({
cluster,
params,
payload,
}) => ({
response: await helmService.updateRelease(
cluster,
params.release,
params.namespace,
payload,
),
}),
}),
injectionToken: routeInjectionToken,
});
export default updateReleaseRouteInjectable;

View File

@ -1,11 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./kubeconfig-route";
export * from "./metrics-route";
export * from "./port-forward-route";
export * from "./helm-route";
export * from "./resource-applier-route";
export * from "./version-route";

View File

@ -1,62 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiRequest } from "../router";
import { respondJson } from "../utils/http-responses";
import type { Cluster } from "../../common/cluster/cluster";
import { CoreV1Api, V1Secret } from "@kubernetes/client-node";
function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster) {
const tokenData = Buffer.from(secret.data["token"], "base64");
return {
"apiVersion": "v1",
"kind": "Config",
"clusters": [
{
"name": cluster.contextName,
"cluster": {
"server": cluster.apiUrl,
"certificate-authority-data": secret.data["ca.crt"],
},
},
],
"users": [
{
"name": username,
"user": {
"token": tokenData.toString("utf8"),
},
},
],
"contexts": [
{
"name": cluster.contextName,
"context": {
"user": username,
"cluster": cluster.contextName,
"namespace": secret.metadata.namespace,
},
},
],
"current-context": cluster.contextName,
};
}
export class KubeconfigRoute {
static async routeServiceAccountRoute(request: LensApiRequest) {
const { params, response, cluster } = request;
const client = (await cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api);
const secretList = await client.listNamespacedSecret(params.namespace);
const secret = secretList.body.items.find(secret => {
const { annotations } = secret.metadata;
return annotations && annotations["kubernetes.io/service-account.name"] == params.account;
});
const data = generateKubeConfig(params.account, secret, cluster);
respondJson(response, data);
}
}

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { apiPrefix } from "../../../common/vars";
import type { Route } from "../../router/router";
import { routeInjectionToken } from "../../router/router.injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import { CoreV1Api, V1Secret } from "@kubernetes/client-node";
const getServiceAccountRouteInjectable = getInjectable({
id: "get-service-account-route",
instantiate: (): Route<ReturnType<typeof generateKubeConfig>> => ({
method: "get",
path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}`,
handler: async (request) => {
const { params, cluster } = request;
const client = (await cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api);
const secretList = await client.listNamespacedSecret(params.namespace);
const secret = secretList.body.items.find(secret => {
const { annotations } = secret.metadata;
return annotations && annotations["kubernetes.io/service-account.name"] == params.account;
});
return { response: generateKubeConfig(params.account, secret, cluster) };
},
}),
injectionToken: routeInjectionToken,
});
export default getServiceAccountRouteInjectable;
function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster) {
const tokenData = Buffer.from(secret.data["token"], "base64");
return {
"apiVersion": "v1",
"kind": "Config",
"clusters": [
{
"name": cluster.contextName,
"cluster": {
"server": cluster.apiUrl,
"certificate-authority-data": secret.data["ca.crt"],
},
},
],
"users": [
{
"name": username,
"user": {
"token": tokenData.toString("utf8"),
},
},
],
"contexts": [
{
"name": cluster.contextName,
"context": {
"user": username,
"cluster": cluster.contextName,
"namespace": secret.metadata.namespace,
},
},
],
"current-context": cluster.contextName,
};
}

View File

@ -1,109 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiRequest } from "../router";
import { respondJson } from "../utils/http-responses";
import type { Cluster } from "../../common/cluster/cluster";
import { ClusterMetadataKey, ClusterPrometheusMetadata } from "../../common/cluster-types";
import logger from "../logger";
import { getMetrics } from "../k8s-request";
import { PrometheusProviderRegistry } from "../prometheus";
export type IMetricsQuery = string | string[] | {
[metricName: string]: string;
};
// This is used for backoff retry tracking.
const ATTEMPTS = [false, false, false, false, true];
// prometheus metrics loader
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> {
const queries = promQueries.map(p => p.trim());
const loaders = new Map<string, Promise<any>>();
async function loadMetric(query: string): Promise<any> {
async function loadMetricHelper(): Promise<any> {
for (const [attempt, lastAttempt] of ATTEMPTS.entries()) { // retry
try {
return await getMetrics(cluster, prometheusPath, { query, ...queryParams });
} catch (error) {
if (lastAttempt || (error?.statusCode >= 400 && error?.statusCode < 500)) {
logger.error("[Metrics]: metrics not available", error?.response ? error.response?.body : error);
throw new Error("Metrics not available");
}
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
}
}
}
return loaders.get(query) ?? loaders.set(query, loadMetricHelper()).get(query);
}
return Promise.all(queries.map(loadMetric));
}
interface MetricProviderInfo {
name: string;
id: string;
isConfigurable: boolean;
}
export class MetricsRoute {
static async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
const prometheusMetadata: ClusterPrometheusMetadata = {};
try {
const { prometheusPath, provider } = await cluster.contextHandler.getPrometheusDetails();
prometheusMetadata.provider = provider?.id;
prometheusMetadata.autoDetected = !cluster.preferences.prometheusProvider?.type;
if (!prometheusPath) {
prometheusMetadata.success = false;
return respondJson(response, {});
}
// return data in same structure as query
if (typeof payload === "string") {
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams);
respondJson(response, data);
} else if (Array.isArray(payload)) {
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams);
respondJson(response, data);
} else {
const queries = Object.entries<Record<string, string>>(payload)
.map(([queryName, queryOpts]) => (
provider.getQuery(queryOpts, queryName)
));
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
respondJson(response, data);
}
prometheusMetadata.success = true;
} catch (error) {
prometheusMetadata.success = false;
respondJson(response, {});
logger.warn(`[METRICS-ROUTE]: failed to get metrics for clusterId=${cluster.id}:`, error);
} finally {
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
}
}
static async routeMetricsProviders({ response }: LensApiRequest) {
const providers: MetricProviderInfo[] = [];
for (const { name, id, isConfigurable } of PrometheusProviderRegistry.getInstance().providers.values()) {
providers.push({ name, id, isConfigurable });
}
respondJson(response, providers);
}
}

View File

@ -0,0 +1,108 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { apiPrefix } from "../../../common/vars";
import type { LensApiRequest, Route } from "../../router/router";
import { routeInjectionToken } from "../../router/router.injectable";
import { ClusterMetadataKey, ClusterPrometheusMetadata } from "../../../common/cluster-types";
import logger from "../../logger";
import type { Cluster } from "../../../common/cluster/cluster";
import { getMetrics } from "../../k8s-request";
import type { IMetricsQuery } from "./metrics-query";
// This is used for backoff retry tracking.
const ATTEMPTS = [false, false, false, false, true];
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> {
const queries = promQueries.map(p => p.trim());
const loaders = new Map<string, Promise<any>>();
async function loadMetric(query: string): Promise<any> {
async function loadMetricHelper(): Promise<any> {
for (const [attempt, lastAttempt] of ATTEMPTS.entries()) { // retry
try {
return await getMetrics(cluster, prometheusPath, { query, ...queryParams });
} catch (error) {
if (lastAttempt || (error?.statusCode >= 400 && error?.statusCode < 500)) {
logger.error("[Metrics]: metrics not available", error?.response ? error.response?.body : error);
throw new Error("Metrics not available");
}
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
}
}
}
return loaders.get(query) ?? loaders.set(query, loadMetricHelper()).get(query);
}
return Promise.all(queries.map(loadMetric));
}
const addMetricsRoute = async ({ cluster, payload, query }: LensApiRequest) => {
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
const prometheusMetadata: ClusterPrometheusMetadata = {};
try {
const { prometheusPath, provider } = await cluster.contextHandler.getPrometheusDetails();
prometheusMetadata.provider = provider?.id;
prometheusMetadata.autoDetected = !cluster.preferences.prometheusProvider?.type;
if (!prometheusPath) {
prometheusMetadata.success = false;
return { response: {}};
}
// return data in same structure as query
if (typeof payload === "string") {
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams);
return { response: data };
}
if (Array.isArray(payload)) {
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams);
return { response: data };
}
const queries = Object.entries<Record<string, string>>(payload)
.map(([queryName, queryOpts]) => (
provider.getQuery(queryOpts, queryName)
));
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
prometheusMetadata.success = true;
return { response: data };
} catch (error) {
prometheusMetadata.success = false;
logger.warn(`[METRICS-ROUTE]: failed to get metrics for clusterId=${cluster.id}:`, error);
return { response: {}};
} finally {
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
}
};
const addMetricsRouteInjectable = getInjectable({
id: "add-metrics-route",
instantiate: (): Route<any> => ({
method: "post",
path: `${apiPrefix}/metrics`,
handler: addMetricsRoute,
}),
injectionToken: routeInjectionToken,
});
export default addMetricsRouteInjectable;

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { apiPrefix } from "../../../common/vars";
import type { Route } from "../../router/router";
import { routeInjectionToken } from "../../router/router.injectable";
import { PrometheusProviderRegistry } from "../../prometheus";
import type { MetricProviderInfo } from "../../../common/k8s-api/endpoints/metrics.api";
const getMetricProvidersRouteInjectable = getInjectable({
id: "get-metric-providers-route",
instantiate: (): Route<MetricProviderInfo[]> => ({
method: "get",
path: `${apiPrefix}/metrics/providers`,
handler: () => {
const providers: MetricProviderInfo[] = [];
for (const { name, id, isConfigurable } of PrometheusProviderRegistry.getInstance().providers.values()) {
providers.push({ name, id, isConfigurable });
}
return { response: providers };
},
}),
injectionToken: routeInjectionToken,
});
export default getMetricProvidersRouteInjectable;

View File

@ -0,0 +1,7 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export type IMetricsQuery = string | string[] | {
[metricName: string]: string;
};

View File

@ -1,48 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiRequest } from "../router";
import logger from "../logger";
import { respondJson } from "../utils/http-responses";
import { PortForward } from "./port-forward/port-forward";
export class PortForwardRoute {
static async routeCurrentPortForward(request: LensApiRequest) {
const { params, query, response, cluster } = request;
const { namespace, resourceType, resourceName } = params;
const port = Number(query.get("port"));
const forwardPort = Number(query.get("forwardPort"));
const portForward = PortForward.getPortforward({
clusterId: cluster.id, kind: resourceType, name: resourceName,
namespace, port, forwardPort,
});
respondJson(response, { port: portForward?.forwardPort ?? null });
}
static async routeCurrentPortForwardStop(request: LensApiRequest) {
const { params, query, response, cluster } = request;
const { namespace, resourceType, resourceName } = params;
const port = Number(query.get("port"));
const forwardPort = Number(query.get("forwardPort"));
const portForward = PortForward.getPortforward({
clusterId: cluster.id, kind: resourceType, name: resourceName,
namespace, port, forwardPort,
});
try {
await portForward.stop();
respondJson(response, { status: true });
} catch (error) {
logger.error("[PORT-FORWARD-ROUTE]: error stopping a port-forward", { namespace, port, forwardPort, resourceType, resourceName });
return respondJson(response, {
message: `error stopping a forward port ${port}`,
}, 400);
}
}
}

View File

@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { PortForward, PortForwardArgs } from "./port-forward";
import bundledKubectlInjectable from "../../kubectl/bundled-kubectl.injectable";
import bundledKubectlInjectable from "../../../kubectl/bundled-kubectl.injectable";
const createPortForwardInjectable = getInjectable({
id: "create-port-forward",

View File

@ -2,8 +2,8 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import logger from "../../logger";
import { getPortFrom } from "../../utils/get-port";
import logger from "../../../logger";
import { getPortFrom } from "../../../utils/get-port";
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
import * as tcpPortUsed from "tcp-port-used";

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { routeInjectionToken } from "../../router/router.injectable";
import type { LensApiRequest, Route } from "../../router/router";
import { apiPrefix } from "../../../common/vars";
import { PortForward } from "./functionality/port-forward";
const getCurrentPortForward = async (request: LensApiRequest) => {
const { params, query, cluster } = request;
const { namespace, resourceType, resourceName } = params;
const port = Number(query.get("port"));
const forwardPort = Number(query.get("forwardPort"));
const portForward = PortForward.getPortforward({
clusterId: cluster.id, kind: resourceType, name: resourceName,
namespace, port, forwardPort,
});
return { response: { port: portForward?.forwardPort ?? null }};
};
const getCurrentPortForwardRouteInjectable = getInjectable({
id: "get-current-port-forward-route",
instantiate: (): Route<{ port: number }> => ({
method: "get",
path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}`,
handler: getCurrentPortForward,
}),
injectionToken: routeInjectionToken,
});
export default getCurrentPortForwardRouteInjectable;

View File

@ -1,17 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { routePortForward } from "./route-port-forward";
import { getInjectable } from "@ogre-tools/injectable";
import createPortForwardInjectable from "../create-port-forward.injectable";
const routePortForwardInjectable = getInjectable({
id: "route-port-forward",
instantiate: (di) => routePortForward({
createPortForward: di.inject(createPortForwardInjectable),
}),
});
export default routePortForwardInjectable;

View File

@ -1,86 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiRequest } from "../../../router";
import logger from "../../../logger";
import { respondJson } from "../../../utils/http-responses";
import { PortForward, PortForwardArgs } from "../port-forward";
interface Dependencies {
createPortForward: (pathToKubeConfig: string, args: PortForwardArgs) => PortForward;
}
export const routePortForward =
({ createPortForward }: Dependencies) =>
async (request: LensApiRequest) => {
const { params, query, response, cluster } = request;
const { namespace, resourceType, resourceName } = params;
const port = Number(query.get("port"));
const forwardPort = Number(query.get("forwardPort"));
try {
let portForward = PortForward.getPortforward({
clusterId: cluster.id,
kind: resourceType,
name: resourceName,
namespace,
port,
forwardPort,
});
if (!portForward) {
logger.info(
`Creating a new port-forward ${namespace}/${resourceType}/${resourceName}:${port}`,
);
const thePort =
0 < forwardPort && forwardPort < 65536 ? forwardPort : 0;
portForward = createPortForward(await cluster.getProxyKubeconfigPath(), {
clusterId: cluster.id,
kind: resourceType,
namespace,
name: resourceName,
port,
forwardPort: thePort,
});
const started = await portForward.start();
if (!started) {
logger.error("[PORT-FORWARD-ROUTE]: failed to start a port-forward", {
namespace,
port,
resourceType,
resourceName,
});
return respondJson(
response,
{
message: `Failed to forward port ${port} to ${
thePort ? forwardPort : "random port"
}`,
},
400,
);
}
}
respondJson(response, { port: portForward.forwardPort });
} catch (error) {
logger.error(
`[PORT-FORWARD-ROUTE]: failed to open a port-forward: ${error}`,
{ namespace, port, resourceType, resourceName },
);
return respondJson(
response,
{
message: `Failed to forward port ${port}`,
},
400,
);
}
};

View File

@ -0,0 +1,97 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { routeInjectionToken } from "../../router/router.injectable";
import type { LensApiRequest, Route } from "../../router/router";
import { apiPrefix } from "../../../common/vars";
import { PortForward, PortForwardArgs } from "./functionality/port-forward";
import logger from "../../logger";
import createPortForwardInjectable from "./functionality/create-port-forward.injectable";
interface Dependencies {
createPortForward: (pathToKubeConfig: string, args: PortForwardArgs) => PortForward;
}
const startPortForward = ({ createPortForward }: Dependencies) => async (request: LensApiRequest) => {
const { params, query, cluster } = request;
const { namespace, resourceType, resourceName } = params;
const port = Number(query.get("port"));
const forwardPort = Number(query.get("forwardPort"));
try {
let portForward = PortForward.getPortforward({
clusterId: cluster.id,
kind: resourceType,
name: resourceName,
namespace,
port,
forwardPort,
});
if (!portForward) {
logger.info(
`Creating a new port-forward ${namespace}/${resourceType}/${resourceName}:${port}`,
);
const thePort =
0 < forwardPort && forwardPort < 65536 ? forwardPort : 0;
portForward = createPortForward(await cluster.getProxyKubeconfigPath(), {
clusterId: cluster.id,
kind: resourceType,
namespace,
name: resourceName,
port,
forwardPort: thePort,
});
const started = await portForward.start();
if (!started) {
logger.error("[PORT-FORWARD-ROUTE]: failed to start a port-forward", {
namespace,
port,
resourceType,
resourceName,
});
return {
error: {
message: `Failed to forward port ${port} to ${
thePort ? forwardPort : "random port"
}`,
},
};
}
}
return { response: { port: portForward.forwardPort }};
} catch (error) {
logger.error(
`[PORT-FORWARD-ROUTE]: failed to open a port-forward: ${error}`,
{ namespace, port, resourceType, resourceName },
);
return {
error: {
message: `Failed to forward port ${port}`,
},
};
}
};
const startPortForwardRouteInjectable = getInjectable({
id: "start-current-port-forward-route",
instantiate: (di): Route<{ port: number }> => ({
method: "post",
path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}`,
handler: startPortForward({ createPortForward: di.inject(createPortForwardInjectable) }),
}),
injectionToken: routeInjectionToken,
});
export default startPortForwardRouteInjectable;

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { routeInjectionToken } from "../../router/router.injectable";
import type { LensApiRequest, Route } from "../../router/router";
import { apiPrefix } from "../../../common/vars";
import { PortForward } from "./functionality/port-forward";
import logger from "../../logger";
const stopCurrentPortForward = async (request: LensApiRequest) => {
const { params, query, cluster } = request;
const { namespace, resourceType, resourceName } = params;
const port = Number(query.get("port"));
const forwardPort = Number(query.get("forwardPort"));
const portForward = PortForward.getPortforward({
clusterId: cluster.id, kind: resourceType, name: resourceName,
namespace, port, forwardPort,
});
try {
await portForward.stop();
return { response: { status: true }};
} catch (error) {
logger.error("[PORT-FORWARD-ROUTE]: error stopping a port-forward", { namespace, port, forwardPort, resourceType, resourceName });
return {
error: {
message: `error stopping a forward port ${port}`,
},
};
}
};
const stopCurrentPortForwardRouteInjectable = getInjectable({
id: "stop-current-port-forward-route",
instantiate: (): Route<{ status: boolean }> => ({
method: "delete",
path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}`,
handler: stopCurrentPortForward,
}),
injectionToken: routeInjectionToken,
});
export default stopCurrentPortForwardRouteInjectable;

View File

@ -1,34 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiRequest } from "../router";
import { respondJson, respondText } from "../utils/http-responses";
import { ResourceApplier } from "../resource-applier";
export class ResourceApplierApiRoute {
static async applyResource(request: LensApiRequest) {
const { response, cluster, payload } = request;
try {
const resource = await new ResourceApplier(cluster).apply(payload);
respondJson(response, resource, 200);
} catch (error) {
respondText(response, error, 422);
}
}
static async patchResource(request: LensApiRequest) {
const { response, cluster, payload } = request;
try {
const resource = await new ResourceApplier(cluster).patch(payload.name, payload.kind, payload.patch, payload.ns);
respondJson(response, resource, 200);
} catch (error) {
respondText(response, error, 422);
}
}
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { routeInjectionToken } from "../../router/router.injectable";
import type { Route } from "../../router/router";
import { apiPrefix } from "../../../common/vars";
import { ResourceApplier } from "../../resource-applier";
const applyResourceRouteInjectable = getInjectable({
id: "apply-resource-route",
instantiate: (): Route<string> => ({
method: "post",
path: `${apiPrefix}/stack`,
handler: async ({ cluster, payload }) => ({
response: await new ResourceApplier(cluster).apply(payload),
}),
}),
injectionToken: routeInjectionToken,
});
export default applyResourceRouteInjectable;

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { routeInjectionToken } from "../../router/router.injectable";
import type { Route } from "../../router/router";
import { apiPrefix } from "../../../common/vars";
import { ResourceApplier } from "../../resource-applier";
const patchResourceRouteInjectable = getInjectable({
id: "patch-resource-route",
instantiate: (): Route<string> => ({
method: "patch",
path: `${apiPrefix}/stack`,
handler: async ({ cluster, payload }) => ({
response: await new ResourceApplier(cluster).patch(
payload.name,
payload.kind,
payload.patch,
payload.ns,
),
}),
}),
injectionToken: routeInjectionToken,
});
export default patchResourceRouteInjectable;

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { LensApiRequest, Route } from "../router/router";
import { contentTypes, SupportedFileExtension } from "../router/router-content-types";
import logger from "../logger";
import { routeInjectionToken } from "../router/router.injectable";
import { appName, publicPath } from "../../common/vars";
import path from "path";
import readFileInjectable from "../../common/fs/read-file.injectable";
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
import httpProxy from "http-proxy";
interface ProductionDependencies {
readFile: (path: string) => Promise<Buffer>;
}
const handleStaticFileInProduction =
({ readFile }: ProductionDependencies) =>
async ({ params }: LensApiRequest) => {
const staticPath = path.resolve(__static);
let filePath = params.path;
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
const asset = path.join(staticPath, filePath);
const normalizedFilePath = path.resolve(asset);
if (!normalizedFilePath.startsWith(staticPath)) {
return { statusCode: 404 };
}
try {
const fileExtension = path
.extname(asset)
.slice(1) as SupportedFileExtension;
const contentType = contentTypes[fileExtension] || contentTypes.txt;
return { response: await readFile(asset), contentType };
} catch (err) {
if (retryCount > 5) {
logger.error("handleStaticFile:", err.toString());
return { statusCode: 404 };
}
filePath = `${publicPath}/${appName}.html`;
}
}
return { statusCode: 404 };
};
interface DevelopmentDependencies {
proxy: httpProxy;
}
const handleStaticFileInDevelopment =
({ proxy }: DevelopmentDependencies) =>
(apiReq: LensApiRequest) => {
const { req, res } = apiReq.raw;
if (req.url === "/" || !req.url.startsWith("/build/")) {
req.url = `${publicPath}/${appName}.html`;
}
proxy.web(req, res, {
target: "http://127.0.0.1:8080",
});
return { proxy };
};
const staticFileRouteInjectable = getInjectable({
id: "static-file-route",
instantiate: (di): Route<Buffer> => {
const isDevelopment = di.inject(isDevelopmentInjectable);
return {
method: "get",
path: `/{path*}`,
handler: isDevelopment
? handleStaticFileInDevelopment({ proxy: httpProxy.createProxy() })
: handleStaticFileInProduction({ readFile: di.inject(readFileInjectable) }),
};
},
injectionToken: routeInjectionToken,
});
export default staticFileRouteInjectable;

View File

@ -1,16 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensApiRequest } from "../router";
import { respondJson } from "../utils/http-responses";
import { getAppVersion } from "../../common/utils";
export class VersionRoute {
static async getVersion(request: LensApiRequest) {
const { response } = request;
respondJson(response, { version: getAppVersion() }, 200);
}
}

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Route } from "../../router/router";
import { routeInjectionToken } from "../../router/router.injectable";
import { getAppVersion } from "../../../common/utils";
const getVersionRouteInjectable = getInjectable({
id: "get-version-route",
instantiate: (): Route<{ version: string }> => ({
method: "get",
path: `/version`,
handler: () => ({ response: { version: getAppVersion() }}),
}),
injectionToken: routeInjectionToken,
});
export default getVersionRouteInjectable;

View File

@ -58,7 +58,7 @@ const NonInjectedLogControls = observer(({ openSaveFileDialog, model }: Dependen
{since && (
<span>
Logs from{" "}
<b>{new Date(since[0]).toLocaleString()}</b>
<b>{new Date(since).toLocaleString()}</b>
</span>
)}
</div>

View File

@ -68,67 +68,76 @@ export class ItemListLayoutContent<I extends ItemObject> extends React.Component
return this.props.store.failedLoading;
}
@boundMethod
renderRow(item: I) {
return this.getTableRow(item);
}
getTableRow(item: I) {
const {
isSelectable, renderTableHeader, renderTableContents, renderItemMenu,
store, hasDetailsView, onDetails,
copyClassNameFromHeadCells, customizeTableRowProps, detailsItem,
} = this.props;
const { isSelected } = store;
return (
<TableRow
nowrap
searchItem={item}
sortItem={item}
selected={detailsItem && detailsItem.getId() === item.getId()}
onClick={hasDetailsView ? prevDefault(() => onDetails(item)) : undefined}
{...customizeTableRowProps(item)}
>
{isSelectable && (
<TableCell
checkbox
isChecked={isSelected(item)}
onClick={prevDefault(() => store.toggleSelection(item))}
/>
)}
{renderTableContents(item).map((content, index) => {
const cellProps: TableCellProps = isReactNode(content)
? { children: content }
: content;
const headCell = renderTableHeader?.[index];
if (copyClassNameFromHeadCells && headCell) {
cellProps.className = cssNames(
cellProps.className,
headCell.className,
);
}
if (!headCell || this.showColumn(headCell)) {
return <TableCell key={index} {...cellProps} />;
}
return null;
})}
{renderItemMenu && (
<TableCell className="menu">
<div onClick={stopPropagation}>
{renderItemMenu(item, store)}
</div>
</TableCell>
)}
</TableRow>
);
}
@boundMethod
getRow(uid: string) {
return (
<div key={uid}>
<Observer>
{() => {
const {
isSelectable, renderTableHeader, renderTableContents, renderItemMenu,
store, hasDetailsView, onDetails,
copyClassNameFromHeadCells, customizeTableRowProps, detailsItem,
} = this.props;
const { isSelected } = store;
const item = this.props.getItems().find(item => item.getId() == uid);
const item = this.props.getItems().find(item => item.getId() === uid);
if (!item) return null;
const itemId = item.getId();
return (
<TableRow
nowrap
searchItem={item}
sortItem={item}
selected={detailsItem && detailsItem.getId() === itemId}
onClick={hasDetailsView ? prevDefault(() => onDetails(item)) : undefined}
{...customizeTableRowProps(item)}
>
{isSelectable && (
<TableCell
checkbox
isChecked={isSelected(item)}
onClick={prevDefault(() => store.toggleSelection(item))}
/>
)}
{renderTableContents(item).map((content, index) => {
const cellProps: TableCellProps = isReactNode(content)
? { children: content }
: content;
const headCell = renderTableHeader?.[index];
if (copyClassNameFromHeadCells && headCell) {
cellProps.className = cssNames(
cellProps.className,
headCell.className,
);
}
if (!headCell || this.showColumn(headCell)) {
return <TableCell key={index} {...cellProps} />;
}
return null;
})}
{renderItemMenu && (
<TableCell className="menu">
<div onClick={stopPropagation}>
{renderItemMenu(item, store)}
</div>
</TableCell>
)}
</TableRow>
);
return this.getTableRow(item);
}}
</Observer>
</div>
@ -248,6 +257,7 @@ export class ItemListLayoutContent<I extends ItemObject> extends React.Component
selectable={hasDetailsView}
sortable={sortingCallbacks}
getTableRow={this.getRow}
renderRow={virtual ? undefined : this.renderRow}
items={items}
selectedItemId={selectedItemId}
noItems={this.renderNoItems()}

View File

@ -75,10 +75,6 @@ export const initClusterFrame =
});
});
window.addEventListener("online", () => {
window.location.reload();
});
window.onbeforeunload = () => {
logger.info(
`${logPrefix} Unload dashboard, clusterId=${(hostedCluster.id)}, frameId=${frameRoutingId}`,

342
yarn.lock
View File

@ -330,6 +330,11 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
@ -1083,7 +1088,7 @@
"@sentry/utils" "6.7.1"
tslib "^1.9.3"
"@sentry/types@6.15.0", "@sentry/types@^6.14.1":
"@sentry/types@6.15.0":
version "6.15.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.15.0.tgz#a2917f8aed91471bdfd6651384ffcd47b95c43ad"
integrity sha512-zBw5gPUsofXUSpS3ZAXqRNedLRBvirl3sqkj2Lez7X2EkKRgn5D8m9fQIrig/X3TsKcXUpijDW5Buk5zeCVzJA==
@ -1093,6 +1098,11 @@
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.7.1.tgz#c8263e1886df5e815570c4668eb40a1cfaa1c88b"
integrity sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA==
"@sentry/types@^6.18.2":
version "6.18.2"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.18.2.tgz#f528fec8b75c19d5a6976004e71703184c6cf7be"
integrity sha512-WzpJf/Q5aORTzrSwer/As1NlO90dBAQpaHV2ikDDKqOyMWEgjKb5/4gh59p9gH8JMMnLetP1AvQel0fOj5UnUw==
"@sentry/utils@6.15.0":
version "6.15.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.15.0.tgz#0c247cb092b1796d39c3d16d8e6977b9cdab9ca2"
@ -1227,6 +1237,11 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
@ -2031,10 +2046,10 @@
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.0.tgz#5cb0dff2a5f616fc8e0c61b482bf01fa20a03cec"
integrity sha512-ZAbqul7QAKpM2h1PFGa5ETN27ulmqtj0QviYHasw9LffvXZvVHuraOx/FOsIPPDNGZN0Qo1nASxxSfMYOtSoCw==
"@types/url-parse@^1.4.5":
version "1.4.5"
resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.5.tgz#bda0b813cc8f79d767dc8ca645fdabc238c6ae77"
integrity sha512-8Wje3itJpk/FX+QItca9vjNLjGx5jlEYBw/CpMi03Fphk2DSVeZDUqWTE81BeCI5Bl6Z+zmA1O9L/8e3ZUSeLg==
"@types/url-parse@^1.4.8":
version "1.4.8"
resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.8.tgz#c3825047efbca1295b7f1646f38203d9145130d6"
integrity sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==
"@types/uuid@^8.3.3":
version "8.3.3"
@ -2143,14 +2158,14 @@
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.1.tgz#870195d0f2146b36d11fc71131b75aba52354c69"
integrity sha512-xN3CYqFlyE/qOcy978/L0xLR2HlcAGIyIK5sMOasxaaAPfQRj/MmMV6OC3I7NZO84oEUdWCOju34Z9W8E0pFDQ==
"@typescript-eslint/eslint-plugin@^5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz#5119b67152356231a0e24b998035288a9cd21335"
integrity sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w==
dependencies:
"@typescript-eslint/scope-manager" "5.10.1"
"@typescript-eslint/type-utils" "5.10.1"
"@typescript-eslint/utils" "5.10.1"
"@typescript-eslint/scope-manager" "5.14.0"
"@typescript-eslint/type-utils" "5.14.0"
"@typescript-eslint/utils" "5.14.0"
debug "^4.3.2"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
@ -2176,12 +2191,20 @@
"@typescript-eslint/types" "5.10.1"
"@typescript-eslint/visitor-keys" "5.10.1"
"@typescript-eslint/type-utils@5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.1.tgz#5e526c00142585e40ab1503e83f1ff608c367405"
integrity sha512-AfVJkV8uck/UIoDqhu+ptEdBoQATON9GXnhOpPLzkQRJcSChkvD//qsz9JVffl2goxX+ybs5klvacE9vmrQyCw==
"@typescript-eslint/scope-manager@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz#ea518962b42db8ed0a55152ea959c218cb53ca7b"
integrity sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw==
dependencies:
"@typescript-eslint/utils" "5.10.1"
"@typescript-eslint/types" "5.14.0"
"@typescript-eslint/visitor-keys" "5.14.0"
"@typescript-eslint/type-utils@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz#711f08105860b12988454e91df433567205a8f0b"
integrity sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw==
dependencies:
"@typescript-eslint/utils" "5.14.0"
debug "^4.3.2"
tsutils "^3.21.0"
@ -2190,6 +2213,11 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.1.tgz#dca9bd4cb8c067fc85304a31f38ec4766ba2d1ea"
integrity sha512-ZvxQ2QMy49bIIBpTqFiOenucqUyjTQ0WNLhBM6X1fh1NNlYAC6Kxsx8bRTY3jdYsYg44a0Z/uEgQkohbR0H87Q==
"@typescript-eslint/types@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.14.0.tgz#96317cf116cea4befabc0defef371a1013f8ab11"
integrity sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw==
"@typescript-eslint/typescript-estree@5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.1.tgz#b268e67be0553f8790ba3fe87113282977adda15"
@ -2203,15 +2231,28 @@
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.10.1":
version "5.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.1.tgz#fa682a33af47080ba2c4368ee0ad2128213a1196"
integrity sha512-RRmlITiUbLuTRtn/gcPRi4202niF+q7ylFLCKu4c+O/PcpRvZ/nAUwQ2G00bZgpWkhrNLNnvhZLbDn8Ml0qsQw==
"@typescript-eslint/typescript-estree@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314"
integrity sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA==
dependencies:
"@typescript-eslint/types" "5.14.0"
"@typescript-eslint/visitor-keys" "5.14.0"
debug "^4.3.2"
globby "^11.0.4"
is-glob "^4.0.3"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.14.0.tgz#6c8bc4f384298cbbb32b3629ba7415f9f80dc8c4"
integrity sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.10.1"
"@typescript-eslint/types" "5.10.1"
"@typescript-eslint/typescript-estree" "5.10.1"
"@typescript-eslint/scope-manager" "5.14.0"
"@typescript-eslint/types" "5.14.0"
"@typescript-eslint/typescript-estree" "5.14.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
@ -2223,6 +2264,14 @@
"@typescript-eslint/types" "5.10.1"
eslint-visitor-keys "^3.0.0"
"@typescript-eslint/visitor-keys@5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz#1927005b3434ccd0d3ae1b2ecf60e65943c36986"
integrity sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw==
dependencies:
"@typescript-eslint/types" "5.14.0"
eslint-visitor-keys "^3.0.0"
"@webassemblyjs/ast@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
@ -2643,10 +2692,10 @@ app-builder-bin@3.7.1:
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.7.1.tgz#cb0825c5e12efc85b196ac3ed9c89f076c61040e"
integrity sha512-ql93vEUq6WsstGXD+SBLSIQw6SNnhbDEM0swzgugytMxLp3rT24Ag/jcC80ZHxiPRTdew1niuR7P3/FCrDqIjw==
app-builder-lib@22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.14.5.tgz#a61a50b132b858e98fdc70b6b88994ae99b4f96d"
integrity sha512-k3VwKP4kpsnUaXoUkm1s4zaSHPHIMFnN4kPMU9yXaKmE1LfHHqBaEah5bXeTAX5V/BC41wFdg8CF5vOjvgy8Rg==
app-builder-lib@22.14.13:
version "22.14.13"
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.14.13.tgz#c1f5b6afc86596357598bb90b69eef06c7c2eeb3"
integrity sha512-SufmrtxU+D0Tn948fjEwAOlCN9757UXLkzzTWXMwZKR/5hisvgqeeBepWfphMIE6OkDGz0fbzEhL1P2Pty4XMg==
dependencies:
"7zip-bin" "~5.1.1"
"@develar/schema-utils" "~2.6.5"
@ -2654,13 +2703,13 @@ app-builder-lib@22.14.5:
"@malept/flatpak-bundler" "^0.4.0"
async-exit-hook "^2.0.1"
bluebird-lst "^1.0.9"
builder-util "22.14.5"
builder-util-runtime "8.9.1"
builder-util "22.14.13"
builder-util-runtime "8.9.2"
chromium-pickle-js "^0.2.0"
debug "^4.3.2"
ejs "^3.1.6"
electron-osx-sign "^0.5.0"
electron-publish "22.14.5"
electron-publish "22.14.13"
form-data "^4.0.0"
fs-extra "^10.0.0"
hosted-git-info "^4.0.2"
@ -2847,10 +2896,10 @@ async@^2.6.2:
dependencies:
lodash "^4.17.14"
async@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
async@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
asynckit@^0.4.0:
version "0.4.0"
@ -3265,21 +3314,31 @@ builder-util-runtime@8.9.1:
debug "^4.3.2"
sax "^1.2.4"
builder-util@22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.14.5.tgz#42a18608d2a566c0846e91266464776c8bfb0cc9"
integrity sha512-zqIHDFJwmA7jV7SC9aI+33MWwT2mWoijH+Ol9IntNAwuuRXoS+7XeJwnhLBXOhcDBzXT4kDzHnRk4JKeaygEYA==
builder-util-runtime@8.9.2:
version "8.9.2"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.9.2.tgz#a9669ae5b5dcabfe411ded26678e7ae997246c28"
integrity sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==
dependencies:
debug "^4.3.2"
sax "^1.2.4"
builder-util@22.14.13:
version "22.14.13"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.14.13.tgz#41b5b7b4ee53aff4e09cc007fb144522598f3ce6"
integrity sha512-oePC/qrrUuerhmH5iaCJzPRAKlSBylrhzuAJmRQClTyWnZUv6jbaHh+VoHMbEiE661wrj2S2aV7/bQh12cj1OA==
dependencies:
"7zip-bin" "~5.1.1"
"@types/debug" "^4.1.6"
"@types/fs-extra" "^9.0.11"
app-builder-bin "3.7.1"
bluebird-lst "^1.0.9"
builder-util-runtime "8.9.1"
builder-util-runtime "8.9.2"
chalk "^4.1.1"
cross-spawn "^7.0.3"
debug "^4.3.2"
fs-extra "^10.0.0"
http-proxy-agent "^5.0.0"
https-proxy-agent "^5.0.0"
is-ci "^3.0.0"
js-yaml "^4.1.0"
source-map-support "^0.5.19"
@ -3820,7 +3879,7 @@ colors@1.0.3:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
colors@^1.1.2, colors@^1.2.1, colors@^1.4.0:
colors@^1.1.2, colors@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
@ -4174,18 +4233,18 @@ css-box-model@^1.2.0:
dependencies:
tiny-invariant "^1.0.6"
css-loader@^6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.5.1.tgz#0c43d4fbe0d97f699c91e9818cb585759091d1b1"
integrity sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ==
css-loader@^6.7.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e"
integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==
dependencies:
icss-utils "^5.1.0"
postcss "^8.2.15"
postcss "^8.4.7"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.1.0"
postcss-value-parser "^4.2.0"
semver "^7.3.5"
css-parse@~2.0.0:
@ -4606,14 +4665,14 @@ dlv@^1.1.3:
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
dmg-builder@22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.14.5.tgz#137c0b55e639badcc0b119eb060e6fa4ed61d948"
integrity sha512-1GvFGQE332bvPamcMwZDqWqfWfJTyyDLOsHMcGi0zs+Jh7JOn6/zuBkHJIWHdsj2QJbhzLVyd2/ZqttOKv7I8w==
dmg-builder@22.14.13:
version "22.14.13"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.14.13.tgz#cc613f3c18e889b8777d525991fd52f50a564f8c"
integrity sha512-xNOugB6AbIRETeU2uID15sUfjdZZcKdxK8xkFnwIggsM00PJ12JxpLNPTjcRoUnfwj3WrPjilrO64vRMwNItQg==
dependencies:
app-builder-lib "22.14.5"
builder-util "22.14.5"
builder-util-runtime "8.9.1"
app-builder-lib "22.14.13"
builder-util "22.14.13"
builder-util-runtime "8.9.2"
fs-extra "^10.0.0"
iconv-lite "^0.6.2"
js-yaml "^4.1.0"
@ -4818,17 +4877,17 @@ ejs@^3.1.6:
dependencies:
jake "^10.6.1"
electron-builder@^22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.14.5.tgz#3a25547bd4fe3728d4704da80956a794c5c31496"
integrity sha512-N73hSbXFz6Mz5Z6h6C5ly6CB+dUN6k1LuCDJjI8VF47bMXv/QE0HE+Kkb0GPKqTqM7Hsk/yIYX+kHCfSkR5FGg==
electron-builder@^22.14.13:
version "22.14.13"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.14.13.tgz#fd40564685cf5422a8f8d667940af3d3776f4fb8"
integrity sha512-3fgLxqF2TXVKiUPeg74O4V3l0l3j7ERLazo8sUbRkApw0+4iVAf2BJkHsHMaXiigsgCoEzK/F4/rB5rne/VAnw==
dependencies:
"@types/yargs" "^17.0.1"
app-builder-lib "22.14.5"
builder-util "22.14.5"
builder-util-runtime "8.9.1"
app-builder-lib "22.14.13"
builder-util "22.14.13"
builder-util-runtime "8.9.2"
chalk "^4.1.1"
dmg-builder "22.14.5"
dmg-builder "22.14.13"
fs-extra "^10.0.0"
is-ci "^3.0.0"
lazy-val "^1.0.5"
@ -4866,14 +4925,14 @@ electron-osx-sign@^0.5.0:
minimist "^1.2.0"
plist "^3.0.1"
electron-publish@22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.14.5.tgz#34bcdce671f0e651330db20040d6919c77c94bd6"
integrity sha512-h+NANRdaA0PqGF15GKvorseWPzh1PXa/zx4I37//PIokW8eKIov8ky23foUSb55ZFWUHGpxQJux7y2NCfBtQeg==
electron-publish@22.14.13:
version "22.14.13"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.14.13.tgz#8b71e6975af8cc6ac5b21f293ade23f8704047c7"
integrity sha512-0oP3QiNj3e8ewOaEpEJV/o6Zrmy2VarVvZ/bH7kyO/S/aJf9x8vQsKVWpsdmSiZ5DJEHgarFIXrnO0ZQf0P9iQ==
dependencies:
"@types/fs-extra" "^9.0.11"
builder-util "22.14.5"
builder-util-runtime "8.9.1"
builder-util "22.14.13"
builder-util-runtime "8.9.2"
chalk "^4.1.1"
fs-extra "^10.0.0"
lazy-val "^1.0.5"
@ -5852,11 +5911,6 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-safe-stringify@^2.0.4:
version "2.0.7"
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
fastest-levenshtein@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
@ -6827,6 +6881,15 @@ http-proxy-agent@^4.0.1:
agent-base "6"
debug "4"
http-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
dependencies:
"@tootallnate/once" "2"
agent-base "6"
debug "4"
http-proxy-middleware@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.2.tgz#94d7593790aad6b3de48164f13792262f656c332"
@ -6973,12 +7036,7 @@ ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
ignore@^5.1.4, ignore@^5.1.8:
version "5.1.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
ignore@^5.2.0:
ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
@ -8822,15 +8880,15 @@ lodash@4.x, lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15,
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
logform@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2"
integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==
logform@^2.2.0, logform@^2.3.2, logform@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.0.tgz#131651715a17d50f09c2a2c1a524ff1a4164bcfe"
integrity sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==
dependencies:
colors "^1.2.1"
fast-safe-stringify "^2.0.4"
"@colors/colors" "1.5.0"
fecha "^4.2.0"
ms "^2.1.1"
safe-stable-stringify "^2.3.1"
triple-beam "^1.3.0"
loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
@ -9042,6 +9100,11 @@ merge2@^1.3.0:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
mergee@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mergee/-/mergee-1.0.0.tgz#027c5addc650f6ecbe4bf56100bd00dae763fda7"
integrity sha512-hbbXD4LOcxVkpS+mp3BMEhkSDf+lTVENFeEeqACgjjL8WrgKuW2EyLT0fOHyTbyDiuRLZJZ1HrHNeiX4iOd79Q==
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@ -9285,6 +9348,13 @@ mock-fs@^5.1.2:
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.1.2.tgz#6fa486e06d00f8793a8d2228de980eff93ce6db7"
integrity sha512-YkjQkdLulFrz0vD4BfNQdQRVmgycXTV7ykuHMlyv+C8WCHazpkiQRDthwa02kSyo8wKnY9wRptHfQLgmf0eR+A==
mock-http@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mock-http/-/mock-http-1.1.0.tgz#b89380a718a103fc5801095804bedd0b20f7638c"
integrity sha512-H2HMGaHNQPWY8PdeEw4RFux2WEOHD6eJAtN3+iFELik5kGjPKAcoyPWcsC2vgDiTa2yimAEDssmMed51e+cBKQ==
dependencies:
mergee "^1.0.0"
moment-timezone@^0.5.34:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
@ -9366,10 +9436,10 @@ nan@^2.14.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nanoid@^3.1.30:
version "3.2.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
nanoid@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
nanomatch@^1.2.9:
version "1.2.13"
@ -9412,12 +9482,7 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
neo-async@^2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
neo-async@^2.6.2:
neo-async@^2.6.0, neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
@ -10692,12 +10757,7 @@ postcss-value-parser@^3.3.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss-value-parser@^4.2.0:
postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@ -10711,14 +10771,14 @@ postcss@^6.0.14, postcss@^6.0.2:
source-map "^0.6.1"
supports-color "^5.4.0"
postcss@^8.1.6, postcss@^8.2.15, postcss@^8.3.0, postcss@^8.4.5:
version "8.4.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95"
integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==
postcss@^8.1.6, postcss@^8.3.0, postcss@^8.4.5, postcss@^8.4.7:
version "8.4.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==
dependencies:
nanoid "^3.1.30"
nanoid "^3.3.1"
picocolors "^1.0.0"
source-map-js "^1.0.1"
source-map-js "^1.0.2"
prebuild-install@^7.0.0:
version "7.0.0"
@ -11120,10 +11180,10 @@ react-router@5.2.1, react-router@^5.2.0:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-select-event@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/react-select-event/-/react-select-event-5.1.0.tgz#d45ef68f2a9c872903e8c9725f3ae6e7576f7be0"
integrity sha512-D5DzJlYCdZsGbDVFMQFynrG0OLalJM3ZzDT7KQADNVWE604JCeQF9bIuvPZqVD7IzhnPsFzOUCsilzDA6w6WRQ==
react-select-event@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/react-select-event/-/react-select-event-5.3.0.tgz#4548fffd615a47176951cbb301ee21a0c60b582a"
integrity sha512-Novkl7X9JJKmDV5LyYaKwl0vffWtqPrBa1vuI0v43P/f87mSA7JfdYxU93SFb99RssphVzBSIAbcnbX1w21QIQ==
dependencies:
"@testing-library/dom" ">=7"
@ -11276,7 +11336,7 @@ read@1, read@~1.0.1, read@~1.0.7:
dependencies:
mute-stream "~0.0.4"
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6:
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@ -11703,6 +11763,11 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
safe-stable-stringify@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73"
integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@^2.1.2, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@ -11730,10 +11795,10 @@ sanitize-filename@^1.6.2, sanitize-filename@^1.6.3:
dependencies:
truncate-utf8-bytes "^1.0.0"
sass-loader@^12.4.0:
version "12.4.0"
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.4.0.tgz#260b0d51a8a373bb8e88efc11f6ba5583fea0bcf"
integrity sha512-7xN+8khDIzym1oL9XyS6zP6Ges+Bo2B2xbPrjdMHEYyV3AQYhd/wXeru++3ODHF0zMjYmVadblSKrPrjEkL8mg==
sass-loader@^12.6.0:
version "12.6.0"
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb"
integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==
dependencies:
klona "^2.0.4"
neo-async "^2.6.2"
@ -12173,11 +12238,16 @@ source-list-map@^2.0.1:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1:
"source-map-js@>=0.6.2 <2.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
version "0.5.3"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
@ -13024,7 +13094,7 @@ tree-kill@^1.2.2:
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
triple-beam@^1.2.0, triple-beam@^1.3.0:
triple-beam@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
@ -13193,10 +13263,10 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-fest@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.0.2.tgz#3f9c39982859f385c77c38b7e5f1432b8a3661c6"
integrity sha512-a720oz3Kjbp3ll0zkeN9qjRhO7I34MKMhPGQiQJAmaZQZQ1lo+NWThK322f7sXV+kTg9B1Ybt16KgBXWgteT8w==
type-fest@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
type-is@~1.6.18:
version "1.6.18"
@ -13899,28 +13969,30 @@ winston-transport-browserconsole@^1.0.5:
winston "^3.2.1"
winston-transport "^4.3.0"
winston-transport@^4.3.0, winston-transport@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59"
integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==
winston-transport@^4.3.0, winston-transport@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa"
integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==
dependencies:
readable-stream "^2.3.7"
triple-beam "^1.2.0"
logform "^2.3.2"
readable-stream "^3.6.0"
triple-beam "^1.3.0"
winston@^3.2.1, winston@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170"
integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==
winston@^3.2.1, winston@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/winston/-/winston-3.6.0.tgz#be32587a099a292b88c49fac6fa529d478d93fb6"
integrity sha512-9j8T75p+bcN6D00sF/zjFVmPp+t8KMPB1MzbbzYjeN9VWxdsYnTB40TkbNUEXAmILEfChMvAMgidlX64OG3p6w==
dependencies:
"@dabh/diagnostics" "^2.0.2"
async "^3.1.0"
async "^3.2.3"
is-stream "^2.0.0"
logform "^2.2.0"
logform "^2.4.0"
one-time "^1.0.0"
readable-stream "^3.4.0"
safe-stable-stringify "^2.3.1"
stack-trace "0.0.x"
triple-beam "^1.3.0"
winston-transport "^4.4.0"
winston-transport "^4.5.0"
word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3"