mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into improve-dock-tabs-ux
This commit is contained in:
commit
00121fd044
15
Makefile
15
Makefile
@ -21,10 +21,7 @@ node_modules: yarn.lock
|
||||
yarn check --verify-tree --integrity
|
||||
|
||||
binaries/client: node_modules
|
||||
yarn download-bins
|
||||
|
||||
static/build/LensDev.html: node_modules
|
||||
yarn compile:renderer
|
||||
yarn download:binaries
|
||||
|
||||
.PHONY: compile-dev
|
||||
compile-dev: node_modules
|
||||
@ -32,7 +29,8 @@ compile-dev: node_modules
|
||||
yarn compile:renderer --cache
|
||||
|
||||
.PHONY: dev
|
||||
dev: binaries/client build-extensions static/build/LensDev.html
|
||||
dev: binaries/client build-extensions
|
||||
rm -rf static/build/
|
||||
yarn dev
|
||||
|
||||
.PHONY: lint
|
||||
@ -62,10 +60,11 @@ ifeq "$(DETECTED_OS)" "Windows"
|
||||
endif
|
||||
yarn run electron-builder --publish onTag $(ELECTRON_BUILDER_EXTRA_ARGS)
|
||||
|
||||
.NOTPARALLEL: $(extension_node_modules)
|
||||
$(extension_node_modules): node_modules
|
||||
cd $(@:/node_modules=) && ../../node_modules/.bin/npm install --no-audit --no-fund
|
||||
cd $(@:/node_modules=) && ../../node_modules/.bin/npm install --no-audit --no-fund --no-save
|
||||
|
||||
$(extension_dists): src/extensions/npm/extensions/dist
|
||||
$(extension_dists): src/extensions/npm/extensions/dist $(extension_node_modules)
|
||||
cd $(@:/dist=) && ../../node_modules/.bin/npm run build
|
||||
|
||||
.PHONY: clean-old-extensions
|
||||
@ -73,7 +72,7 @@ clean-old-extensions:
|
||||
find ./extensions -mindepth 1 -maxdepth 1 -type d '!' -exec test -e '{}/package.json' \; -exec rm -rf {} \;
|
||||
|
||||
.PHONY: build-extensions
|
||||
build-extensions: node_modules clean-old-extensions $(extension_node_modules) $(extension_dists)
|
||||
build-extensions: node_modules clean-old-extensions $(extension_dists)
|
||||
|
||||
.PHONY: test-extensions
|
||||
test-extensions: $(extension_node_modules)
|
||||
|
||||
235
build/download_binaries.ts
Normal file
235
build/download_binaries.ts
Normal file
@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import packageInfo from "../package.json";
|
||||
import { type WriteStream } from "fs";
|
||||
import { FileHandle, open } from "fs/promises";
|
||||
import { constants, ensureDir, unlink } from "fs-extra";
|
||||
import path from "path";
|
||||
import fetch from "node-fetch";
|
||||
import { promisify } from "util";
|
||||
import { pipeline as _pipeline, Transform, Writable } from "stream";
|
||||
import { MultiBar, SingleBar } from "cli-progress";
|
||||
import AbortController from "abort-controller";
|
||||
import { extract } from "tar-stream";
|
||||
import gunzip from "gunzip-maybe";
|
||||
import { getBinaryName, normalizedPlatform } from "../src/common/vars";
|
||||
|
||||
const pipeline = promisify(_pipeline);
|
||||
|
||||
interface BinaryDownloaderArgs {
|
||||
readonly version: string;
|
||||
readonly platform: SupportedPlatform;
|
||||
readonly downloadArch: string;
|
||||
readonly fileArch: string;
|
||||
readonly binaryName: string;
|
||||
readonly baseDir: string;
|
||||
}
|
||||
|
||||
abstract class BinaryDownloader {
|
||||
protected abstract readonly url: string;
|
||||
protected readonly bar: SingleBar;
|
||||
protected readonly target: string;
|
||||
|
||||
protected getTransformStreams(file: Writable): (NodeJS.ReadWriteStream | NodeJS.WritableStream)[] {
|
||||
return [file];
|
||||
}
|
||||
|
||||
constructor(public readonly args: BinaryDownloaderArgs, multiBar: MultiBar) {
|
||||
this.bar = multiBar.create(1, 0, args);
|
||||
this.target = path.join(args.baseDir, args.platform, args.fileArch, args.binaryName);
|
||||
}
|
||||
|
||||
async ensureBinary(): Promise<void> {
|
||||
const controller = new AbortController();
|
||||
const stream = await fetch(this.url, {
|
||||
timeout: 15 * 60 * 1000, // 15min
|
||||
signal: controller.signal,
|
||||
});
|
||||
const total = Number(stream.headers.get("content-length"));
|
||||
const bar = this.bar;
|
||||
let fileHandle: FileHandle;
|
||||
|
||||
if (isNaN(total)) {
|
||||
throw new Error("no content-length header was present");
|
||||
}
|
||||
|
||||
bar.setTotal(total);
|
||||
|
||||
await ensureDir(path.dirname(this.target), 0o755);
|
||||
|
||||
try {
|
||||
/**
|
||||
* This is necessary because for some reason `createWriteStream({ flags: "wx" })`
|
||||
* was throwing someplace else and not here
|
||||
*/
|
||||
fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
|
||||
|
||||
await pipeline(
|
||||
stream.body,
|
||||
new Transform({
|
||||
transform(chunk, encoding, callback) {
|
||||
bar.increment(chunk.length);
|
||||
this.push(chunk);
|
||||
callback();
|
||||
},
|
||||
}),
|
||||
...this.getTransformStreams(new Writable({
|
||||
write(chunk, encoding, cb) {
|
||||
fileHandle.write(chunk)
|
||||
.then(() => cb())
|
||||
.catch(cb);
|
||||
},
|
||||
})),
|
||||
);
|
||||
await fileHandle.chmod(0o755);
|
||||
await fileHandle.close();
|
||||
} catch (error) {
|
||||
await fileHandle?.close();
|
||||
|
||||
if (error.code === "EEXIST") {
|
||||
bar.increment(total); // mark as finished
|
||||
controller.abort(); // stop trying to download
|
||||
} else {
|
||||
await unlink(this.target);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LensK8sProxyDownloader extends BinaryDownloader {
|
||||
protected readonly url: string;
|
||||
|
||||
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||
const binaryName = getBinaryName("lens-k8s-proxy", { forPlatform: args.platform });
|
||||
|
||||
super({ ...args, binaryName }, bar);
|
||||
this.url = `https://github.com/lensapp/lens-k8s-proxy/releases/download/v${args.version}/lens-k8s-proxy-${args.platform}-${args.downloadArch}`;
|
||||
}
|
||||
}
|
||||
|
||||
class KubectlDownloader extends BinaryDownloader {
|
||||
protected readonly url: string;
|
||||
|
||||
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||
const binaryName = getBinaryName("kubectl", { forPlatform: args.platform });
|
||||
|
||||
super({ ...args, binaryName }, bar);
|
||||
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${args.version}/bin/${args.platform}/${args.downloadArch}/${binaryName}`;
|
||||
}
|
||||
}
|
||||
|
||||
class HelmDownloader extends BinaryDownloader {
|
||||
protected readonly url: string;
|
||||
|
||||
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||
const binaryName = getBinaryName("helm", { forPlatform: args.platform });
|
||||
|
||||
super({ ...args, binaryName }, bar);
|
||||
this.url = `https://get.helm.sh/helm-v${args.version}-${args.platform}-${args.downloadArch}.tar.gz`;
|
||||
}
|
||||
|
||||
protected getTransformStreams(file: WriteStream) {
|
||||
const extracting = extract({
|
||||
allowUnknownFormat: false,
|
||||
});
|
||||
|
||||
extracting.on("entry", (headers, stream, next) => {
|
||||
if (headers.name.endsWith(this.args.binaryName)) {
|
||||
stream
|
||||
.pipe(file)
|
||||
.once("finish", () => next())
|
||||
.once("error", next);
|
||||
} else {
|
||||
stream.resume();
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
return [gunzip(3), extracting];
|
||||
}
|
||||
}
|
||||
|
||||
type SupportedPlatform = "darwin" | "linux" | "windows";
|
||||
|
||||
async function main() {
|
||||
const multiBar = new MultiBar({
|
||||
align: "left",
|
||||
clearOnComplete: false,
|
||||
hideCursor: true,
|
||||
autopadding: true,
|
||||
noTTYOutput: true,
|
||||
format: "[{bar}] {percentage}% | {downloadArch} {binaryName}",
|
||||
});
|
||||
const baseDir = path.join(__dirname, "..", "binaries", "client");
|
||||
const downloaders: BinaryDownloader[] = [
|
||||
new LensK8sProxyDownloader({
|
||||
version: packageInfo.config.k8sProxyVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "amd64",
|
||||
fileArch: "x64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
new KubectlDownloader({
|
||||
version: packageInfo.config.bundledKubectlVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "amd64",
|
||||
fileArch: "x64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
new HelmDownloader({
|
||||
version: packageInfo.config.bundledHelmVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "amd64",
|
||||
fileArch: "x64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
];
|
||||
|
||||
if (normalizedPlatform === "darwin") {
|
||||
downloaders.push(
|
||||
new LensK8sProxyDownloader({
|
||||
version: packageInfo.config.k8sProxyVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "arm64",
|
||||
fileArch: "arm64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
new KubectlDownloader({
|
||||
version: packageInfo.config.bundledKubectlVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "arm64",
|
||||
fileArch: "arm64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
new HelmDownloader({
|
||||
version: packageInfo.config.bundledHelmVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "arm64",
|
||||
fileArch: "arm64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
);
|
||||
}
|
||||
|
||||
const settledResults = await Promise.allSettled(downloaders.map(downloader => (
|
||||
downloader.ensureBinary()
|
||||
.catch(error => {
|
||||
throw new Error(`Failed to download ${downloader.args.binaryName} for ${downloader.args.platform}/${downloader.args.downloadArch}: ${error}`);
|
||||
})
|
||||
)));
|
||||
|
||||
multiBar.stop();
|
||||
const errorResult = settledResults.find(res => res.status === "rejected") as PromiseRejectedResult | undefined;
|
||||
|
||||
if (errorResult) {
|
||||
console.error("234", String(errorResult.reason));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch(error => console.error("from main", error));
|
||||
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import packageInfo from "../package.json";
|
||||
import { isWindows } from "../src/common/vars";
|
||||
import { HelmCli } from "../src/main/helm/helm-cli";
|
||||
import * as path from "path";
|
||||
|
||||
const helmVersion = packageInfo.config.bundledHelmVersion;
|
||||
|
||||
if (!isWindows) {
|
||||
Promise.all([
|
||||
new HelmCli(path.join(process.cwd(), "binaries", "client", "x64"), helmVersion).ensureBinary(),
|
||||
new HelmCli(path.join(process.cwd(), "binaries", "client", "arm64"), helmVersion).ensureBinary(),
|
||||
]);
|
||||
} else {
|
||||
new HelmCli(path.join(process.cwd(), "binaries", "client", "x64"), helmVersion).ensureBinary();
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import packageInfo from "../package.json";
|
||||
import fs from "fs";
|
||||
import request from "request";
|
||||
import { ensureDir, pathExists } from "fs-extra";
|
||||
import path from "path";
|
||||
import { noop } from "lodash";
|
||||
import { isLinux, isMac } from "../src/common/vars";
|
||||
|
||||
class K8sProxyDownloader {
|
||||
public version: string;
|
||||
protected url: string;
|
||||
protected path: string;
|
||||
protected dirname: string;
|
||||
|
||||
constructor(version: string, platform: string, arch: string, target: string) {
|
||||
this.version = version;
|
||||
this.url = `https://github.com/lensapp/lens-k8s-proxy/releases/download/v${this.version}/lens-k8s-proxy-${platform}-${arch}`;
|
||||
this.dirname = path.dirname(target);
|
||||
this.path = target;
|
||||
}
|
||||
|
||||
public async checkBinary() {
|
||||
const exists = await pathExists(this.path);
|
||||
|
||||
if (exists) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async download() {
|
||||
if (await this.checkBinary()) {
|
||||
return console.log("Already exists");
|
||||
}
|
||||
|
||||
await ensureDir(path.dirname(this.path), 0o755);
|
||||
|
||||
const file = fs.createWriteStream(this.path);
|
||||
|
||||
console.log(`Downloading lens-k8s-proxy ${this.version} from ${this.url} to ${this.path}`);
|
||||
const requestOpts: request.UriOptions & request.CoreOptions = {
|
||||
uri: this.url,
|
||||
gzip: true,
|
||||
followAllRedirects: true,
|
||||
};
|
||||
const stream = request(requestOpts);
|
||||
|
||||
stream.on("complete", () => {
|
||||
console.log("lens-k8s-proxy binary download finished");
|
||||
file.end(noop);
|
||||
});
|
||||
|
||||
stream.on("error", (error) => {
|
||||
console.log(error);
|
||||
fs.unlink(this.path, noop);
|
||||
throw error;
|
||||
});
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
file.on("close", () => {
|
||||
console.log("lens-k8s-proxy binary download closed");
|
||||
fs.chmod(this.path, 0o755, (err) => {
|
||||
if (err) reject(err);
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
stream.pipe(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
const downloadVersion = packageInfo.config.k8sProxyVersion;
|
||||
const baseDir = path.join(__dirname, "..", "binaries", "client");
|
||||
|
||||
const downloads = [];
|
||||
|
||||
if (isMac) {
|
||||
downloads.push({ platform: "darwin", arch: "amd64", target: path.join(baseDir, "darwin", "x64", "lens-k8s-proxy") });
|
||||
downloads.push({ platform: "darwin", arch: "arm64", target: path.join(baseDir, "darwin", "arm64", "lens-k8s-proxy") });
|
||||
} else if (isLinux) {
|
||||
downloads.push({ platform: "linux", arch: "amd64", target: path.join(baseDir, "linux", "x64", "lens-k8s-proxy") });
|
||||
downloads.push({ platform: "linux", arch: "arm64", target: path.join(baseDir, "linux", "arm64", "lens-k8s-proxy") });
|
||||
} else {
|
||||
downloads.push({ platform: "windows", arch: "amd64", target: path.join(baseDir, "windows", "x64", "lens-k8s-proxy.exe") });
|
||||
downloads.push({ platform: "windows", arch: "386", target: path.join(baseDir, "windows", "ia32", "lens-k8s-proxy.exe") });
|
||||
}
|
||||
|
||||
downloads.forEach((dlOpts) => {
|
||||
console.log(dlOpts);
|
||||
const downloader = new K8sProxyDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target);
|
||||
|
||||
console.log(`Downloading: ${JSON.stringify(dlOpts)}`);
|
||||
downloader.download().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
|
||||
});
|
||||
@ -1,125 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import packageInfo from "../package.json";
|
||||
import fs from "fs";
|
||||
import request from "request";
|
||||
import md5File from "md5-file";
|
||||
import requestPromise from "request-promise-native";
|
||||
import { ensureDir, pathExists } from "fs-extra";
|
||||
import path from "path";
|
||||
import { noop } from "lodash";
|
||||
import { isLinux, isMac } from "../src/common/vars";
|
||||
|
||||
class KubectlDownloader {
|
||||
public kubectlVersion: string;
|
||||
protected url: string;
|
||||
protected path: string;
|
||||
protected dirname: string;
|
||||
|
||||
constructor(clusterVersion: string, platform: string, arch: string, target: string) {
|
||||
this.kubectlVersion = clusterVersion;
|
||||
const binaryName = platform === "windows" ? "kubectl.exe" : "kubectl";
|
||||
|
||||
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${this.kubectlVersion}/bin/${platform}/${arch}/${binaryName}`;
|
||||
this.dirname = path.dirname(target);
|
||||
this.path = target;
|
||||
}
|
||||
|
||||
protected async urlEtag() {
|
||||
const response = await requestPromise({
|
||||
method: "HEAD",
|
||||
uri: this.url,
|
||||
resolveWithFullResponse: true,
|
||||
}).catch(console.error);
|
||||
|
||||
if (response.headers["etag"]) {
|
||||
return response.headers["etag"].replace(/"/g, "");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public async checkBinary() {
|
||||
const exists = await pathExists(this.path);
|
||||
|
||||
if (exists) {
|
||||
const hash = md5File.sync(this.path);
|
||||
const etag = await this.urlEtag();
|
||||
|
||||
if (hash == etag) {
|
||||
console.log("Kubectl md5sum matches the remote etag");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`Kubectl md5sum ${hash} does not match the remote etag ${etag}, unlinking and downloading again`);
|
||||
await fs.promises.unlink(this.path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async downloadKubectl() {
|
||||
if (await this.checkBinary()) {
|
||||
return console.log("Already exists and is valid");
|
||||
}
|
||||
|
||||
await ensureDir(path.dirname(this.path), 0o755);
|
||||
|
||||
const file = fs.createWriteStream(this.path);
|
||||
|
||||
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
||||
const requestOpts: request.UriOptions & request.CoreOptions = {
|
||||
uri: this.url,
|
||||
gzip: true,
|
||||
};
|
||||
const stream = request(requestOpts);
|
||||
|
||||
stream.on("complete", () => {
|
||||
console.log("kubectl binary download finished");
|
||||
file.end(noop);
|
||||
});
|
||||
|
||||
stream.on("error", (error) => {
|
||||
console.log(error);
|
||||
fs.unlink(this.path, noop);
|
||||
throw error;
|
||||
});
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
file.on("close", () => {
|
||||
console.log("kubectl binary download closed");
|
||||
fs.chmod(this.path, 0o755, (err) => {
|
||||
if (err) reject(err);
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
stream.pipe(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
const downloadVersion = packageInfo.config.bundledKubectlVersion;
|
||||
const baseDir = path.join(__dirname, "..", "binaries", "client");
|
||||
|
||||
const downloads = [];
|
||||
|
||||
if (isMac) {
|
||||
downloads.push({ platform: "darwin", arch: "amd64", target: path.join(baseDir, "darwin", "x64", "kubectl") });
|
||||
downloads.push({ platform: "darwin", arch: "arm64", target: path.join(baseDir, "darwin", "arm64", "kubectl") });
|
||||
} else if (isLinux) {
|
||||
downloads.push({ platform: "linux", arch: "amd64", target: path.join(baseDir, "linux", "x64", "kubectl") });
|
||||
downloads.push({ platform: "linux", arch: "arm64", target: path.join(baseDir, "linux", "arm64", "kubectl") });
|
||||
} else {
|
||||
downloads.push({ platform: "windows", arch: "amd64", target: path.join(baseDir, "windows", "x64", "kubectl.exe") });
|
||||
downloads.push({ platform: "windows", arch: "386", target: path.join(baseDir, "windows", "ia32", "kubectl.exe") });
|
||||
}
|
||||
|
||||
downloads.forEach((dlOpts) => {
|
||||
console.log(dlOpts);
|
||||
const downloader = new KubectlDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target);
|
||||
|
||||
console.log(`Downloading: ${JSON.stringify(dlOpts)}`);
|
||||
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
|
||||
});
|
||||
2424
extensions/kube-object-event-status/package-lock.json
generated
2424
extensions/kube-object-event-status/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,18 +8,15 @@
|
||||
"styles": []
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack && npm pack",
|
||||
"dev": "webpack --watch",
|
||||
"build": "npx webpack && npm pack",
|
||||
"dev": "npx webpack -- --watch",
|
||||
"test": "echo NO TESTS"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"ts-loader": "latest",
|
||||
"typescript": "latest",
|
||||
"webpack": "latest"
|
||||
"npm": "^8.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
5234
extensions/metrics-cluster-feature/package-lock.json
generated
5234
extensions/metrics-cluster-feature/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,9 +8,9 @@
|
||||
"styles": []
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack && npm pack",
|
||||
"dev": "webpack --watch",
|
||||
"test": "jest --passWithNoTests --env=jsdom src $@",
|
||||
"build": "npx webpack && npm pack",
|
||||
"dev": "npx webpack -- --watch",
|
||||
"test": "npx jest --passWithNoTests --env=jsdom src $@",
|
||||
"clean": "rm -rf dist/ && rm *.tgz"
|
||||
},
|
||||
"files": [
|
||||
@ -19,10 +19,7 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"semver": "^7.3.2",
|
||||
"jest": "latest",
|
||||
"ts-loader": "latest",
|
||||
"typescript": "latest",
|
||||
"webpack": "latest"
|
||||
"npm": "^8.5.3",
|
||||
"semver": "^7.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
5205
extensions/node-menu/package-lock.json
generated
5205
extensions/node-menu/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,9 +8,9 @@
|
||||
"styles": []
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack && npm pack",
|
||||
"dev": "webpack --watch",
|
||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
||||
"build": "npx webpack && npm pack",
|
||||
"dev": "npx webpack -- --watch",
|
||||
"test": "npx jest --passWithNoTests --env=jsdom src $@"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
@ -18,9 +18,6 @@
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"jest": "latest",
|
||||
"ts-loader": "latest",
|
||||
"typescript": "latest",
|
||||
"webpack": "latest"
|
||||
"npm": "^8.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
5205
extensions/pod-menu/package-lock.json
generated
5205
extensions/pod-menu/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,9 +8,9 @@
|
||||
"styles": []
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack && npm pack",
|
||||
"dev": "webpack --watch",
|
||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
||||
"build": "npx webpack && npm pack",
|
||||
"dev": "npx webpack -- --watch",
|
||||
"test": "npx jest --passWithNoTests --env=jsdom src $@"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
@ -18,9 +18,6 @@
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"jest": "latest",
|
||||
"ts-loader": "latest",
|
||||
"typescript": "latest",
|
||||
"webpack": "latest"
|
||||
"npm": "^8.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
52
package.json
52
package.json
@ -15,10 +15,9 @@
|
||||
"dev": "concurrently -i -k \"yarn run dev-run -C\" yarn:dev:*",
|
||||
"dev-build": "concurrently yarn:compile:*",
|
||||
"debug-build": "concurrently yarn:compile:main yarn:compile:extension-types",
|
||||
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"",
|
||||
"dev-run": "nodemon --watch ./static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"",
|
||||
"dev:main": "yarn run compile:main --watch --progress",
|
||||
"dev:renderer": "yarn run compile:renderer --watch --progress",
|
||||
"dev:extension-types": "yarn run compile:extension-types --watch --progress",
|
||||
"dev:renderer": "yarn run ts-node webpack.dev-server.ts",
|
||||
"compile": "env NODE_ENV=production concurrently yarn:compile:*",
|
||||
"compile:main": "yarn run webpack --config webpack.main.ts",
|
||||
"compile:renderer": "yarn run webpack --config webpack.renderer.ts",
|
||||
@ -31,10 +30,7 @@
|
||||
"integration": "jest --runInBand --detectOpenHandles --forceExit integration",
|
||||
"dist": "yarn run compile && electron-builder --publish onTag",
|
||||
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
|
||||
"download-bins": "concurrently yarn:download:*",
|
||||
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
|
||||
"download:helm": "yarn run ts-node build/download_helm.ts",
|
||||
"download:k8s-proxy": "yarn run ts-node build/download_k8s_proxy.ts",
|
||||
"download:binaries": "yarn run ts-node build/download_binaries.ts",
|
||||
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
|
||||
"build:theme-vars": "yarn run ts-node build/build_theme_vars.ts",
|
||||
"lint": "PROD=true yarn run eslint --ext js,ts,tsx --max-warnings=0 .",
|
||||
@ -48,7 +44,7 @@
|
||||
"postversion": "git push --set-upstream ${GIT_REMOTE:-origin} release/v$npm_package_version"
|
||||
},
|
||||
"config": {
|
||||
"k8sProxyVersion": "0.1.2",
|
||||
"k8sProxyVersion": "0.1.5",
|
||||
"bundledKubectlVersion": "1.23.3",
|
||||
"bundledHelmVersion": "3.7.2",
|
||||
"sentryDsn": ""
|
||||
@ -136,8 +132,8 @@
|
||||
"to": "./${arch}/lens-k8s-proxy"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/${arch}/helm3/helm3",
|
||||
"to": "./helm3/helm3"
|
||||
"from": "binaries/client/linux/${arch}/helm",
|
||||
"to": "./${arch}/helm"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -161,8 +157,8 @@
|
||||
"to": "./${arch}/lens-k8s-proxy"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/${arch}/helm3/helm3",
|
||||
"to": "./helm3/helm3"
|
||||
"from": "binaries/client/darwin/${arch}/helm",
|
||||
"to": "./${arch}/helm"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -172,24 +168,16 @@
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "binaries/client/windows/x64/kubectl.exe",
|
||||
"to": "./x64/kubectl.exe"
|
||||
"from": "binaries/client/windows/${arch}/kubectl.exe",
|
||||
"to": "./${arch}/kubectl.exe"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/windows/ia32/kubectl.exe",
|
||||
"to": "./ia32/kubectl.exe"
|
||||
"from": "binaries/client/windows/${arch}/lens-k8s-proxy.exe",
|
||||
"to": "./${arch}/lens-k8s-proxy.exe"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/windows/x64/lens-k8s-proxy",
|
||||
"to": "./x64/lens-k8s-proxy.exe"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/windows/ia32/lens-k8s-proxy",
|
||||
"to": "./ia32/lens-k8s-proxy.exe"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/x64/helm3/helm3.exe",
|
||||
"to": "./helm3/helm3.exe"
|
||||
"from": "binaries/client/windows/${arch}/helm.exe",
|
||||
"to": "./${arch}/helm.exe"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -273,7 +261,7 @@
|
||||
"tar": "^6.1.11",
|
||||
"tcp-port-used": "^1.0.2",
|
||||
"tempy": "1.0.1",
|
||||
"url-parse": "^1.5.3",
|
||||
"url-parse": "^1.5.10",
|
||||
"uuid": "^8.3.2",
|
||||
"win-ca": "^3.4.5",
|
||||
"winston": "^3.3.3",
|
||||
@ -287,18 +275,20 @@
|
||||
"@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",
|
||||
"@types/byline": "^4.2.33",
|
||||
"@types/chart.js": "^2.9.34",
|
||||
"@types/cli-progress": "^3.9.2",
|
||||
"@types/color": "^3.0.2",
|
||||
"@types/crypto-js": "^3.1.47",
|
||||
"@types/dompurify": "^2.3.1",
|
||||
"@types/electron-devtools-installer": "^2.2.0",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/glob-to-regexp": "^0.4.1",
|
||||
"@types/gunzip-maybe": "^1.4.0",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/html-webpack-plugin": "^3.2.6",
|
||||
"@types/http-proxy": "^1.17.7",
|
||||
@ -331,6 +321,7 @@
|
||||
"@types/sharp": "^0.29.4",
|
||||
"@types/spdy": "^3.4.5",
|
||||
"@types/tar": "^4.0.5",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/tcp-port-used": "^1.0.0",
|
||||
"@types/tempy": "^0.3.0",
|
||||
"@types/triple-beam": "^1.3.2",
|
||||
@ -345,6 +336,7 @@
|
||||
"ansi_up": "^5.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",
|
||||
@ -363,6 +355,7 @@
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"flex.box": "^3.4.4",
|
||||
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
||||
"gunzip-maybe": "^1.4.2",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"ignore-loader": "^0.1.2",
|
||||
@ -393,10 +386,11 @@
|
||||
"sharp": "^0.29.3",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.0.7",
|
||||
"tar-stream": "^2.2.0",
|
||||
"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",
|
||||
|
||||
@ -3,19 +3,11 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import isDevelopmentInjectable from "../../vars/is-development.injectable";
|
||||
import contextDirInjectable from "../../vars/context-dir.injectable";
|
||||
import { baseBinariesDir } from "../../vars";
|
||||
|
||||
const directoryForBundledBinariesInjectable = getInjectable({
|
||||
id: "directory-for-bundled-binaries",
|
||||
instantiate: (di) => {
|
||||
if (di.inject(isDevelopmentInjectable)) {
|
||||
return path.join(di.inject(contextDirInjectable), "binaries");
|
||||
}
|
||||
|
||||
return process.resourcesPath;
|
||||
},
|
||||
instantiate: () => baseBinariesDir.get(),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
8
src/common/ipc/native-theme.ts
Normal file
8
src/common/ipc/native-theme.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
|
||||
export const setNativeThemeChannel = "theme:set-native-theme";
|
||||
export const getNativeThemeChannel = "theme:get-native-theme";
|
||||
@ -36,13 +36,6 @@ describe("HelmChart tests", () => {
|
||||
version: "!",
|
||||
repo: "!",
|
||||
} as any)).toThrowError('"created" is required');
|
||||
expect(() => HelmChart.create({
|
||||
apiVersion: "!",
|
||||
name: "!",
|
||||
version: "!",
|
||||
repo: "!",
|
||||
created: "!",
|
||||
} as any)).toThrowError('"digest" is required');
|
||||
});
|
||||
|
||||
it("should throw on fields being wrong type", () => {
|
||||
@ -62,6 +55,14 @@ describe("HelmChart tests", () => {
|
||||
created: "!",
|
||||
digest: "!",
|
||||
} as any)).toThrowError('"name" must be a string');
|
||||
expect(() => HelmChart.create({
|
||||
apiVersion: "!",
|
||||
name: "!",
|
||||
version: "!",
|
||||
repo: "!",
|
||||
created: "!",
|
||||
digest: 1,
|
||||
} as any)).toThrowError('"digest" must be a string');
|
||||
expect(() => HelmChart.create({
|
||||
apiVersion: "1",
|
||||
name: "",
|
||||
|
||||
@ -32,7 +32,7 @@ export class ApiManager {
|
||||
return iter.find(this.apis.values(), api => api.kind === kind && api.apiVersionWithGroup === apiVersion);
|
||||
}
|
||||
|
||||
registerApi(apiBase: string, api: KubeApi<KubeObject>) {
|
||||
registerApi<K extends KubeObject>(apiBase: string, api: KubeApi<K>) {
|
||||
if (!api.apiBase) return;
|
||||
|
||||
if (!this.apis.has(apiBase)) {
|
||||
@ -46,13 +46,13 @@ export class ApiManager {
|
||||
}
|
||||
}
|
||||
|
||||
protected resolveApi<K extends KubeObject>(api?: string | KubeApi<K>): KubeApi<K> | undefined {
|
||||
protected resolveApi(api?: string | KubeApi<KubeObject>): KubeApi<KubeObject> | undefined {
|
||||
if (!api) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof api === "string") {
|
||||
return this.getApi(api) as KubeApi<K>;
|
||||
return this.getApi(api) as KubeApi<KubeObject>;
|
||||
}
|
||||
|
||||
return api;
|
||||
@ -69,7 +69,7 @@ export class ApiManager {
|
||||
}
|
||||
|
||||
@action
|
||||
registerStore(store: KubeObjectStore<KubeObject>, apis: KubeApi<KubeObject>[] = [store.api]) {
|
||||
registerStore<K extends KubeObject>(store: KubeObjectStore<K>, apis: KubeApi<K>[] = [store.api]) {
|
||||
apis.filter(Boolean).forEach(api => {
|
||||
if (api.apiBase) this.stores.set(api.apiBase, store);
|
||||
});
|
||||
|
||||
@ -75,7 +75,7 @@ export interface RawHelmChart {
|
||||
version: string;
|
||||
repo: string;
|
||||
created: string;
|
||||
digest: string;
|
||||
digest?: string;
|
||||
kubeVersion?: string;
|
||||
description?: string;
|
||||
home?: string;
|
||||
@ -142,7 +142,7 @@ const helmChartValidator = Joi.object<HelmChart, true, RawHelmChart>({
|
||||
.required(),
|
||||
digest: Joi
|
||||
.string()
|
||||
.required(),
|
||||
.optional(),
|
||||
kubeVersion: Joi
|
||||
.string()
|
||||
.optional(),
|
||||
@ -247,22 +247,22 @@ export interface HelmChart {
|
||||
name: string;
|
||||
version: string;
|
||||
repo: string;
|
||||
kubeVersion?: string;
|
||||
created: string;
|
||||
description: string;
|
||||
digest: string;
|
||||
keywords: string[];
|
||||
home?: string;
|
||||
sources: string[];
|
||||
urls: string[];
|
||||
annotations: Record<string, string>;
|
||||
dependencies: HelmChartDependency[];
|
||||
maintainers: HelmChartMaintainer[];
|
||||
deprecated: boolean;
|
||||
kubeVersion?: string;
|
||||
digest?: string;
|
||||
home?: string;
|
||||
engine?: string;
|
||||
icon?: string;
|
||||
appVersion?: string;
|
||||
type?: string;
|
||||
deprecated: boolean;
|
||||
tillerVersion?: string;
|
||||
}
|
||||
|
||||
@ -324,7 +324,11 @@ export class HelmChart {
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return `${this.repo}:${this.apiVersion}/${this.name}@${this.getAppVersion()}+${this.digest}`;
|
||||
const digestPart = this.digest
|
||||
? `+${this.digest}`
|
||||
: "";
|
||||
|
||||
return `${this.repo}:${this.apiVersion}/${this.name}@${this.getAppVersion()}${digestPart}`;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
|
||||
35
src/common/utils/lazy-initialized.ts
Normal file
35
src/common/utils/lazy-initialized.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A OnceCell is an object that wraps some function that produces a value.
|
||||
*
|
||||
* It then only calls the function on the first call to `get()` and returns the
|
||||
* same instance/value on every subsequent call.
|
||||
*/
|
||||
export interface LazyInitialized<T> {
|
||||
get(): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to make a `OnceCell<T>`
|
||||
*/
|
||||
export function lazyInitialized<T>(builder: () => T): LazyInitialized<T> {
|
||||
let value: T | undefined;
|
||||
let called = false;
|
||||
|
||||
return {
|
||||
get() {
|
||||
if (called) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = builder();
|
||||
called = true;
|
||||
|
||||
return value;
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -8,6 +8,7 @@ import path from "path";
|
||||
import { SemVer } from "semver";
|
||||
import packageInfo from "../../package.json";
|
||||
import { defineGlobal } from "./utils/defineGlobal";
|
||||
import { lazyInitialized } from "./utils/lazy-initialized";
|
||||
|
||||
export const isMac = process.platform === "darwin";
|
||||
export const isWindows = process.platform === "win32";
|
||||
@ -29,6 +30,54 @@ export const defaultTheme = "lens-dark" as string;
|
||||
export const defaultFontSize = 12;
|
||||
export const defaultTerminalFontFamily = "RobotoMono";
|
||||
export const defaultEditorFontFamily = "RobotoMono";
|
||||
export const normalizedPlatform = (() => {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return "darwin";
|
||||
case "linux":
|
||||
return "linux";
|
||||
case "win32":
|
||||
return "windows";
|
||||
default:
|
||||
throw new Error(`platform=${process.platform} is unsupported`);
|
||||
}
|
||||
})();
|
||||
export const normalizedArch = (() => {
|
||||
switch (process.arch) {
|
||||
case "arm64":
|
||||
return "arm64";
|
||||
case "x64":
|
||||
case "amd64":
|
||||
return "x64";
|
||||
case "386":
|
||||
case "x32":
|
||||
case "ia32":
|
||||
return "ia32";
|
||||
default:
|
||||
throw new Error(`arch=${process.arch} is unsupported`);
|
||||
}
|
||||
})();
|
||||
|
||||
export function getBinaryName(name: string, { forPlatform = normalizedPlatform } = {}): string {
|
||||
if (forPlatform === "windows") {
|
||||
return `${name}.exe`;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
const resourcesDir = lazyInitialized(() => (
|
||||
isProduction
|
||||
? process.resourcesPath
|
||||
: path.join(process.cwd(), "binaries", "client", normalizedPlatform)
|
||||
));
|
||||
|
||||
export const baseBinariesDir = lazyInitialized(() => path.join(resourcesDir.get(), normalizedArch));
|
||||
export const kubeAuthProxyBinaryName = getBinaryName("lens-k8s-proxy");
|
||||
export const helmBinaryName = getBinaryName("helm");
|
||||
export const helmBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), helmBinaryName));
|
||||
export const kubectlBinaryName = getBinaryName("kubectl");
|
||||
export const kubectlBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), kubectlBinaryName));
|
||||
|
||||
// Webpack build paths
|
||||
export const contextDir = process.cwd();
|
||||
|
||||
@ -6,7 +6,7 @@ export type { StatusBarRegistration } from "../../renderer/components/status-bar
|
||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
|
||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
|
||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
|
||||
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
|
||||
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
||||
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";
|
||||
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../registries/page-menu-registry";
|
||||
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler";
|
||||
|
||||
@ -277,8 +277,6 @@ export class ExtensionLoader {
|
||||
registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension),
|
||||
registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension),
|
||||
registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems),
|
||||
registries.KubeObjectStatusRegistry.getInstance().add(extension.kubeObjectStatusTexts),
|
||||
registries.WorkloadsOverviewDetailRegistry.getInstance().add(extension.kubeWorkloadsOverviewItems),
|
||||
];
|
||||
|
||||
this.events.on("remove", (removedExtension: LensRendererExtension) => {
|
||||
|
||||
@ -20,18 +20,20 @@ import type { AdditionalCategoryColumnRegistration } from "../renderer/component
|
||||
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
|
||||
import type { StatusBarRegistration } from "../renderer/components/status-bar/status-bar-registration";
|
||||
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
|
||||
import type { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration";
|
||||
import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration";
|
||||
|
||||
export class LensRendererExtension extends LensExtension {
|
||||
globalPages: registries.PageRegistration[] = [];
|
||||
clusterPages: registries.PageRegistration[] = [];
|
||||
clusterPageMenus: registries.ClusterPageMenuRegistration[] = [];
|
||||
kubeObjectStatusTexts: registries.KubeObjectStatusRegistration[] = [];
|
||||
kubeObjectStatusTexts: KubeObjectStatusRegistration[] = [];
|
||||
appPreferences: AppPreferenceRegistration[] = [];
|
||||
entitySettings: registries.EntitySettingRegistration[] = [];
|
||||
statusBarItems: StatusBarRegistration[] = [];
|
||||
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
|
||||
kubeObjectMenuItems: KubeObjectMenuRegistration[] = [];
|
||||
kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = [];
|
||||
kubeWorkloadsOverviewItems: WorkloadsOverviewDetailRegistration[] = [];
|
||||
commands: CommandRegistration[] = [];
|
||||
welcomeMenus: WelcomeMenuRegistration[] = [];
|
||||
welcomeBanners: WelcomeBannerRegistration[] = [];
|
||||
|
||||
@ -29,6 +29,10 @@ jest.mock("electron", () => ({
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
ipcRenderer: {
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@ -8,8 +8,6 @@
|
||||
export * from "./page-registry";
|
||||
export * from "./page-menu-registry";
|
||||
export * from "./kube-object-detail-registry";
|
||||
export * from "./kube-object-status-registry";
|
||||
export * from "./entity-setting-registry";
|
||||
export * from "./catalog-entity-detail-registry";
|
||||
export * from "./workloads-overview-detail-registry";
|
||||
export * from "./protocol-handler";
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { KubeObject, KubeObjectStatus } from "../renderer-api/k8s-api";
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
|
||||
export interface KubeObjectStatusRegistration {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
resolve: (object: KubeObject) => KubeObjectStatus;
|
||||
}
|
||||
|
||||
export class KubeObjectStatusRegistry extends BaseRegistry<KubeObjectStatusRegistration> {
|
||||
getItemsForKind(kind: string, apiVersion: string) {
|
||||
return this.getItems()
|
||||
.filter((item) => (
|
||||
item.kind === kind
|
||||
&& item.apiVersions.includes(apiVersion)
|
||||
));
|
||||
}
|
||||
|
||||
getItemsForObject(src: KubeObject) {
|
||||
return this.getItemsForKind(src.kind, src.apiVersion)
|
||||
.map(item => item.resolve(src))
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { orderBy } from "lodash";
|
||||
import type React from "react";
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
|
||||
export interface WorkloadsOverviewDetailComponents {
|
||||
Details: React.ComponentType<{}>;
|
||||
}
|
||||
|
||||
export interface WorkloadsOverviewDetailRegistration {
|
||||
components: WorkloadsOverviewDetailComponents;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
type RegisteredWorkloadsOverviewDetail = Required<WorkloadsOverviewDetailRegistration>;
|
||||
|
||||
export class WorkloadsOverviewDetailRegistry extends BaseRegistry<WorkloadsOverviewDetailRegistration, RegisteredWorkloadsOverviewDetail> {
|
||||
getItems() {
|
||||
return orderBy(super.getItems(), "priority", "desc");
|
||||
}
|
||||
|
||||
protected getRegisteredItem(item: WorkloadsOverviewDetailRegistration): RegisteredWorkloadsOverviewDetail {
|
||||
const { priority = 50, ...rest } = item;
|
||||
|
||||
return { priority, ...rest };
|
||||
}
|
||||
}
|
||||
@ -118,7 +118,7 @@ export class ContextHandler {
|
||||
await this.ensureServer();
|
||||
const path = this.clusterUrl.path !== "/" ? this.clusterUrl.path : "";
|
||||
|
||||
return `http://127.0.0.1:${this.kubeAuthProxy.port}${this.kubeAuthProxy.apiPrefix.slice(0, -1)}${path}`;
|
||||
return `http://127.0.0.1:${this.kubeAuthProxy.port}${this.kubeAuthProxy.apiPrefix}${path}`;
|
||||
}
|
||||
|
||||
async getApiTarget(isLongRunningRequest = false): Promise<httpProxy.ServerOptions> {
|
||||
|
||||
23
src/main/helm/exec.ts
Normal file
23
src/main/helm/exec.ts
Normal 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 { promiseExecFile } from "../../common/utils/promise-exec";
|
||||
import type { BaseEncodingOptions } from "fs";
|
||||
import type { ExecFileOptions } from "child_process";
|
||||
import { helmBinaryPath } from "../../common/vars";
|
||||
|
||||
/**
|
||||
* ExecFile the bundled helm CLI
|
||||
* @returns STDOUT
|
||||
*/
|
||||
export async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise<string> {
|
||||
try {
|
||||
const { stdout } = await promiseExecFile(helmBinaryPath.get(), args, options);
|
||||
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
throw error?.stderr || error;
|
||||
}
|
||||
}
|
||||
@ -8,10 +8,9 @@ import v8 from "v8";
|
||||
import * as yaml from "js-yaml";
|
||||
import type { HelmRepo } from "./helm-repo-manager";
|
||||
import logger from "../logger";
|
||||
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||
import { helmCli } from "./helm-cli";
|
||||
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import { iter, sortCharts } from "../../common/utils";
|
||||
import { execHelm } from "./exec";
|
||||
|
||||
interface ChartCacheEntry {
|
||||
data: Buffer;
|
||||
@ -49,21 +48,13 @@ export class HelmChartManager {
|
||||
}
|
||||
|
||||
private async executeCommand(args: string[], name: string, version?: string) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
|
||||
args.push(`${this.repo.name}/${name}`);
|
||||
|
||||
if (version) {
|
||||
args.push("--version", version);
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await promiseExecFile(helm, args);
|
||||
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
throw error.stderr || error;
|
||||
}
|
||||
return execHelm(args);
|
||||
}
|
||||
|
||||
public async getReadme(name: string, version?: string) {
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import packageInfo from "../../../package.json";
|
||||
import path from "path";
|
||||
import { LensBinary, LensBinaryOpts } from "../lens-binary";
|
||||
import { isProduction } from "../../common/vars";
|
||||
|
||||
export class HelmCli extends LensBinary {
|
||||
|
||||
public constructor(baseDir: string, version: string) {
|
||||
const opts: LensBinaryOpts = {
|
||||
version,
|
||||
baseDir,
|
||||
originalBinaryName: "helm",
|
||||
newBinaryName: "helm3",
|
||||
};
|
||||
|
||||
super(opts);
|
||||
}
|
||||
|
||||
protected getTarName(): string | null {
|
||||
return `${this.binaryName}-v${this.binaryVersion}-${this.platformName}-${this.arch}.tar.gz`;
|
||||
}
|
||||
|
||||
protected getUrl() {
|
||||
return `https://get.helm.sh/helm-v${this.binaryVersion}-${this.platformName}-${this.arch}.tar.gz`;
|
||||
}
|
||||
|
||||
protected getBinaryPath() {
|
||||
return path.join(this.dirname, this.binaryName);
|
||||
}
|
||||
|
||||
protected getOriginalBinaryPath() {
|
||||
return path.join(this.dirname, `${this.platformName}-${this.arch}`, this.originalBinaryName);
|
||||
}
|
||||
}
|
||||
|
||||
const helmVersion = packageInfo.config.bundledHelmVersion;
|
||||
let baseDir = process.resourcesPath;
|
||||
|
||||
if (!isProduction) {
|
||||
baseDir = path.join(process.cwd(), "binaries", "client", process.arch);
|
||||
}
|
||||
|
||||
export const helmCli = new HelmCli(baseDir, helmVersion);
|
||||
|
||||
@ -6,23 +6,9 @@
|
||||
import tempy from "tempy";
|
||||
import fse from "fs-extra";
|
||||
import * as yaml from "js-yaml";
|
||||
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||
import { helmCli } from "./helm-cli";
|
||||
import { toCamelCase } from "../../common/utils/camelCase";
|
||||
import type { BaseEncodingOptions } from "fs";
|
||||
import { execFile, ExecFileOptions } from "child_process";
|
||||
|
||||
async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise<string> {
|
||||
const helmCliPath = await helmCli.binaryPath();
|
||||
|
||||
try {
|
||||
const { stdout } = await promiseExecFile(helmCliPath, args, options);
|
||||
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
throw error?.stderr || error;
|
||||
}
|
||||
}
|
||||
import { execFile } from "child_process";
|
||||
import { execHelm } from "./exec";
|
||||
|
||||
export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise<Record<string, any>[]> {
|
||||
const args = [
|
||||
|
||||
@ -4,14 +4,12 @@
|
||||
*/
|
||||
|
||||
import yaml from "js-yaml";
|
||||
import { BaseEncodingOptions, readFile } from "fs-extra";
|
||||
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||
import { helmCli } from "./helm-cli";
|
||||
import { readFile } from "fs-extra";
|
||||
import { Singleton } from "../../common/utils/singleton";
|
||||
import { customRequestPromise } from "../../common/request";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import logger from "../logger";
|
||||
import type { ExecFileOptions } from "child_process";
|
||||
import { execHelm } from "./exec";
|
||||
|
||||
export type HelmEnv = Record<string, string> & {
|
||||
HELM_REPOSITORY_CACHE?: string;
|
||||
@ -34,18 +32,6 @@ export interface HelmRepo {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise<string> {
|
||||
const helmCliPath = await helmCli.binaryPath();
|
||||
|
||||
try {
|
||||
const { stdout } = await promiseExecFile(helmCliPath, args, options);
|
||||
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
throw error?.stderr || error;
|
||||
}
|
||||
}
|
||||
|
||||
export class HelmRepoManager extends Singleton {
|
||||
protected repos: HelmRepo[];
|
||||
protected helmEnv: HelmEnv;
|
||||
@ -63,9 +49,6 @@ export class HelmRepoManager extends Singleton {
|
||||
}
|
||||
|
||||
private async ensureInitialized() {
|
||||
helmCli.setLogger(logger);
|
||||
await helmCli.ensureBinary();
|
||||
|
||||
this.helmEnv ??= await this.parseHelmEnv();
|
||||
|
||||
const repos = await this.list();
|
||||
|
||||
@ -7,10 +7,11 @@
|
||||
|
||||
import { injectSystemCAs } from "../common/system-ca";
|
||||
import * as Mobx from "mobx";
|
||||
import httpProxy from "http-proxy";
|
||||
import * as LensExtensionsCommonApi from "../extensions/common-api";
|
||||
import * as LensExtensionsMainApi from "../extensions/main-api";
|
||||
import { app, autoUpdater, dialog, powerMonitor } from "electron";
|
||||
import { appName, isIntegrationTesting, isMac, isWindows, productName, isDevelopment } from "../common/vars";
|
||||
import { appName, isIntegrationTesting, isMac, isWindows, productName } from "../common/vars";
|
||||
import { LensProxy } from "./lens-proxy";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { ClusterManager } from "./cluster-manager";
|
||||
@ -55,6 +56,7 @@ import routerInjectable from "./router/router.injectable";
|
||||
import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable";
|
||||
import userStoreInjectable from "../common/user-store/user-store.injectable";
|
||||
import trayMenuItemsInjectable from "./tray/tray-menu-items.injectable";
|
||||
import { broadcastNativeThemeOnUpdate } from "./native-theme";
|
||||
|
||||
const di = getDi();
|
||||
|
||||
@ -108,6 +110,8 @@ di.runSetups().then(() => {
|
||||
}
|
||||
}
|
||||
|
||||
broadcastNativeThemeOnUpdate();
|
||||
|
||||
app.on("second-instance", (event, argv) => {
|
||||
logger.debug("second-instance message");
|
||||
|
||||
@ -164,7 +168,7 @@ di.runSetups().then(() => {
|
||||
const router = di.inject(routerInjectable);
|
||||
const shellApiRequest = di.inject(shellApiRequestInjectable);
|
||||
|
||||
const lensProxy = LensProxy.createInstance(router, {
|
||||
const lensProxy = LensProxy.createInstance(router, httpProxy.createProxy(), {
|
||||
getClusterForRequest: (req) => ClusterManager.getInstance().getClusterForRequest(req),
|
||||
kubeApiRequest,
|
||||
shellApiRequest,
|
||||
@ -227,16 +231,6 @@ di.runSetups().then(() => {
|
||||
|
||||
logger.info("🖥️ Starting WindowManager");
|
||||
const windowManager = WindowManager.createInstance();
|
||||
|
||||
// Override main content view url to local webpack-dev-server to support HMR / live-reload
|
||||
if (isDevelopment) {
|
||||
const { createDevServer } = await import("../../webpack.dev-server");
|
||||
const devServer = createDevServer(lensProxy.port);
|
||||
|
||||
await devServer.start();
|
||||
windowManager.mainContentUrl = `http://localhost:${devServer.options.port}`;
|
||||
}
|
||||
|
||||
const menuItems = di.inject(electronMenuItemsInjectable);
|
||||
const trayMenuItems = di.inject(trayMenuItemsInjectable);
|
||||
|
||||
|
||||
@ -24,6 +24,8 @@ import { onLocationChange, handleWindowAction } from "../../ipc/window";
|
||||
import { openFilePickingDialogChannel } from "../../../common/ipc/dialog";
|
||||
import { showOpenDialog } from "../../ipc/dialog";
|
||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../common/ipc/window";
|
||||
import { getNativeColorTheme } from "../../native-theme";
|
||||
import { getNativeThemeChannel } from "../../../common/ipc/native-theme";
|
||||
|
||||
interface Dependencies {
|
||||
electronMenuItems: IComputedValue<MenuRegistration[]>;
|
||||
@ -158,4 +160,8 @@ export const initIpcMainHandlers = ({ electronMenuItems, directoryForLensLocalSt
|
||||
y: 20,
|
||||
});
|
||||
});
|
||||
|
||||
ipcMainHandle(getNativeThemeChannel, () => {
|
||||
return getNativeColorTheme();
|
||||
});
|
||||
};
|
||||
|
||||
@ -3,24 +3,24 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { KubeAuthProxy } from "./kube-auth-proxy";
|
||||
import { KubeAuthProxy, KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import path from "path";
|
||||
import { isDevelopment, isWindows } from "../../common/vars";
|
||||
import { getBinaryName } from "../../common/vars";
|
||||
import directoryForBundledBinariesInjectable from "../../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable";
|
||||
|
||||
const createKubeAuthProxyInjectable = getInjectable({
|
||||
id: "create-kube-auth-proxy",
|
||||
|
||||
instantiate: (di) => {
|
||||
const binaryName = isWindows ? "lens-k8s-proxy.exe" : "lens-k8s-proxy";
|
||||
const proxyPath = isDevelopment ? path.join("client", process.platform, process.arch) : process.arch;
|
||||
const dependencies = {
|
||||
proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), proxyPath, binaryName),
|
||||
const binaryName = getBinaryName("lens-k8s-proxy");
|
||||
const dependencies: KubeAuthProxyDependencies = {
|
||||
proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), binaryName),
|
||||
};
|
||||
|
||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) =>
|
||||
new KubeAuthProxy(dependencies, cluster, environmentVariables);
|
||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => (
|
||||
new KubeAuthProxy(dependencies, cluster, environmentVariables)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -13,12 +13,12 @@ import { makeObservable, observable, when } from "mobx";
|
||||
|
||||
const startingServeRegex = /starting to serve on (?<address>.+)/i;
|
||||
|
||||
interface Dependencies {
|
||||
export interface KubeAuthProxyDependencies {
|
||||
proxyBinPath: string;
|
||||
}
|
||||
|
||||
export class KubeAuthProxy {
|
||||
public readonly apiPrefix = `/${randomBytes(8).toString("hex")}/`;
|
||||
public readonly apiPrefix = `/${randomBytes(8).toString("hex")}`;
|
||||
|
||||
public get port(): number {
|
||||
return this._port;
|
||||
@ -28,7 +28,7 @@ export class KubeAuthProxy {
|
||||
protected proxyProcess?: ChildProcess;
|
||||
@observable protected ready = false;
|
||||
|
||||
constructor(private dependencies: Dependencies, protected readonly cluster: Cluster, protected readonly env: NodeJS.ProcessEnv) {
|
||||
constructor(private dependencies: KubeAuthProxyDependencies, protected readonly cluster: Cluster, protected readonly env: NodeJS.ProcessEnv) {
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@ -42,8 +42,8 @@ export class KubeAuthProxy {
|
||||
}
|
||||
|
||||
const proxyBin = this.dependencies.proxyBinPath;
|
||||
|
||||
this.proxyProcess = spawn(proxyBin, [], {
|
||||
|
||||
this.proxyProcess = spawn(proxyBin, [], {
|
||||
env: {
|
||||
...this.env,
|
||||
KUBECONFIG: this.cluster.kubeConfigPath,
|
||||
|
||||
@ -9,9 +9,8 @@ import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||
import logger from "../logger";
|
||||
import { ensureDir, pathExists } from "fs-extra";
|
||||
import * as lockFile from "proper-lockfile";
|
||||
import { helmCli } from "../helm/helm-cli";
|
||||
import { getBundledKubectlVersion } from "../../common/utils/app-version";
|
||||
import { isDevelopment, isWindows, isTestEnv } from "../../common/vars";
|
||||
import { normalizedPlatform, normalizedArch, kubectlBinaryName, kubectlBinaryPath, baseBinariesDir } from "../../common/vars";
|
||||
import { SemVer } from "semver";
|
||||
import { defaultPackageMirror, packageMirrors } from "../../common/user-store/preferences-helpers";
|
||||
import got from "got/dist/source";
|
||||
@ -39,27 +38,8 @@ const kubectlMap: Map<string, string> = new Map([
|
||||
["1.22", "1.22.6"],
|
||||
["1.23", bundledVersion],
|
||||
]);
|
||||
let bundledPath: string;
|
||||
const initScriptVersionString = "# lens-initscript v3";
|
||||
|
||||
export function bundledKubectlPath(): string {
|
||||
if (bundledPath) { return bundledPath; }
|
||||
|
||||
if (isDevelopment || isTestEnv) {
|
||||
const platformName = isWindows ? "windows" : process.platform;
|
||||
|
||||
bundledPath = path.join(process.cwd(), "binaries", "client", platformName, process.arch, "kubectl");
|
||||
} else {
|
||||
bundledPath = path.join(process.resourcesPath, process.arch, "kubectl");
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
bundledPath = `${bundledPath}.exe`;
|
||||
}
|
||||
|
||||
return bundledPath;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
directoryForKubectlBinaries: string;
|
||||
|
||||
@ -102,27 +82,13 @@ export class Kubectl {
|
||||
logger.debug(`Set kubectl version ${this.kubectlVersion} for cluster version ${clusterVersion} using fallback`);
|
||||
}
|
||||
|
||||
let arch = null;
|
||||
|
||||
if (process.arch == "x64") {
|
||||
arch = "amd64";
|
||||
} else if (process.arch == "x86" || process.arch == "ia32") {
|
||||
arch = "386";
|
||||
} else {
|
||||
arch = process.arch;
|
||||
}
|
||||
|
||||
const platformName = isWindows ? "windows" : process.platform;
|
||||
const binaryName = isWindows ? "kubectl.exe" : "kubectl";
|
||||
|
||||
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${platformName}/${arch}/${binaryName}`;
|
||||
|
||||
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${normalizedPlatform}/${normalizedArch}/${kubectlBinaryName}`;
|
||||
this.dirname = path.normalize(path.join(this.getDownloadDir(), this.kubectlVersion));
|
||||
this.path = path.join(this.dirname, binaryName);
|
||||
this.path = path.join(this.dirname, kubectlBinaryName);
|
||||
}
|
||||
|
||||
public getBundledPath() {
|
||||
return bundledKubectlPath();
|
||||
return kubectlBinaryPath.get();
|
||||
}
|
||||
|
||||
public getPathFromPreferences() {
|
||||
@ -316,7 +282,7 @@ export class Kubectl {
|
||||
? this.dirname
|
||||
: path.dirname(this.getPathFromPreferences());
|
||||
|
||||
const helmPath = helmCli.getBinaryDir();
|
||||
const binariesDir = baseBinariesDir.get();
|
||||
|
||||
const bashScriptPath = path.join(this.dirname, ".bash_set_path");
|
||||
const bashScript = [
|
||||
@ -330,7 +296,7 @@ export class Kubectl {
|
||||
"elif test -f \"$HOME/.profile\"; then",
|
||||
" . \"$HOME/.profile\"",
|
||||
"fi",
|
||||
`export PATH="${helmPath}:${kubectlPath}:$PATH"`,
|
||||
`export PATH="${binariesDir}:${kubectlPath}:$PATH"`,
|
||||
'export KUBECONFIG="$tempkubeconfig"',
|
||||
`NO_PROXY=",\${NO_PROXY:-localhost},"`,
|
||||
`NO_PROXY="\${NO_PROXY//,localhost,/,}"`,
|
||||
@ -356,12 +322,12 @@ export class Kubectl {
|
||||
|
||||
// voodoo to replace any previous occurrences of kubectl path in the PATH
|
||||
`kubectlpath="${kubectlPath}"`,
|
||||
`helmpath="${helmPath}"`,
|
||||
`binariesDir="${binariesDir}"`,
|
||||
"p=\":$kubectlpath:\"",
|
||||
"d=\":$PATH:\"",
|
||||
`d=\${d//$p/:}`,
|
||||
`d=\${d/#:/}`,
|
||||
`export PATH="$helmpath:$kubectlpath:\${d/%:/}"`,
|
||||
`export PATH="$binariesDir:$kubectlpath:\${d/%:/}"`,
|
||||
"export KUBECONFIG=\"$tempkubeconfig\"",
|
||||
`NO_PROXY=",\${NO_PROXY:-localhost},"`,
|
||||
`NO_PROXY="\${NO_PROXY//,localhost,/,}"`,
|
||||
|
||||
@ -1,202 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import request from "request";
|
||||
import { ensureDir, pathExists } from "fs-extra";
|
||||
import * as tar from "tar";
|
||||
import { isWindows } from "../common/vars";
|
||||
import type winston from "winston";
|
||||
|
||||
export interface LensBinaryOpts {
|
||||
version: string;
|
||||
baseDir: string;
|
||||
originalBinaryName: string;
|
||||
newBinaryName?: string;
|
||||
requestOpts?: request.Options;
|
||||
}
|
||||
|
||||
export class LensBinary {
|
||||
|
||||
public binaryVersion: string;
|
||||
protected directory: string;
|
||||
protected url: string;
|
||||
protected path: string;
|
||||
protected tarPath: string;
|
||||
protected dirname: string;
|
||||
protected binaryName: string;
|
||||
protected platformName: string;
|
||||
protected arch: string;
|
||||
protected originalBinaryName: string;
|
||||
protected requestOpts: request.Options;
|
||||
protected logger: Console | winston.Logger;
|
||||
|
||||
constructor(opts: LensBinaryOpts) {
|
||||
const baseDir = opts.baseDir;
|
||||
|
||||
this.originalBinaryName = opts.originalBinaryName;
|
||||
this.binaryName = opts.newBinaryName || opts.originalBinaryName;
|
||||
this.binaryVersion = opts.version;
|
||||
this.requestOpts = opts.requestOpts;
|
||||
this.logger = console;
|
||||
let arch = null;
|
||||
|
||||
if (process.env.BINARY_ARCH) {
|
||||
arch = process.env.BINARY_ARCH;
|
||||
} else if (process.arch == "x64") {
|
||||
arch = "amd64";
|
||||
} else if (process.arch == "x86" || process.arch == "ia32") {
|
||||
arch = "386";
|
||||
} else {
|
||||
arch = process.arch;
|
||||
}
|
||||
this.arch = arch;
|
||||
this.platformName = isWindows ? "windows" : process.platform;
|
||||
this.dirname = path.normalize(path.join(baseDir, this.binaryName));
|
||||
|
||||
if (isWindows) {
|
||||
this.binaryName = `${this.binaryName}.exe`;
|
||||
this.originalBinaryName = `${this.originalBinaryName}.exe`;
|
||||
}
|
||||
const tarName = this.getTarName();
|
||||
|
||||
if (tarName) {
|
||||
this.tarPath = path.join(this.dirname, tarName);
|
||||
}
|
||||
}
|
||||
|
||||
public setLogger(logger: Console | winston.Logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
protected binaryDir() {
|
||||
throw new Error("binaryDir not implemented");
|
||||
}
|
||||
|
||||
public async binaryPath() {
|
||||
await this.ensureBinary();
|
||||
|
||||
return this.getBinaryPath();
|
||||
}
|
||||
|
||||
protected getTarName(): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected getUrl() {
|
||||
return "";
|
||||
}
|
||||
|
||||
protected getBinaryPath() {
|
||||
return "";
|
||||
}
|
||||
|
||||
protected getOriginalBinaryPath() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public getBinaryDir() {
|
||||
return path.dirname(this.getBinaryPath());
|
||||
}
|
||||
|
||||
public async binDir() {
|
||||
try {
|
||||
await this.ensureBinary();
|
||||
|
||||
return this.dirname;
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
protected async checkBinary() {
|
||||
const exists = await pathExists(this.getBinaryPath());
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
public async ensureBinary() {
|
||||
const isValid = await this.checkBinary();
|
||||
|
||||
if (!isValid) {
|
||||
await this.downloadBinary().catch((error) => {
|
||||
this.logger.error(error);
|
||||
});
|
||||
if (this.tarPath) await this.untarBinary();
|
||||
if (this.originalBinaryName != this.binaryName) await this.renameBinary();
|
||||
this.logger.info(`${this.originalBinaryName} has been downloaded to ${this.getBinaryPath()}`);
|
||||
}
|
||||
}
|
||||
|
||||
protected async untarBinary() {
|
||||
return new Promise<void>(resolve => {
|
||||
this.logger.debug(`Extracting ${this.originalBinaryName} binary`);
|
||||
tar.x({
|
||||
file: this.tarPath,
|
||||
cwd: this.dirname,
|
||||
}).then((() => {
|
||||
resolve();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
protected async renameBinary() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.logger.debug(`Renaming ${this.originalBinaryName} binary to ${this.binaryName}`);
|
||||
fs.rename(this.getOriginalBinaryPath(), this.getBinaryPath(), (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected async downloadBinary() {
|
||||
const binaryPath = this.tarPath || this.getBinaryPath();
|
||||
|
||||
await ensureDir(this.getBinaryDir(), 0o755);
|
||||
|
||||
const file = fs.createWriteStream(binaryPath);
|
||||
const url = this.getUrl();
|
||||
|
||||
this.logger.info(`Downloading ${this.originalBinaryName} ${this.binaryVersion} from ${url} to ${binaryPath}`);
|
||||
const requestOpts: request.UriOptions & request.CoreOptions = {
|
||||
uri: url,
|
||||
gzip: true,
|
||||
...this.requestOpts,
|
||||
};
|
||||
const stream = request(requestOpts);
|
||||
|
||||
stream.on("complete", () => {
|
||||
this.logger.info(`Download of ${this.originalBinaryName} finished`);
|
||||
file.end();
|
||||
});
|
||||
|
||||
stream.on("error", (error) => {
|
||||
this.logger.error(error);
|
||||
fs.unlink(binaryPath, () => {
|
||||
// do nothing
|
||||
});
|
||||
throw(error);
|
||||
});
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
file.on("close", () => {
|
||||
this.logger.debug(`${this.originalBinaryName} binary download closed`);
|
||||
if (!this.tarPath) fs.chmod(binaryPath, 0o755, (err) => {
|
||||
if (err) reject(err);
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
stream.pipe(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@
|
||||
import type net from "net";
|
||||
import type http from "http";
|
||||
import spdy from "spdy";
|
||||
import httpProxy from "http-proxy";
|
||||
import type httpProxy from "http-proxy";
|
||||
import { apiPrefix, apiKubePrefix } from "../common/vars";
|
||||
import type { Router } from "./router";
|
||||
import type { ContextHandler } from "./context-handler/context-handler";
|
||||
@ -57,14 +57,15 @@ export class LensProxy extends Singleton {
|
||||
protected proxyServer: http.Server;
|
||||
protected closed = false;
|
||||
protected retryCounters = new Map<string, number>();
|
||||
protected proxy = this.createProxy();
|
||||
protected getClusterForRequest: GetClusterForRequest;
|
||||
|
||||
public port: number;
|
||||
|
||||
constructor(protected router: Router, { shellApiRequest, kubeApiRequest, getClusterForRequest }: LensProxyFunctions) {
|
||||
constructor(protected router: Router, protected proxy: httpProxy, { shellApiRequest, kubeApiRequest, getClusterForRequest }: LensProxyFunctions) {
|
||||
super();
|
||||
|
||||
this.configureProxy(proxy);
|
||||
|
||||
this.getClusterForRequest = getClusterForRequest;
|
||||
|
||||
this.proxyServer = spdy.createServer({
|
||||
@ -82,7 +83,9 @@ export class LensProxy extends Singleton {
|
||||
const cluster = getClusterForRequest(req);
|
||||
|
||||
if (!cluster) {
|
||||
return void logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
|
||||
logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
|
||||
|
||||
return socket.destroy();
|
||||
}
|
||||
|
||||
const reqHandler = isInternal ? shellApiRequest : kubeApiRequest;
|
||||
@ -163,9 +166,7 @@ export class LensProxy extends Singleton {
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
protected createProxy(): httpProxy {
|
||||
const proxy = httpProxy.createProxyServer();
|
||||
|
||||
protected configureProxy(proxy: httpProxy): httpProxy {
|
||||
proxy.on("proxyRes", (proxyRes, req, res) => {
|
||||
const retryCounterId = this.getRequestId(req);
|
||||
|
||||
|
||||
18
src/main/native-theme.ts
Normal file
18
src/main/native-theme.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { nativeTheme } from "electron";
|
||||
import { broadcastMessage } from "../common/ipc";
|
||||
import { setNativeThemeChannel } from "../common/ipc/native-theme";
|
||||
|
||||
export function broadcastNativeThemeOnUpdate() {
|
||||
nativeTheme.on("updated", () => {
|
||||
broadcastMessage(setNativeThemeChannel, getNativeColorTheme());
|
||||
});
|
||||
}
|
||||
|
||||
export function getNativeColorTheme() {
|
||||
return nativeTheme.shouldUseDarkColors ? "dark" : "light";
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
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";
|
||||
@ -40,6 +41,7 @@ export interface LensApiRequest<P = any> {
|
||||
query: URLSearchParams;
|
||||
raw: {
|
||||
req: http.IncomingMessage;
|
||||
res: http.ServerResponse;
|
||||
};
|
||||
}
|
||||
|
||||
@ -62,6 +64,7 @@ function getMimeType(filename: string) {
|
||||
|
||||
interface Dependencies {
|
||||
routePortForward: (request: LensApiRequest) => Promise<void>;
|
||||
httpProxy?: httpProxy;
|
||||
}
|
||||
|
||||
export class Router {
|
||||
@ -101,7 +104,7 @@ export class Router {
|
||||
cluster,
|
||||
path: url.pathname,
|
||||
raw: {
|
||||
req,
|
||||
req, res,
|
||||
},
|
||||
response: res,
|
||||
query: url.searchParams,
|
||||
@ -140,12 +143,26 @@ export class Router {
|
||||
filePath = `${publicPath}/${appName}.html`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected addRoutes() {
|
||||
// Static assets
|
||||
this.router.add({ method: "get", path: "/{path*}" }, Router.handleStaticFile);
|
||||
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);
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
* 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";
|
||||
@ -10,9 +12,15 @@ import routePortForwardInjectable
|
||||
const routerInjectable = getInjectable({
|
||||
id: "router",
|
||||
|
||||
instantiate: (di) => new Router({
|
||||
routePortForward: di.inject(routePortForwardInjectable),
|
||||
}),
|
||||
instantiate: (di) => {
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const proxy = isDevelopment ? httpProxy.createProxy() : undefined;
|
||||
|
||||
return new Router({
|
||||
routePortForward: di.inject(routePortForwardInjectable),
|
||||
httpProxy: proxy,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default routerInjectable;
|
||||
|
||||
@ -5,12 +5,12 @@
|
||||
|
||||
import type WebSocket from "ws";
|
||||
import path from "path";
|
||||
import { helmCli } from "../../helm/helm-cli";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type { ClusterId } from "../../../common/cluster-types";
|
||||
import { ShellSession } from "../shell-session";
|
||||
import type { Kubectl } from "../../kubectl/kubectl";
|
||||
import { baseBinariesDir } from "../../../common/vars";
|
||||
|
||||
export class LocalShellSession extends ShellSession {
|
||||
ShellType = "shell";
|
||||
@ -18,9 +18,9 @@ export class LocalShellSession extends ShellSession {
|
||||
constructor(protected shellEnvModify: (clusterId: ClusterId, env: Record<string, string>) => Record<string, string>, kubectl: Kubectl, websocket: WebSocket, cluster: Cluster, terminalId: string) {
|
||||
super(kubectl, websocket, cluster, terminalId);
|
||||
}
|
||||
|
||||
|
||||
protected getPathEntries(): string[] {
|
||||
return [helmCli.getBinaryDir()];
|
||||
return [baseBinariesDir.get()];
|
||||
}
|
||||
|
||||
protected get cwd(): string | undefined {
|
||||
@ -40,17 +40,16 @@ export class LocalShellSession extends ShellSession {
|
||||
}
|
||||
|
||||
protected async getShellArgs(shell: string): Promise<string[]> {
|
||||
const helmpath = helmCli.getBinaryDir();
|
||||
const pathFromPreferences = UserStore.getInstance().kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||
const kubectlPathDir = UserStore.getInstance().downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences);
|
||||
|
||||
switch(path.basename(shell)) {
|
||||
case "powershell.exe":
|
||||
return ["-NoExit", "-command", `& {$Env:PATH="${helmpath};${kubectlPathDir};$Env:PATH"}`];
|
||||
return ["-NoExit", "-command", `& {$Env:PATH="${baseBinariesDir.get()};${kubectlPathDir};$Env:PATH"}`];
|
||||
case "bash":
|
||||
return ["--init-file", path.join(await this.kubectlBinDirP, ".bash_set_path")];
|
||||
case "fish":
|
||||
return ["--login", "--init-command", `export PATH="${helmpath}:${kubectlPathDir}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`];
|
||||
return ["--login", "--init-command", `export PATH="${baseBinariesDir.get()}:${kubectlPathDir}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`];
|
||||
case "zsh":
|
||||
return ["--login"];
|
||||
default:
|
||||
|
||||
@ -76,9 +76,6 @@ export async function bootstrap(di: DiContainer) {
|
||||
logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
|
||||
initializers.initKubeObjectDetailRegistry();
|
||||
|
||||
logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegistry`);
|
||||
initializers.initWorkloadsOverviewDetailRegistry();
|
||||
|
||||
logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`);
|
||||
initializers.initCatalogEntityDetailRegistry();
|
||||
|
||||
|
||||
@ -41,6 +41,10 @@ jest.mock("electron", () => ({
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
ipcRenderer: {
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("./hotbar-toggle-menu-item", () => ({
|
||||
|
||||
@ -45,7 +45,10 @@ const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems })
|
||||
<section id="appearance">
|
||||
<SubTitle title="Theme" />
|
||||
<Select
|
||||
options={themeStore.themeOptions}
|
||||
options={[
|
||||
{ label: "Sync with computer", value: "system" },
|
||||
...themeStore.themeOptions,
|
||||
]}
|
||||
value={userStore.colorTheme}
|
||||
onChange={({ value }) => userStore.colorTheme = value}
|
||||
themeName="lens"
|
||||
|
||||
@ -8,13 +8,12 @@ import { observer } from "mobx-react";
|
||||
import { Input, InputValidators } from "../input";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import { bundledKubectlPath } from "../../../main/kubectl/kubectl";
|
||||
import { SelectOption, Select } from "../select";
|
||||
import { Switch } from "../switch";
|
||||
import { packageMirrors } from "../../../common/user-store/preferences-helpers";
|
||||
import directoryForBinariesInjectable
|
||||
from "../../../common/app-paths/directory-for-binaries/directory-for-binaries.injectable";
|
||||
import directoryForBinariesInjectable from "../../../common/app-paths/directory-for-binaries/directory-for-binaries.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { kubectlBinaryPath } from "../../../common/vars";
|
||||
|
||||
interface Dependencies {
|
||||
defaultPathForKubectlBinaries: string;
|
||||
@ -80,7 +79,7 @@ const NonInjectedKubectlBinaries: React.FC<Dependencies> = observer(({ defaultPa
|
||||
<SubTitle title="Path to kubectl binary" />
|
||||
<Input
|
||||
theme="round-black"
|
||||
placeholder={bundledKubectlPath()}
|
||||
placeholder={kubectlBinaryPath.get()}
|
||||
value={binariesPath}
|
||||
validators={pathValidator}
|
||||
onChange={setBinariesPath}
|
||||
|
||||
@ -15,7 +15,6 @@ import { Notifications } from "../notifications";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Input } from "../input";
|
||||
import { systemName, maxLength } from "../input/input_validators";
|
||||
import type { KubeObjectMetadata } from "../../../common/k8s-api/kube-object";
|
||||
|
||||
export interface CronJobTriggerDialogProps extends Partial<DialogProps> {
|
||||
}
|
||||
@ -80,6 +79,7 @@ export class CronJobTriggerDialog extends Component<CronJobTriggerDialogProps> {
|
||||
}, {
|
||||
spec: cronjobDefinition.spec.jobTemplate.spec,
|
||||
metadata: {
|
||||
annotations: { "cronjob.kubernetes.io/instantiate": "manual" },
|
||||
ownerReferences: [{
|
||||
apiVersion: cronjob.apiVersion,
|
||||
blockOwnerDeletion: true,
|
||||
@ -88,7 +88,7 @@ export class CronJobTriggerDialog extends Component<CronJobTriggerDialogProps> {
|
||||
name: cronjob.metadata.name,
|
||||
uid: cronjob.metadata.uid,
|
||||
}],
|
||||
} as KubeObjectMetadata,
|
||||
},
|
||||
});
|
||||
|
||||
close();
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
||||
import { OverviewStatuses } from "./overview-statuses";
|
||||
import { WorkloadEvents } from "../../initializers/workload-events";
|
||||
import { orderBy } from "lodash/fp";
|
||||
import type { WorkloadsOverviewDetailRegistration } from "./workloads-overview-detail-registration";
|
||||
|
||||
const detailComponentsInjectable = getInjectable({
|
||||
id: "workload-detail-components",
|
||||
|
||||
instantiate: (di) => {
|
||||
const extensions = di.inject(rendererExtensionsInjectable);
|
||||
|
||||
return computed(() => {
|
||||
const extensionRegistrations = extensions
|
||||
.get()
|
||||
.flatMap((extension) => extension.kubeWorkloadsOverviewItems);
|
||||
|
||||
const allRegistrations = [
|
||||
...coreRegistrations,
|
||||
...extensionRegistrations,
|
||||
];
|
||||
|
||||
return getRegistrationsInPriorityOrder(allRegistrations).map(
|
||||
(item) => item.components.Details,
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
const coreRegistrations = [
|
||||
{
|
||||
components: {
|
||||
Details: OverviewStatuses,
|
||||
},
|
||||
},
|
||||
{
|
||||
priority: 5,
|
||||
components: {
|
||||
Details: WorkloadEvents,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const toRegistrationWithDefaultPriority = ({
|
||||
priority = 50,
|
||||
...rest
|
||||
}: WorkloadsOverviewDetailRegistration) => ({
|
||||
priority,
|
||||
...rest,
|
||||
});
|
||||
|
||||
const getRegistrationsInPriorityOrder = (
|
||||
allRegistrations: WorkloadsOverviewDetailRegistration[],
|
||||
) =>
|
||||
orderBy(
|
||||
"priority",
|
||||
"desc",
|
||||
|
||||
allRegistrations.map(toRegistrationWithDefaultPriority),
|
||||
);
|
||||
|
||||
export default detailComponentsInjectable;
|
||||
@ -16,9 +16,8 @@ import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
|
||||
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
||||
import { jobStore } from "../+workloads-jobs/job.store";
|
||||
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
|
||||
import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries";
|
||||
import type { WorkloadsOverviewRouteParams } from "../../../common/routes";
|
||||
import { makeObservable, observable, reaction } from "mobx";
|
||||
import { IComputedValue, makeObservable, observable, reaction } from "mobx";
|
||||
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
|
||||
import { Icon } from "../icon";
|
||||
import { TooltipPosition } from "../tooltip";
|
||||
@ -30,11 +29,13 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import type { Disposer } from "../../../common/utils";
|
||||
import kubeWatchApiInjectable from "../../kube-watch-api/kube-watch-api.injectable";
|
||||
import type { KubeWatchSubscribeStoreOptions } from "../../kube-watch-api/kube-watch-api";
|
||||
import detailComponentsInjectable from "./detail-components.injectable";
|
||||
|
||||
export interface WorkloadsOverviewProps extends RouteComponentProps<WorkloadsOverviewRouteParams> {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
detailComponents: IComputedValue<React.ComponentType<{}>[]>;
|
||||
clusterFrameContext: ClusterFrameContext;
|
||||
subscribeStores: (stores: KubeObjectStore<KubeObject>[], options: KubeWatchSubscribeStoreOptions) => Disposer;
|
||||
}
|
||||
@ -91,13 +92,6 @@ class NonInjectedWorkloadsOverview extends React.Component<WorkloadsOverviewProp
|
||||
}
|
||||
|
||||
render() {
|
||||
const items = WorkloadsOverviewDetailRegistry
|
||||
.getInstance()
|
||||
.getItems()
|
||||
.map(({ components: { Details }}, index) => (
|
||||
<Details key={`workload-overview-${index}`}/>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className="WorkloadsOverview flex column gaps">
|
||||
<div className="header flex gaps align-center">
|
||||
@ -105,7 +99,10 @@ class NonInjectedWorkloadsOverview extends React.Component<WorkloadsOverviewProp
|
||||
{this.renderLoadErrors()}
|
||||
<NamespaceSelectFilter />
|
||||
</div>
|
||||
{items}
|
||||
|
||||
{this.props.detailComponents.get().map((Details, index) => (
|
||||
<Details key={`workload-overview-${index}`} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -116,6 +113,7 @@ export const WorkloadsOverview = withInjectables<Dependencies, WorkloadsOverview
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
detailComponents: di.inject(detailComponentsInjectable),
|
||||
clusterFrameContext: di.inject(clusterFrameContextInjectable),
|
||||
subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores,
|
||||
...props,
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
interface WorkloadsOverviewDetailComponents {
|
||||
Details: React.ComponentType<{}>;
|
||||
}
|
||||
|
||||
export interface WorkloadsOverviewDetailRegistration {
|
||||
components: WorkloadsOverviewDetailComponents;
|
||||
priority?: number;
|
||||
}
|
||||
@ -33,6 +33,10 @@ jest.mock("electron", () => ({
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
ipcRenderer: {
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const initialTabs: DockTab[] = [
|
||||
|
||||
@ -36,6 +36,10 @@ jest.mock("electron", () => ({
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
ipcRenderer: {
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependencies>): LogTabViewModel {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -39,7 +39,7 @@ const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStore
|
||||
model.reloadLogs();
|
||||
|
||||
return model.stopLoadingLogs;
|
||||
}, []);
|
||||
}, [tab.id]);
|
||||
useEffect(() => subscribeStores([
|
||||
podsStore,
|
||||
], {
|
||||
|
||||
@ -26,6 +26,13 @@ const mockHotbars: { [id: string]: any } = {
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
ipcRenderer: {
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("<HotbarRemoveCommand />", () => {
|
||||
let di: DiContainer;
|
||||
let render: DiRender;
|
||||
|
||||
@ -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()}
|
||||
|
||||
@ -0,0 +1,332 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`kube-object-status-icon given info and warning statuses are present, when rendered, renders with statuses 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<i
|
||||
class="Icon KubeObjectStatusIcon warning material focusable"
|
||||
id="tooltip_target_5"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="warning"
|
||||
>
|
||||
warning
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="Tooltip narrow formatter"
|
||||
>
|
||||
<div
|
||||
class="KubeObjectStatusTooltip"
|
||||
>
|
||||
<div
|
||||
class="level warning"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Warning
|
||||
</span>
|
||||
<div
|
||||
class="status msg"
|
||||
>
|
||||
-
|
||||
Some warning status for some-name
|
||||
|
||||
<span
|
||||
class="age"
|
||||
>
|
||||
·
|
||||
2d
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="level info"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Info
|
||||
</span>
|
||||
<div
|
||||
class="status msg"
|
||||
>
|
||||
-
|
||||
Some info status for some-name
|
||||
|
||||
<span
|
||||
class="age"
|
||||
>
|
||||
·
|
||||
2d
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`kube-object-status-icon given level "critical" status, when rendered, renders with status 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<i
|
||||
class="Icon KubeObjectStatusIcon error material focusable"
|
||||
id="tooltip_target_1"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="error"
|
||||
>
|
||||
error
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="Tooltip narrow formatter"
|
||||
>
|
||||
<div
|
||||
class="KubeObjectStatusTooltip"
|
||||
>
|
||||
<div
|
||||
class="level error"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Critical
|
||||
</span>
|
||||
<div
|
||||
class="status msg"
|
||||
>
|
||||
-
|
||||
Some critical status for some-name
|
||||
|
||||
<span
|
||||
class="age"
|
||||
>
|
||||
·
|
||||
2d
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`kube-object-status-icon given level "info" status, when rendered, renders with status 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<i
|
||||
class="Icon KubeObjectStatusIcon info material focusable"
|
||||
id="tooltip_target_2"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="info"
|
||||
>
|
||||
info
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="Tooltip narrow formatter"
|
||||
>
|
||||
<div
|
||||
class="KubeObjectStatusTooltip"
|
||||
>
|
||||
<div
|
||||
class="level info"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Info
|
||||
</span>
|
||||
<div
|
||||
class="status msg"
|
||||
>
|
||||
-
|
||||
Some info status for some-name
|
||||
|
||||
<span
|
||||
class="age"
|
||||
>
|
||||
·
|
||||
2d
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`kube-object-status-icon given level "warning" status, when rendered, renders with status 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<i
|
||||
class="Icon KubeObjectStatusIcon warning material focusable"
|
||||
id="tooltip_target_3"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="warning"
|
||||
>
|
||||
warning
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="Tooltip narrow formatter"
|
||||
>
|
||||
<div
|
||||
class="KubeObjectStatusTooltip"
|
||||
>
|
||||
<div
|
||||
class="level warning"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Warning
|
||||
</span>
|
||||
<div
|
||||
class="status msg"
|
||||
>
|
||||
-
|
||||
Some warning status for some-name
|
||||
|
||||
<span
|
||||
class="age"
|
||||
>
|
||||
·
|
||||
2d
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`kube-object-status-icon given no statuses, when rendered, renders as empty 1`] = `<div />`;
|
||||
|
||||
exports[`kube-object-status-icon given registration for wrong api version, when rendered, renders as empty 1`] = `
|
||||
<body>
|
||||
<div />
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`kube-object-status-icon given registration for wrong kind, when rendered, renders as empty 1`] = `
|
||||
<body>
|
||||
<div />
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`kube-object-status-icon given registration without status for exact kube object, when rendered, renders as empty 1`] = `
|
||||
<body>
|
||||
<div />
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`kube-object-status-icon given status for all levels is present, when rendered, renders with statuses 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<i
|
||||
class="Icon KubeObjectStatusIcon error material focusable"
|
||||
id="tooltip_target_4"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="error"
|
||||
>
|
||||
error
|
||||
</span>
|
||||
<div />
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="Tooltip narrow formatter"
|
||||
>
|
||||
<div
|
||||
class="KubeObjectStatusTooltip"
|
||||
>
|
||||
<div
|
||||
class="level error"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Critical
|
||||
</span>
|
||||
<div
|
||||
class="status msg"
|
||||
>
|
||||
-
|
||||
Some critical status for some-name
|
||||
|
||||
<span
|
||||
class="age"
|
||||
>
|
||||
·
|
||||
2d
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="level warning"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Warning
|
||||
</span>
|
||||
<div
|
||||
class="status msg"
|
||||
>
|
||||
-
|
||||
Some warning status for some-name
|
||||
|
||||
<span
|
||||
class="age"
|
||||
>
|
||||
·
|
||||
2d
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="level info"
|
||||
>
|
||||
<span
|
||||
class="title"
|
||||
>
|
||||
Info
|
||||
</span>
|
||||
<div
|
||||
class="status msg"
|
||||
>
|
||||
-
|
||||
Some info status for some-name
|
||||
|
||||
<span
|
||||
class="age"
|
||||
>
|
||||
·
|
||||
2d
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
||||
import { DiRender, renderFor } from "../test-utils/renderFor";
|
||||
import { computed } from "mobx";
|
||||
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||
import { KubeObjectStatusLevel } from "../../../extensions/renderer-api/kube-object-status";
|
||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { KubeObjectStatusIcon } from "./kube-object-status-icon";
|
||||
import React from "react";
|
||||
import type { KubeObjectStatusRegistration } from "./kube-object-status-registration";
|
||||
|
||||
describe("kube-object-status-icon", () => {
|
||||
let render: DiRender;
|
||||
let kubeObjectStatusRegistrations: KubeObjectStatusRegistration[];
|
||||
|
||||
beforeEach(async () => {
|
||||
// TODO: Make mocking of date in unit tests global
|
||||
global.Date.now = () => new Date("2015-10-21T07:28:00Z").getTime();
|
||||
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
kubeObjectStatusRegistrations = [];
|
||||
|
||||
const someTestExtension = new SomeTestExtension(
|
||||
kubeObjectStatusRegistrations,
|
||||
);
|
||||
|
||||
di.override(rendererExtensionsInjectable, () =>
|
||||
computed(() => [someTestExtension]),
|
||||
);
|
||||
|
||||
await di.runSetups();
|
||||
});
|
||||
|
||||
it("given no statuses, when rendered, renders as empty", () => {
|
||||
const kubeObject = getKubeObjectStub("irrelevant", "irrelevant");
|
||||
|
||||
const { container } = render(<KubeObjectStatusIcon object={kubeObject} />);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('given level "critical" status, when rendered, renders with status', () => {
|
||||
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
|
||||
|
||||
const statusRegistration = getStatusRegistration(
|
||||
KubeObjectStatusLevel.CRITICAL,
|
||||
"critical",
|
||||
"some-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
kubeObjectStatusRegistrations.push(statusRegistration);
|
||||
|
||||
const { baseElement } = render(
|
||||
<KubeObjectStatusIcon object={kubeObject} />,
|
||||
);
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('given level "info" status, when rendered, renders with status', () => {
|
||||
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
|
||||
|
||||
const statusRegistration = getStatusRegistration(
|
||||
KubeObjectStatusLevel.INFO,
|
||||
"info",
|
||||
"some-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
kubeObjectStatusRegistrations.push(statusRegistration);
|
||||
|
||||
const { baseElement } = render(
|
||||
<KubeObjectStatusIcon object={kubeObject} />,
|
||||
);
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('given level "warning" status, when rendered, renders with status', () => {
|
||||
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
|
||||
|
||||
const statusRegistration = getStatusRegistration(
|
||||
KubeObjectStatusLevel.WARNING,
|
||||
"warning",
|
||||
"some-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
kubeObjectStatusRegistrations.push(statusRegistration);
|
||||
|
||||
const { baseElement } = render(
|
||||
<KubeObjectStatusIcon object={kubeObject} />,
|
||||
);
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("given status for all levels is present, when rendered, renders with statuses", () => {
|
||||
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
|
||||
|
||||
const critical = getStatusRegistration(
|
||||
KubeObjectStatusLevel.CRITICAL,
|
||||
"critical",
|
||||
"some-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
const warning = getStatusRegistration(
|
||||
KubeObjectStatusLevel.WARNING,
|
||||
"warning",
|
||||
"some-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
const info = getStatusRegistration(
|
||||
KubeObjectStatusLevel.INFO,
|
||||
"info",
|
||||
"some-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
kubeObjectStatusRegistrations.push(critical);
|
||||
kubeObjectStatusRegistrations.push(warning);
|
||||
kubeObjectStatusRegistrations.push(info);
|
||||
|
||||
const { baseElement } = render(
|
||||
<KubeObjectStatusIcon object={kubeObject} />,
|
||||
);
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("given info and warning statuses are present, when rendered, renders with statuses", () => {
|
||||
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
|
||||
|
||||
const warning = getStatusRegistration(
|
||||
KubeObjectStatusLevel.WARNING,
|
||||
"warning",
|
||||
"some-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
const info = getStatusRegistration(
|
||||
KubeObjectStatusLevel.INFO,
|
||||
"info",
|
||||
"some-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
kubeObjectStatusRegistrations.push(warning);
|
||||
kubeObjectStatusRegistrations.push(info);
|
||||
|
||||
const { baseElement } = render(
|
||||
<KubeObjectStatusIcon object={kubeObject} />,
|
||||
);
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
it("given registration for wrong api version, when rendered, renders as empty", () => {
|
||||
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
|
||||
|
||||
const statusRegistration = getStatusRegistration(
|
||||
KubeObjectStatusLevel.CRITICAL,
|
||||
"irrelevant",
|
||||
"some-kind",
|
||||
["some-other-api-version"],
|
||||
);
|
||||
|
||||
kubeObjectStatusRegistrations.push(statusRegistration);
|
||||
|
||||
const { baseElement } = render(
|
||||
<KubeObjectStatusIcon object={kubeObject} />,
|
||||
);
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("given registration for wrong kind, when rendered, renders as empty", () => {
|
||||
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
|
||||
|
||||
const statusRegistration = getStatusRegistration(
|
||||
KubeObjectStatusLevel.CRITICAL,
|
||||
"irrelevant",
|
||||
"some-other-kind",
|
||||
["some-api-version"],
|
||||
);
|
||||
|
||||
kubeObjectStatusRegistrations.push(statusRegistration);
|
||||
|
||||
const { baseElement } = render(
|
||||
<KubeObjectStatusIcon object={kubeObject} />,
|
||||
);
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("given registration without status for exact kube object, when rendered, renders as empty", () => {
|
||||
const kubeObject = getKubeObjectStub("some-kind", "some-api-version");
|
||||
|
||||
const statusRegistration = {
|
||||
apiVersions: ["some-api-version"],
|
||||
kind: "some-kind",
|
||||
resolve: (): void => {},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
kubeObjectStatusRegistrations.push(statusRegistration);
|
||||
|
||||
const { baseElement } = render(
|
||||
<KubeObjectStatusIcon object={kubeObject} />,
|
||||
);
|
||||
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
const getKubeObjectStub = (kind: string, apiVersion: string) => KubeObject.create({
|
||||
apiVersion,
|
||||
kind,
|
||||
metadata: {
|
||||
uid: "some-uid",
|
||||
name: "some-name",
|
||||
resourceVersion: "some-resource-version",
|
||||
namespace: "some-namespace",
|
||||
},
|
||||
});
|
||||
|
||||
const getStatusRegistration = (level: KubeObjectStatusLevel, title: string, kind: string, apiVersions: string[]) => ({
|
||||
apiVersions,
|
||||
kind,
|
||||
resolve: (kubeObject: KubeObject) => ({
|
||||
level,
|
||||
text: `Some ${title} status for ${kubeObject.getName()}`,
|
||||
timestamp: "2015-10-19T07:28:00Z",
|
||||
}),
|
||||
});
|
||||
|
||||
class SomeTestExtension extends LensRendererExtension {
|
||||
constructor(kubeObjectStatusTexts: KubeObjectStatusRegistration[]) {
|
||||
super({
|
||||
id: "some-id",
|
||||
absolutePath: "irrelevant",
|
||||
isBundled: false,
|
||||
isCompatible: false,
|
||||
isEnabled: false,
|
||||
manifest: { name: "some-id", version: "some-version" },
|
||||
manifestPath: "irrelevant",
|
||||
});
|
||||
|
||||
this.kubeObjectStatusTexts = kubeObjectStatusTexts;
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,8 @@ import React from "react";
|
||||
import { Icon } from "../icon";
|
||||
import { cssNames, formatDuration } from "../../utils";
|
||||
import { KubeObject, KubeObjectStatus, KubeObjectStatusLevel } from "../../..//extensions/renderer-api/k8s-api";
|
||||
import { KubeObjectStatusRegistry } from "../../../extensions/registries";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import statusesForKubeObjectInjectable from "./statuses-for-kube-object.injectable";
|
||||
|
||||
function statusClassName(level: KubeObjectStatusLevel): string {
|
||||
switch (level) {
|
||||
@ -68,7 +69,11 @@ export interface KubeObjectStatusIconProps {
|
||||
object: KubeObject;
|
||||
}
|
||||
|
||||
export class KubeObjectStatusIcon extends React.Component<KubeObjectStatusIconProps> {
|
||||
interface Dependencies {
|
||||
statuses: KubeObjectStatus[];
|
||||
}
|
||||
|
||||
class NonInjectedKubeObjectStatusIcon extends React.Component<KubeObjectStatusIconProps & Dependencies> {
|
||||
renderStatuses(statuses: KubeObjectStatus[], level: number) {
|
||||
const filteredStatuses = statuses.filter((item) => item.level == level);
|
||||
|
||||
@ -89,7 +94,7 @@ export class KubeObjectStatusIcon extends React.Component<KubeObjectStatusIconPr
|
||||
}
|
||||
|
||||
render() {
|
||||
const statuses = KubeObjectStatusRegistry.getInstance().getItemsForObject(this.props.object);
|
||||
const statuses = this.props.statuses;
|
||||
|
||||
if (statuses.length === 0) {
|
||||
return null;
|
||||
@ -114,3 +119,14 @@ export class KubeObjectStatusIcon extends React.Component<KubeObjectStatusIconPr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const KubeObjectStatusIcon = withInjectables<Dependencies, KubeObjectStatusIconProps>(
|
||||
NonInjectedKubeObjectStatusIcon,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
statuses: di.inject(statusesForKubeObjectInjectable, props.object),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import type { KubeObjectStatus } from "../../../extensions/renderer-api/kube-object-status";
|
||||
|
||||
export interface KubeObjectStatusRegistration {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
resolve: (object: KubeObject) => KubeObjectStatus;
|
||||
}
|
||||
@ -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, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
||||
|
||||
const statusRegistrationsInjectable = getInjectable({
|
||||
id: "status-registrations",
|
||||
|
||||
instantiate: (di) => {
|
||||
const extensions = di.inject(rendererExtensionsInjectable);
|
||||
|
||||
return computed(() =>
|
||||
extensions.get().flatMap((extension) => extension.kubeObjectStatusTexts),
|
||||
);
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default statusRegistrationsInjectable;
|
||||
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import statusRegistrationsInjectable from "./status-registrations.injectable";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { conforms, eq, includes } from "lodash/fp";
|
||||
import type { KubeObjectStatusRegistration } from "./kube-object-status-registration";
|
||||
|
||||
const statusesForKubeObjectInjectable = getInjectable({
|
||||
id: "statuses-for-kube-object",
|
||||
|
||||
instantiate: (di, kubeObject: KubeObject) =>
|
||||
di
|
||||
.inject(statusRegistrationsInjectable)
|
||||
.get()
|
||||
.filter(toKubeObjectRelated(kubeObject))
|
||||
.map(toStatus(kubeObject))
|
||||
.filter(Boolean),
|
||||
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
const toKubeObjectRelated = (kubeObject: KubeObject) =>
|
||||
conforms({
|
||||
kind: eq(kubeObject.kind),
|
||||
apiVersions: includes(kubeObject.apiVersion),
|
||||
});
|
||||
|
||||
const toStatus =
|
||||
(kubeObject: KubeObject) => (item: KubeObjectStatusRegistration) =>
|
||||
item.resolve(kubeObject);
|
||||
|
||||
export default statusesForKubeObjectInjectable;
|
||||
163
src/renderer/components/select/select.test.tsx
Normal file
163
src/renderer/components/select/select.test.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import React from "react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { Select } from "./select";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { DiRender, renderFor } from "../test-utils/renderFor";
|
||||
import mockFs from "mock-fs";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
||||
import { computed } from "mobx";
|
||||
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
ipcRenderer: {
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("<Select />", () => {
|
||||
let di: DiContainer;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
render = renderFor(di);
|
||||
|
||||
mockFs();
|
||||
|
||||
await di.runSetups();
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(rendererExtensionsInjectable, () => computed(() => [] as LensRendererExtension[]));
|
||||
|
||||
UserStore.createInstance();
|
||||
ThemeStore.createInstance();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ThemeStore.resetInstance();
|
||||
UserStore.resetInstance();
|
||||
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it("should render the select", async () => {
|
||||
const options = [
|
||||
{
|
||||
label: "Option one label",
|
||||
value: "optionOneValue",
|
||||
},
|
||||
{
|
||||
label: "Option two label",
|
||||
value: "optionTwoValue",
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { container } = render(<Select onChange={onChange} options={options} />);
|
||||
|
||||
expect(container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it("should show selected option", async () => {
|
||||
const options = [
|
||||
{
|
||||
label: "Option one label",
|
||||
value: "optionOneValue",
|
||||
},
|
||||
{
|
||||
label: "Option two label",
|
||||
value: "optionTwoValue",
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { container } = render(<Select value={options[0].value} onChange={onChange} options={options} />);
|
||||
const selectedValueContainer = container.querySelector(".Select__single-value");
|
||||
|
||||
expect(selectedValueContainer.textContent).toBe(options[0].label);
|
||||
});
|
||||
|
||||
it("should reflect to change value", async () => {
|
||||
const options = [
|
||||
{
|
||||
label: "Option one label",
|
||||
value: "optionOneValue",
|
||||
},
|
||||
{
|
||||
label: "Option two label",
|
||||
value: "optionTwoValue",
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { container, rerender } = render(<Select value={options[0].value} onChange={onChange} options={options} />);
|
||||
const selectedValueContainer = container.querySelector(".Select__single-value");
|
||||
|
||||
expect(selectedValueContainer.textContent).toBe(options[0].label);
|
||||
|
||||
rerender(<Select value={options[1].value} onChange={onChange} options={options} />);
|
||||
|
||||
expect(container.querySelector(".Select__single-value").textContent).toBe(options[1].label);
|
||||
});
|
||||
|
||||
it("should unselect value if null is passed as a value", async () => {
|
||||
const options = [
|
||||
{
|
||||
label: "Option one label",
|
||||
value: "optionOneValue",
|
||||
},
|
||||
{
|
||||
label: "Option two label",
|
||||
value: "optionTwoValue",
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { container, rerender } = render(<Select value={options[0].value} onChange={onChange} options={options} />);
|
||||
const selectedValueContainer = container.querySelector(".Select__single-value");
|
||||
|
||||
expect(selectedValueContainer.textContent).toBe(options[0].label);
|
||||
|
||||
rerender(<Select value={null} onChange={onChange} options={options} />);
|
||||
|
||||
expect(container.querySelector(".Select__single-value")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should unselect value if undefined is passed as a value", async () => {
|
||||
const options = [
|
||||
{
|
||||
label: "Option one label",
|
||||
value: "optionOneValue",
|
||||
},
|
||||
{
|
||||
label: "Option two label",
|
||||
value: "optionTwoValue",
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { container, rerender } = render(<Select value={options[0].value} onChange={onChange} options={options} />);
|
||||
const selectedValueContainer = container.querySelector(".Select__single-value");
|
||||
|
||||
expect(selectedValueContainer.textContent).toBe(options[0].label);
|
||||
|
||||
rerender(<Select value={undefined} onChange={onChange} options={options} />);
|
||||
|
||||
expect(container.querySelector(".Select__single-value")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -80,7 +80,7 @@ export class Select extends React.Component<SelectProps> {
|
||||
});
|
||||
}
|
||||
|
||||
return this.options.find(opt => opt === value || opt.value === value);
|
||||
return this.options.find(opt => opt === value || opt.value === value) || null;
|
||||
}
|
||||
|
||||
@computed get options(): SelectOption[] {
|
||||
|
||||
@ -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}`,
|
||||
|
||||
@ -9,5 +9,4 @@ export * from "./entity-settings-registry";
|
||||
export * from "./ipc";
|
||||
export * from "./kube-object-detail-registry";
|
||||
export * from "./registries";
|
||||
export * from "./workloads-overview-detail-registry";
|
||||
export * from "./catalog-category-registry";
|
||||
|
||||
@ -12,6 +12,4 @@ export function initRegistries() {
|
||||
registries.EntitySettingRegistry.createInstance();
|
||||
registries.GlobalPageRegistry.createInstance();
|
||||
registries.KubeObjectDetailRegistry.createInstance();
|
||||
registries.KubeObjectStatusRegistry.createInstance();
|
||||
registries.WorkloadsOverviewDetailRegistry.createInstance();
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { WorkloadsOverviewDetailRegistry } from "../../extensions/registries";
|
||||
import { OverviewStatuses } from "../components/+workloads-overview/overview-statuses";
|
||||
import { WorkloadEvents } from "./workload-events";
|
||||
|
||||
export function initWorkloadsOverviewDetailRegistry() {
|
||||
WorkloadsOverviewDetailRegistry.getInstance()
|
||||
.add([
|
||||
{
|
||||
components: {
|
||||
Details: OverviewStatuses,
|
||||
},
|
||||
},
|
||||
{
|
||||
priority: 5,
|
||||
components: {
|
||||
Details: WorkloadEvents,
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
@ -13,6 +13,8 @@ import type { SelectOption } from "./components/select";
|
||||
import type { MonacoEditorProps } from "./components/monaco-editor";
|
||||
import { defaultTheme } from "../common/vars";
|
||||
import { camelCase } from "lodash";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { getNativeThemeChannel, setNativeThemeChannel } from "../common/ipc/native-theme";
|
||||
|
||||
export type ThemeId = string;
|
||||
|
||||
@ -34,6 +36,8 @@ export class ThemeStore extends Singleton {
|
||||
"lens-light": lensLightThemeJson as Theme,
|
||||
});
|
||||
|
||||
@observable osNativeTheme: "dark" | "light" | undefined;
|
||||
|
||||
@computed get activeThemeId(): ThemeId {
|
||||
return UserStore.getInstance().colorTheme;
|
||||
}
|
||||
@ -43,7 +47,7 @@ export class ThemeStore extends Singleton {
|
||||
}
|
||||
|
||||
@computed get activeTheme(): Theme {
|
||||
return this.themes.get(this.activeThemeId) ?? this.themes.get(defaultTheme);
|
||||
return this.systemTheme ?? this.themes.get(this.activeThemeId) ?? this.themes.get(defaultTheme);
|
||||
}
|
||||
|
||||
@computed get terminalColors(): [string, string][] {
|
||||
@ -72,11 +76,25 @@ export class ThemeStore extends Singleton {
|
||||
}));
|
||||
}
|
||||
|
||||
@computed get systemTheme() {
|
||||
if (this.activeThemeId == "system" && this.osNativeTheme) {
|
||||
return this.themes.get(`lens-${this.osNativeTheme}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.setNativeTheme();
|
||||
this.bindNativeThemeUpdateEvent();
|
||||
|
||||
// auto-apply active theme
|
||||
reaction(() => ({
|
||||
@ -95,12 +113,26 @@ export class ThemeStore extends Singleton {
|
||||
});
|
||||
}
|
||||
|
||||
bindNativeThemeUpdateEvent() {
|
||||
ipcRenderer.on(setNativeThemeChannel, (event, theme: "dark" | "light") => {
|
||||
this.osNativeTheme = theme;
|
||||
this.applyTheme(theme);
|
||||
});
|
||||
}
|
||||
|
||||
async setNativeTheme() {
|
||||
const theme: "dark" | "light" = await ipcRenderer.invoke(getNativeThemeChannel);
|
||||
|
||||
this.osNativeTheme = theme;
|
||||
}
|
||||
|
||||
getThemeById(themeId: ThemeId): Theme {
|
||||
return this.themes.get(themeId);
|
||||
}
|
||||
|
||||
protected applyTheme(themeId: ThemeId) {
|
||||
const theme = this.getThemeById(themeId);
|
||||
const theme = this.systemTheme ?? this.getThemeById(themeId);
|
||||
|
||||
const colors = Object.entries({
|
||||
...theme.colors,
|
||||
...Object.fromEntries(this.terminalColors),
|
||||
|
||||
@ -9,20 +9,18 @@ import { webpackLensRenderer } from "./webpack.renderer";
|
||||
import { buildDir } from "./src/common/vars";
|
||||
import logger from "./src/common/logger";
|
||||
|
||||
export interface DevServer extends WebpackDevServer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates `webpack-dev-server`
|
||||
* API docs:
|
||||
* @url https://webpack.js.org/configuration/dev-server/
|
||||
* @url https://github.com/chimurai/http-proxy-middleware
|
||||
*/
|
||||
export function createDevServer(lensProxyPort: number): DevServer {
|
||||
function createDevServer(): WebpackDevServer {
|
||||
const config = webpackLensRenderer({ showVars: false });
|
||||
const compiler = Webpack(config);
|
||||
|
||||
const server = new WebpackDevServer({
|
||||
setupExitSignals: true,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
@ -31,17 +29,13 @@ export function createDevServer(lensProxyPort: number): DevServer {
|
||||
static: buildDir, // aka `devServer.contentBase` in webpack@4
|
||||
hot: "only", // use HMR only without errors
|
||||
liveReload: false,
|
||||
devMiddleware: {
|
||||
writeToDisk: false,
|
||||
index: "OpenLensDev.html",
|
||||
publicPath: "/build",
|
||||
},
|
||||
proxy: {
|
||||
"*": {
|
||||
router(req) {
|
||||
logger.silly(`[WEBPACK-DEV-SERVER]: proxy path ${req.path}`, req.headers);
|
||||
|
||||
return `http://localhost:${lensProxyPort}`;
|
||||
},
|
||||
secure: false, // allow http connections
|
||||
ws: true, // proxy websockets, e.g. terminal
|
||||
logLevel: "error",
|
||||
},
|
||||
"^/$": "/build/",
|
||||
},
|
||||
client: {
|
||||
overlay: false, // don't show warnings and errors on top of rendered app view
|
||||
@ -53,3 +47,7 @@ export function createDevServer(lensProxyPort: number): DevServer {
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
const server = createDevServer();
|
||||
|
||||
server.start();
|
||||
|
||||
@ -15,15 +15,16 @@ import { iconsAndImagesWebpackRules } from "./webpack.renderer";
|
||||
const configs: { (): webpack.Configuration }[] = [];
|
||||
|
||||
configs.push((): webpack.Configuration => {
|
||||
console.info("WEBPACK:main", vars);
|
||||
console.info("WEBPACK:main", { ...vars });
|
||||
const { mainDir, buildDir, isDevelopment } = vars;
|
||||
|
||||
return {
|
||||
name: "lens-app-main",
|
||||
context: __dirname,
|
||||
target: "electron-main",
|
||||
mode: isDevelopment ? "development" : "production",
|
||||
devtool: isDevelopment ? "cheap-module-source-map" : "source-map",
|
||||
cache: isDevelopment,
|
||||
cache: isDevelopment ? { type: "filesystem" } : false,
|
||||
entry: {
|
||||
main: path.resolve(mainDir, "index.ts"),
|
||||
},
|
||||
@ -49,7 +50,6 @@ configs.push((): webpack.Configuration => {
|
||||
},
|
||||
plugins: [
|
||||
new ForkTsCheckerPlugin(),
|
||||
|
||||
new CircularDependencyPlugin({
|
||||
cwd: __dirname,
|
||||
exclude: /node_modules/,
|
||||
|
||||
@ -16,7 +16,7 @@ import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
|
||||
|
||||
export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration {
|
||||
if (showVars) {
|
||||
console.info("WEBPACK:renderer", vars);
|
||||
console.info("WEBPACK:renderer", { ...vars });
|
||||
}
|
||||
|
||||
const assetsFolderName = "assets";
|
||||
@ -24,11 +24,11 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
|
||||
return {
|
||||
target: "electron-renderer",
|
||||
name: "lens-app",
|
||||
name: "lens-app-renderer",
|
||||
mode: isDevelopment ? "development" : "production",
|
||||
// https://webpack.js.org/configuration/devtool/ (see description of each option)
|
||||
devtool: isDevelopment ? "cheap-module-source-map" : "source-map",
|
||||
cache: isDevelopment,
|
||||
cache: isDevelopment ? { type: "filesystem" } : false,
|
||||
entry: {
|
||||
[appName]: path.resolve(rendererDir, "bootstrap.tsx"),
|
||||
},
|
||||
|
||||
137
yarn.lock
137
yarn.lock
@ -1083,7 +1083,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 +1093,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"
|
||||
@ -1361,6 +1366,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cli-progress@^3.9.2":
|
||||
version "3.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/cli-progress/-/cli-progress-3.9.2.tgz#6ca355f96268af39bee9f9307f0ac96145639c26"
|
||||
integrity sha512-VO5/X5Ij+oVgEVjg5u0IXVe3JQSKJX+Ev8C5x+0hPy0AuWyW+bF8tbajR7cPFnDGhs7pidztcac+ccrDtk5teA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/color-convert@*":
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d"
|
||||
@ -1491,6 +1503,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/gunzip-maybe@^1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/gunzip-maybe/-/gunzip-maybe-1.4.0.tgz#9410fd15ff68eca8907b7b9198e63e2a7c14d511"
|
||||
integrity sha512-dFP9GrYAR9KhsjTkWJ8q8Gsfql75YIKcg9DuQOj/IrlPzR7W+1zX+cclw1McV82UXAQ+Lpufvgk3e9bC8+HzgA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/history@*", "@types/history@^4.7.8":
|
||||
version "4.7.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
|
||||
@ -1956,6 +1975,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
|
||||
integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==
|
||||
|
||||
"@types/tar-stream@^2.2.2":
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/tar-stream/-/tar-stream-2.2.2.tgz#be9d0be9404166e4b114151f93e8442e6ab6fb1d"
|
||||
integrity sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/tar@^4.0.3", "@types/tar@^4.0.5":
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.5.tgz#5f953f183e36a15c6ce3f336568f6051b7b183f3"
|
||||
@ -2979,12 +3005,7 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base64-js@^1.0.2:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||
|
||||
base64-js@^1.5.1:
|
||||
base64-js@^1.0.2, base64-js@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
@ -3163,6 +3184,13 @@ browser-process-hrtime@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
||||
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
|
||||
|
||||
browserify-zlib@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
|
||||
integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=
|
||||
dependencies:
|
||||
pako "~0.2.0"
|
||||
|
||||
browserslist@^4.14.5:
|
||||
version "4.19.1"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3"
|
||||
@ -3613,6 +3641,13 @@ cli-columns@^3.1.2:
|
||||
string-width "^2.0.0"
|
||||
strip-ansi "^3.0.1"
|
||||
|
||||
cli-progress@^3.10.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.10.0.tgz#63fd9d6343c598c93542fdfa3563a8b59887d78a"
|
||||
integrity sha512-kLORQrhYCAtUPLZxqsAt2YJGOvRdt34+O6jl5cQGb7iF3dM55FQZlTR+rQyIK9JUcO9bBMwZsTlND+3dmFU2Cw==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
|
||||
cli-table3@^0.5.0, cli-table3@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
|
||||
@ -4753,7 +4788,7 @@ duplexer3@^0.1.4:
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
|
||||
|
||||
duplexify@^3.4.2, duplexify@^3.6.0:
|
||||
duplexify@^3.4.2, duplexify@^3.5.0, duplexify@^3.6.0:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
|
||||
integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
|
||||
@ -6017,9 +6052,9 @@ fn.name@1.x.x:
|
||||
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.14.7"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
|
||||
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
|
||||
version "1.14.8"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
|
||||
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
@ -6520,6 +6555,18 @@ growly@^1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
||||
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
|
||||
|
||||
gunzip-maybe@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
|
||||
integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==
|
||||
dependencies:
|
||||
browserify-zlib "^0.1.4"
|
||||
is-deflate "^1.0.0"
|
||||
is-gzip "^1.0.0"
|
||||
peek-stream "^1.1.0"
|
||||
pumpify "^1.3.3"
|
||||
through2 "^2.0.3"
|
||||
|
||||
handle-thing@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
|
||||
@ -7241,6 +7288,11 @@ is-date-object@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
|
||||
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
|
||||
|
||||
is-deflate@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14"
|
||||
integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=
|
||||
|
||||
is-descriptor@^0.1.0:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
|
||||
@ -7320,6 +7372,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-gzip@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83"
|
||||
integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=
|
||||
|
||||
is-in-browser@^1.0.2, is-in-browser@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
|
||||
@ -9315,9 +9372,9 @@ nan@^2.14.0:
|
||||
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
|
||||
|
||||
nanoid@^3.1.30:
|
||||
version "3.1.30"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362"
|
||||
integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
|
||||
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
@ -10257,6 +10314,11 @@ pacote@^9.1.0, pacote@^9.5.12, pacote@^9.5.3:
|
||||
unique-filename "^1.1.1"
|
||||
which "^1.3.1"
|
||||
|
||||
pako@~0.2.0:
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
|
||||
integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=
|
||||
|
||||
pako@~1.0.2:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
@ -10395,6 +10457,15 @@ path-type@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
peek-stream@^1.1.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67"
|
||||
integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
duplexify "^3.5.0"
|
||||
through2 "^2.0.3"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
@ -10486,13 +10557,12 @@ playwright@^1.17.1:
|
||||
playwright-core "=1.17.1"
|
||||
|
||||
plist@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.2.tgz#74bbf011124b90421c22d15779cee60060ba95bc"
|
||||
integrity sha512-MSrkwZBdQ6YapHy87/8hDU8MnIcyxBKjeF+McXnr5A9MtffPewTs7G3hlpodT5TacyfIyFTaJEhh3GGcmasTgQ==
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.4.tgz#a62df837e3aed2bb3b735899d510c4f186019cbe"
|
||||
integrity sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==
|
||||
dependencies:
|
||||
base64-js "^1.5.1"
|
||||
xmlbuilder "^9.0.7"
|
||||
xmldom "^0.5.0"
|
||||
|
||||
pngjs@^5.0.0:
|
||||
version "5.0.0"
|
||||
@ -11976,9 +12046,9 @@ simple-concat@^1.0.0:
|
||||
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
|
||||
|
||||
simple-get@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675"
|
||||
integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
|
||||
integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
|
||||
dependencies:
|
||||
decompress-response "^6.0.0"
|
||||
once "^1.3.1"
|
||||
@ -12676,7 +12746,7 @@ tar-fs@^2.0.0, tar-fs@^2.1.1:
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.1.4"
|
||||
|
||||
tar-stream@^2.1.4:
|
||||
tar-stream@^2.1.4, tar-stream@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
|
||||
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
|
||||
@ -12814,7 +12884,7 @@ throat@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
|
||||
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
|
||||
|
||||
through2@^2.0.0:
|
||||
through2@^2.0.0, through2@^2.0.3:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
|
||||
integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
|
||||
@ -13128,10 +13198,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"
|
||||
@ -13386,10 +13456,10 @@ url-parse-lax@^3.0.0:
|
||||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
|
||||
url-parse@^1.5.3:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862"
|
||||
integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==
|
||||
url-parse@^1.5.10:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
@ -13973,11 +14043,6 @@ xmlchars@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xmldom@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
|
||||
integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
|
||||
|
||||
xtend@^4.0.2, xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user