Restructure to monorepo (#6907)
* wip: restructure to monorepo Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * refactor create-release-pr to a package Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * build fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * github workflow fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix typo Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * add webpack-env types to core Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix github workflows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * refactor/fix integration tests Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * lint fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * yarn run dev Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * eslint settings for vscode Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * move templates to right package Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * open-lens build fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * integration test fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix nx task dependencies Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * use bash shell for unit tests in test workflow Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix test:unit for windows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix win-ca webpack error in open-lens Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix win-ca webpack error in open-lens Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix build:app on windows Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * remove ELECTRON_BUILDER_EXTRA_ARGS Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * sync src/ from master Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * remove Makefile from core Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
No files matched your search
@@ -0,0 +1,4 @@
|
||||
static/build
|
||||
binaries/
|
||||
dist/
|
||||
node_modules/
|
||||
@@ -0,0 +1,3 @@
|
||||
disturl "https://electronjs.org/headers"
|
||||
target "19.0.4"
|
||||
runtime "electron"
|
||||
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* 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 { FileHandle } from "fs/promises";
|
||||
import { open } from "fs/promises";
|
||||
import type { WriteStream } from "fs-extra";
|
||||
import { constants, ensureDir, unlink } from "fs-extra";
|
||||
import path from "path";
|
||||
import type * as FetchModule from "node-fetch";
|
||||
import { promisify } from "util";
|
||||
import { pipeline as _pipeline, Transform, Writable } from "stream";
|
||||
import type { SingleBar } from "cli-progress";
|
||||
import { MultiBar } from "cli-progress";
|
||||
import { extract } from "tar-stream";
|
||||
import gunzip from "gunzip-maybe";
|
||||
import { isErrnoException, setTimeoutFor } from "../../core/src/common/utils";
|
||||
import AbortController from "abort-controller";
|
||||
|
||||
type Response = FetchModule.Response;
|
||||
type RequestInfo = FetchModule.RequestInfo;
|
||||
type RequestInit = FetchModule.RequestInit;
|
||||
|
||||
const pipeline = promisify(_pipeline);
|
||||
|
||||
const getBinaryName = (binaryName: string, { forPlatform }: { forPlatform : string }) => {
|
||||
if (forPlatform === "windows") {
|
||||
return `${binaryName}.exe`;
|
||||
}
|
||||
|
||||
return binaryName;
|
||||
};
|
||||
|
||||
interface BinaryDownloaderArgs {
|
||||
readonly version: string;
|
||||
readonly platform: SupportedPlatform;
|
||||
readonly downloadArch: string;
|
||||
readonly fileArch: string;
|
||||
readonly binaryName: string;
|
||||
readonly baseDir: string;
|
||||
}
|
||||
|
||||
interface BinaryDownloaderDependencies {
|
||||
fetch: (url: RequestInfo, init?: RequestInit) => Promise<Response>;
|
||||
}
|
||||
|
||||
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(protected readonly dependencies: BinaryDownloaderDependencies, 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> {
|
||||
if (process.env.LENS_SKIP_DOWNLOAD_BINARIES === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
setTimeoutFor(controller, 15 * 60 * 1000);
|
||||
|
||||
const stream = await this.dependencies.fetch(this.url, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
const total = Number(stream.headers.get("content-length"));
|
||||
const bar = this.bar;
|
||||
let fileHandle: FileHandle | undefined = undefined;
|
||||
|
||||
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
|
||||
*/
|
||||
const handle = fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
|
||||
|
||||
if (!stream.body) {
|
||||
throw new Error("no body on stream");
|
||||
}
|
||||
|
||||
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) {
|
||||
handle.write(chunk)
|
||||
.then(() => cb())
|
||||
.catch(cb);
|
||||
},
|
||||
})),
|
||||
);
|
||||
await fileHandle.chmod(0o755);
|
||||
await fileHandle.close();
|
||||
} catch (error) {
|
||||
await fileHandle?.close();
|
||||
|
||||
if (isErrnoException(error) && 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(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||
const binaryName = getBinaryName("lens-k8s-proxy", { forPlatform: args.platform });
|
||||
|
||||
super(deps, { ...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(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||
const binaryName = getBinaryName("kubectl", { forPlatform: args.platform });
|
||||
|
||||
super(deps, { ...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(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||
const binaryName = getBinaryName("helm", { forPlatform: args.platform });
|
||||
|
||||
super(deps, { ...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";
|
||||
|
||||
const importFetchModule = new Function('return import("node-fetch")') as () => Promise<typeof FetchModule>;
|
||||
|
||||
async function main() {
|
||||
const deps: BinaryDownloaderDependencies = {
|
||||
fetch: (await importFetchModule()).default,
|
||||
};
|
||||
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`);
|
||||
}
|
||||
})();
|
||||
const multiBar = new MultiBar({
|
||||
align: "left",
|
||||
clearOnComplete: false,
|
||||
hideCursor: true,
|
||||
autopadding: true,
|
||||
noTTYOutput: true,
|
||||
format: "[{bar}] {percentage}% | {downloadArch} {binaryName}",
|
||||
});
|
||||
const baseDir = path.join(process.cwd(), "binaries", "client");
|
||||
const downloaders: BinaryDownloader[] = [
|
||||
new LensK8sProxyDownloader(deps, {
|
||||
version: packageInfo.config.k8sProxyVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "amd64",
|
||||
fileArch: "x64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
new KubectlDownloader(deps, {
|
||||
version: packageInfo.config.bundledKubectlVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "amd64",
|
||||
fileArch: "x64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
new HelmDownloader(deps, {
|
||||
version: packageInfo.config.bundledHelmVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "amd64",
|
||||
fileArch: "x64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
];
|
||||
|
||||
if (normalizedPlatform !== "windows") {
|
||||
downloaders.push(
|
||||
new LensK8sProxyDownloader(deps, {
|
||||
version: packageInfo.config.k8sProxyVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "arm64",
|
||||
fileArch: "arm64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
new KubectlDownloader(deps, {
|
||||
version: packageInfo.config.bundledKubectlVersion,
|
||||
platform: normalizedPlatform,
|
||||
downloadArch: "arm64",
|
||||
fileArch: "arm64",
|
||||
baseDir,
|
||||
}, multiBar),
|
||||
new HelmDownloader(deps, {
|
||||
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));
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { ensureDir, readFile } from "fs-extra";
|
||||
import { JSDOM } from "jsdom";
|
||||
import path from "path";
|
||||
import sharp from "sharp";
|
||||
|
||||
const size = Number(process.env.OUTPUT_SIZE || "16");
|
||||
const outputFolder = process.env.OUTPUT_DIR || "./static/build/tray";
|
||||
const inputFile = process.env.INPUT_SVG_PATH || path.resolve(__dirname, "../../core/src/renderer/components/icon/logo-lens.svg");
|
||||
const noticeFile = process.env.NOTICE_SVG_PATH || path.resolve(__dirname, "../../core/src/renderer/components/icon/notice.svg");
|
||||
const spinnerFile = process.env.SPINNER_SVG_PATH || path.resolve(__dirname, "../../core/src/renderer/components/icon/arrow-spinner.svg");
|
||||
|
||||
async function ensureOutputFoler() {
|
||||
await ensureDir(outputFolder);
|
||||
}
|
||||
|
||||
function getSvgStyling(colouring: "dark" | "light"): string {
|
||||
return `
|
||||
<style>
|
||||
ellipse {
|
||||
stroke: ${colouring === "dark" ? "white" : "black"} !important;
|
||||
}
|
||||
path, rect {
|
||||
fill: ${colouring === "dark" ? "white" : "black"} !important;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
type TargetSystems = "macos" | "windows-or-linux";
|
||||
|
||||
async function getBaseIconImage(system: TargetSystems) {
|
||||
const svgData = await readFile(inputFile, { encoding: "utf-8" });
|
||||
const dom = new JSDOM(`<body>${svgData}</body>`);
|
||||
const root = dom.window.document.body.getElementsByTagName("svg")[0];
|
||||
|
||||
root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark");
|
||||
|
||||
return Buffer.from(root.outerHTML);
|
||||
}
|
||||
|
||||
async function generateImage(image: Buffer, size: number, namePrefix: string) {
|
||||
sharp(image)
|
||||
.resize({ width: size, height: size })
|
||||
.png()
|
||||
.toFile(path.join(outputFolder, `${namePrefix}.png`));
|
||||
}
|
||||
|
||||
async function generateImages(image: Buffer, size: number, name: string) {
|
||||
await Promise.all([
|
||||
generateImage(image, size, name),
|
||||
generateImage(image, size*2, `${name}@2x`),
|
||||
generateImage(image, size*3, `${name}@3x`),
|
||||
generateImage(image, size*4, `${name}@4x`),
|
||||
]);
|
||||
}
|
||||
|
||||
async function generateImageWithSvg(baseImage: Buffer, system: TargetSystems, filePath: string) {
|
||||
const svgFile = await getIconImage(system, filePath);
|
||||
|
||||
const circleBuffer = await sharp(Buffer.from(`
|
||||
<svg viewBox="0 0 64 64">
|
||||
<circle cx="32" cy="32" r="32" fill="black" />
|
||||
</svg>
|
||||
`))
|
||||
.toBuffer();
|
||||
|
||||
return sharp(baseImage)
|
||||
.resize({ width: 128, height: 128 })
|
||||
.composite([
|
||||
{
|
||||
input: circleBuffer,
|
||||
top: 64,
|
||||
left: 64,
|
||||
blend: "dest-out",
|
||||
},
|
||||
{
|
||||
input: (
|
||||
await sharp(svgFile)
|
||||
.resize({
|
||||
width: 60,
|
||||
height: 60,
|
||||
})
|
||||
.toBuffer()
|
||||
),
|
||||
top: 66,
|
||||
left: 66,
|
||||
},
|
||||
])
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
async function getIconImage(system: TargetSystems, filePath: string) {
|
||||
const svgData = await readFile(filePath, { encoding: "utf-8" });
|
||||
const root = new JSDOM(svgData).window.document.getElementsByTagName("svg")[0];
|
||||
|
||||
root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark");
|
||||
|
||||
return Buffer.from(root.outerHTML);
|
||||
}
|
||||
|
||||
async function generateTrayIcons() {
|
||||
try {
|
||||
console.log("Generating tray icon pngs");
|
||||
await ensureOutputFoler();
|
||||
|
||||
const baseIconTemplateImage = await getBaseIconImage("macos");
|
||||
const baseIconImage = await getBaseIconImage("windows-or-linux");
|
||||
|
||||
const updateAvailableTemplateImage = await generateImageWithSvg(baseIconTemplateImage, "macos", noticeFile);
|
||||
const updateAvailableImage = await generateImageWithSvg(baseIconImage, "windows-or-linux", noticeFile);
|
||||
|
||||
const checkingForUpdatesTemplateImage = await generateImageWithSvg(baseIconTemplateImage, "macos", spinnerFile);
|
||||
const checkingForUpdatesImage = await generateImageWithSvg(baseIconImage, "windows-or-linux", spinnerFile);
|
||||
|
||||
await Promise.all([
|
||||
// Templates are for macOS only
|
||||
generateImages(baseIconTemplateImage, size, "trayIconTemplate"),
|
||||
generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"),
|
||||
generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"),
|
||||
generateImages(checkingForUpdatesTemplateImage, size, "trayIconCheckingForUpdatesTemplate"),
|
||||
|
||||
// Non-templates are for windows and linux
|
||||
generateImages(baseIconImage, size, "trayIcon"),
|
||||
generateImages(updateAvailableImage, size, "trayIconUpdateAvailable"),
|
||||
generateImages(checkingForUpdatesImage, size, "trayIconCheckingForUpdates"),
|
||||
]);
|
||||
|
||||
console.log("Generated all images");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
generateTrayIcons();
|
||||
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,15 @@
|
||||
!macro customInit
|
||||
; Make sure all old extensions are removed
|
||||
RMDir /r "$INSTDIR\resources\extensions"
|
||||
|
||||
; Workaround for installer handing when the app directory is removed manually
|
||||
${ifNot} ${FileExists} "$INSTDIR"
|
||||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\{${UNINSTALL_APP_KEY}}"
|
||||
${EndIf}
|
||||
|
||||
; Workaround for the old-format uninstall registry key (some people report it causes hangups, too)
|
||||
ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_APP_KEY}" "QuietUninstallString"
|
||||
StrCmp $0 "" proceed 0
|
||||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_APP_KEY}"
|
||||
proceed:
|
||||
!macroend
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
const { notarize } = require("electron-notarize");
|
||||
|
||||
exports.default = async function notarizing(context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
|
||||
if (electronPlatformName !== "darwin") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.env.APPLEID || !process.env.APPLEIDPASS) {
|
||||
return;
|
||||
}
|
||||
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
|
||||
return await notarize({
|
||||
appBundleId: process.env.APPBUNDLEID || "io.kontena.lens-app",
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLEID,
|
||||
appleIdPassword: process.env.APPLEIDPASS,
|
||||
ascProvider:process.env.ASCPROVIDER,
|
||||
});
|
||||
};
|
||||
|
After Width: | Height: | Size: 392 B |
|
After Width: | Height: | Size: 724 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 504 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 993 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 397 B |
|
After Width: | Height: | Size: 717 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 518 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 466 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": [
|
||||
"./**/*",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/*! For license information please see node-fetch.bundle.js.LICENSE.txt */
|
||||
(()=>{var e={414:(e,t,r)=>{if(!globalThis.DOMException)try{const{MessageChannel:e}=r(267),t=(new e).port1,o=new ArrayBuffer;t.postMessage(o,[o,o])}catch(e){"DOMException"===e.constructor.name&&(globalThis.DOMException=e.constructor)}e.exports=globalThis.DOMException},660:function(e,t){var r;r=function(e){"use strict";const t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol:e=>`Symbol(${e})`;function r(){}const o="undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:void 0;function n(e){return"object"==typeof e&&null!==e||"function"==typeof e}const i=r,s=Promise,a=Promise.prototype.then,l=Promise.resolve.bind(s),u=Promise.reject.bind(s);function c(e){return new s(e)}function d(e){return l(e)}function f(e){return u(e)}function h(e,t,r){return a.call(e,t,r)}function p(e,t,r){h(h(e,t,r),void 0,i)}function b(e,t){p(e,t)}function y(e,t){p(e,void 0,t)}function m(e,t,r){return h(e,t,r)}function _(e){h(e,void 0,i)}const g=(()=>{const e=o&&o.queueMicrotask;if("function"==typeof e)return e;const t=d(void 0);return e=>h(t,e)})();function w(e,t,r){if("function"!=typeof e)throw new TypeError("Argument is not a function");return Function.prototype.apply.call(e,t,r)}function v(e,t,r){try{return d(w(e,t,r))}catch(e){return f(e)}}class S{constructor(){this._cursor=0,this._size=0,this._front={_elements:[],_next:void 0},this._back=this._front,this._cursor=0,this._size=0}get length(){return this._size}push(e){const t=this._back;let r=t;16383===t._elements.length&&(r={_elements:[],_next:void 0}),t._elements.push(e),r!==t&&(this._back=r,t._next=r),++this._size}shift(){const e=this._front;let t=e;const r=this._cursor;let o=r+1;const n=e._elements,i=n[r];return 16384===o&&(t=e._next,o=0),--this._size,this._cursor=o,e!==t&&(this._front=t),n[r]=void 0,i}forEach(e){let t=this._cursor,r=this._front,o=r._elements;for(;!(t===o.length&&void 0===r._next||t===o.length&&(r=r._next,o=r._elements,t=0,0===o.length));)e(o[t]),++t}peek(){const e=this._front,t=this._cursor;return e._elements[t]}}function T(e,t){e._ownerReadableStream=t,t._reader=e,"readable"===t._state?A(e):"closed"===t._state?function(e){A(e),k(e)}(e):C(e,t._storedError)}function R(e,t){return wr(e._ownerReadableStream,t)}function E(e){"readable"===e._ownerReadableStream._state?q(e,new TypeError("Reader was released and can no longer be used to monitor the stream's closedness")):function(e,t){C(e,new TypeError("Reader was released and can no longer be used to monitor the stream's closedness"))}(e),e._ownerReadableStream._reader=void 0,e._ownerReadableStream=void 0}function P(e){return new TypeError("Cannot "+e+" a stream using a released reader")}function A(e){e._closedPromise=c(((t,r)=>{e._closedPromise_resolve=t,e._closedPromise_reject=r}))}function C(e,t){A(e),q(e,t)}function q(e,t){void 0!==e._closedPromise_reject&&(_(e._closedPromise),e._closedPromise_reject(t),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0)}function k(e){void 0!==e._closedPromise_resolve&&(e._closedPromise_resolve(void 0),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0)}const O=t("[[AbortSteps]]"),B=t("[[ErrorSteps]]"),L=t("[[CancelSteps]]"),j=t("[[PullSteps]]"),z=Number.isFinite||function(e){return"number"==typeof e&&isFinite(e)},W=Math.trunc||function(e){return e<0?Math.ceil(e):Math.floor(e)};function $(e,t){if(void 0!==e&&"object"!=typeof(r=e)&&"function"!=typeof r)throw new TypeError(`${t} is not an object.`);var r}function F(e,t){if("function"!=typeof e)throw new TypeError(`${t} is not a function.`)}function D(e,t){if(!function(e){return"object"==typeof e&&null!==e||"function"==typeof e}(e))throw new TypeError(`${t} is not an object.`)}function M(e,t,r){if(void 0===e)throw new TypeError(`Parameter ${t} is required in '${r}'.`)}function x(e,t,r){if(void 0===e)throw new TypeError(`${t} is required in '${r}'.`)}function I(e){return Number(e)}function U(e){return 0===e?0:e}function H(e,t){const r=Number.MAX_SAFE_INTEGER;let o=Number(e);if(o=U(o),!z(o))throw new TypeError(`${t} is not a finite number`);if(o=function(e){return U(W(e))}(o),o<0||o>r)throw new TypeError(`${t} is outside the accepted range of 0 to ${r}, inclusive`);return z(o)&&0!==o?o:0}function N(e,t){if(!_r(e))throw new TypeError(`${t} is not a ReadableStream.`)}function V(e){return new J(e)}function Y(e,t){e._reader._readRequests.push(t)}function Q(e,t,r){const o=e._reader._readRequests.shift();r?o._closeSteps():o._chunkSteps(t)}function Z(e){return e._reader._readRequests.length}function G(e){const t=e._reader;return void 0!==t&&!!K(t)}class J{constructor(e){if(M(e,1,"ReadableStreamDefaultReader"),N(e,"First parameter"),gr(e))throw new TypeError("This stream has already been locked for exclusive reading by another reader");T(this,e),this._readRequests=new S}get closed(){return K(this)?this._closedPromise:f(ee("closed"))}cancel(e){return K(this)?void 0===this._ownerReadableStream?f(P("cancel")):R(this,e):f(ee("cancel"))}read(){if(!K(this))return f(eLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/*
|
||||
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
|
||||
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
|
||||
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
||||
cluster and vice versa.
|
||||
*/
|
||||
import type { ElectronApplication, Page } from "playwright";
|
||||
import * as utils from "../helpers/utils";
|
||||
|
||||
describe("preferences page tests", () => {
|
||||
let window: Page;
|
||||
let cleanup: undefined | (() => Promise<void>);
|
||||
|
||||
beforeEach(async () => {
|
||||
let app: ElectronApplication;
|
||||
|
||||
({ window, cleanup, app } = await utils.start());
|
||||
await utils.clickWelcomeButton(window);
|
||||
|
||||
await app.evaluate(async ({ app }) => {
|
||||
await app.applicationMenu
|
||||
?.getMenuItemById(process.platform === "darwin" ? "mac" : "file")
|
||||
?.submenu
|
||||
?.getMenuItemById("navigate-to-preferences")
|
||||
?.click();
|
||||
});
|
||||
}, 10*60*1000);
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanup?.();
|
||||
}, 10*60*1000);
|
||||
|
||||
it('shows "preferences" and can navigate through the tabs', async () => {
|
||||
const pages = [
|
||||
{
|
||||
id: "app",
|
||||
header: "Application",
|
||||
},
|
||||
{
|
||||
id: "proxy",
|
||||
header: "Proxy",
|
||||
},
|
||||
{
|
||||
id: "kubernetes",
|
||||
header: "Kubernetes",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { id, header } of pages) {
|
||||
await window.click(`[data-preference-tab-link-test=${id}]`);
|
||||
await window.waitForSelector(`[data-preference-page-title-test] >> text=${header}`);
|
||||
}
|
||||
}, 10*60*1000);
|
||||
|
||||
// Skipping, but will turn it on again in the follow up PR
|
||||
it.skip("ensures helm repos", async () => {
|
||||
await window.click("[data-testid=kubernetes-tab]");
|
||||
await window.waitForSelector("[data-testid=repository-name]");
|
||||
await window.click("#HelmRepoSelect");
|
||||
await window.waitForSelector("div.Select__option");
|
||||
}, 10*60*1000);
|
||||
});
|
||||
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/*
|
||||
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
|
||||
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
|
||||
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
||||
cluster and vice versa.
|
||||
*/
|
||||
import * as utils from "../helpers/utils";
|
||||
import { minikubeReady } from "../helpers/minikube";
|
||||
import type { Frame, Page } from "playwright";
|
||||
import { groupBy, toPairs } from "lodash/fp";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { describeIf } from "../../../core/src/test-utils/skippers";
|
||||
|
||||
const TEST_NAMESPACE = "integration-tests";
|
||||
|
||||
describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
||||
let window: Page;
|
||||
let cleanup: undefined | (() => Promise<void>);
|
||||
let frame: Frame;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ window, cleanup } = await utils.start());
|
||||
await utils.clickWelcomeButton(window);
|
||||
|
||||
frame = await utils.lauchMinikubeClusterFromCatalog(window);
|
||||
}, 10 * 60 * 1000);
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanup?.();
|
||||
}, 10 * 60 * 1000);
|
||||
|
||||
it("shows cluster context menu in sidebar", async () => {
|
||||
await frame.click(`[data-testid="sidebar-cluster-dropdown"]`);
|
||||
await frame.waitForSelector(`.Menu >> text="Add to Hotbar"`);
|
||||
await frame.waitForSelector(`.Menu >> text="Settings"`);
|
||||
await frame.waitForSelector(`.Menu >> text="Disconnect"`);
|
||||
await frame.waitForSelector(`.Menu >> text="Remove"`);
|
||||
});
|
||||
|
||||
// FIXME: failed locally since metrics might already exist, cc @aleksfront
|
||||
it.skip("opens cluster settings by clicking link in no-metrics area", async () => {
|
||||
await frame.locator("text=Open cluster settings >> nth=0").click();
|
||||
await window.waitForSelector(`[data-testid="metrics-header"]`);
|
||||
});
|
||||
|
||||
it(
|
||||
"should navigate around common cluster pages",
|
||||
async () => {
|
||||
const scenariosByParent = pipeline(
|
||||
scenarios,
|
||||
groupBy("parentSidebarItemTestId"),
|
||||
toPairs,
|
||||
);
|
||||
|
||||
for (const [parentSidebarItemTestId, scenarios] of scenariosByParent) {
|
||||
if (parentSidebarItemTestId !== "null") {
|
||||
await frame.click(`[data-testid="${parentSidebarItemTestId}"]`);
|
||||
}
|
||||
|
||||
for (const scenario of scenarios) {
|
||||
await frame.click(`[data-testid="${scenario.sidebarItemTestId}"]`);
|
||||
|
||||
await frame.waitForSelector(
|
||||
scenario.expectedSelector,
|
||||
selectorTimeout,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
10 * 60 * 1000,
|
||||
);
|
||||
|
||||
it(
|
||||
"should show the default namespaces",
|
||||
async () => {
|
||||
await navigateToNamespaces(frame);
|
||||
await frame.waitForSelector("div.TableCell >> text='default'");
|
||||
await frame.waitForSelector("div.TableCell >> text='kube-system'");
|
||||
},
|
||||
10 * 60 * 1000,
|
||||
);
|
||||
|
||||
it(
|
||||
`should create the ${TEST_NAMESPACE} and a pod in the namespace and then remove that pod via the context menu`,
|
||||
async () => {
|
||||
await navigateToNamespaces(frame);
|
||||
await frame.click("button.add-button");
|
||||
await frame.waitForSelector(
|
||||
"div.AddNamespaceDialog >> text='Create Namespace'",
|
||||
);
|
||||
|
||||
const namespaceNameInput = await frame.waitForSelector(
|
||||
".AddNamespaceDialog input",
|
||||
);
|
||||
|
||||
await namespaceNameInput.type(TEST_NAMESPACE);
|
||||
await namespaceNameInput.press("Enter");
|
||||
|
||||
await frame.waitForSelector(`div.TableCell >> text=${TEST_NAMESPACE}`);
|
||||
|
||||
await navigateToPods(frame);
|
||||
|
||||
const namespacesSelector = await frame.waitForSelector(
|
||||
".NamespaceSelect",
|
||||
);
|
||||
|
||||
await namespacesSelector.click();
|
||||
await namespacesSelector.type(TEST_NAMESPACE);
|
||||
await namespacesSelector.press("Enter");
|
||||
await namespacesSelector.click();
|
||||
|
||||
await frame.click(".Icon.new-dock-tab");
|
||||
|
||||
try {
|
||||
await frame.click("li.MenuItem.create-resource-tab", {
|
||||
// NOTE: the following shouldn't be required, but is because without it a TypeError is thrown
|
||||
// see: https://github.com/microsoft/playwright/issues/8229
|
||||
position: {
|
||||
y: 0,
|
||||
x: 0,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await frame.waitForTimeout(100_000);
|
||||
}
|
||||
|
||||
const testPodName = "nginx-create-pod-test";
|
||||
const monacoEditor = await frame.waitForSelector(`.Dock.isOpen [data-test-id="monaco-editor"]`);
|
||||
|
||||
await monacoEditor.click();
|
||||
await monacoEditor.type("apiVersion: v1", { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
await monacoEditor.type("kind: Pod", { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
await monacoEditor.type("metadata:", { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
await monacoEditor.type(` name: ${testPodName}`, { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
await monacoEditor.type(`namespace: ${TEST_NAMESPACE}`, { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
await monacoEditor.press("Backspace", { delay: 10 });
|
||||
await monacoEditor.type("spec:", { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
await monacoEditor.type(" containers:", { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
await monacoEditor.type(`- name: ${testPodName}`, { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
await monacoEditor.type(" image: nginx:alpine", { delay: 10 });
|
||||
await monacoEditor.press("Enter", { delay: 10 });
|
||||
|
||||
await frame.click(".Dock .Button >> text='Create'");
|
||||
await frame.waitForSelector(`.TableCell >> text=${testPodName}`);
|
||||
await frame.click(".TableRow .TableCell.menu");
|
||||
await frame.click(".MenuItem >> text=Delete");
|
||||
await frame.click("button >> text=Remove");
|
||||
await frame.waitForSelector(`.TableCell >> text=${testPodName}`, { state: "detached" });
|
||||
},
|
||||
10 * 60 * 1000,
|
||||
);
|
||||
});
|
||||
|
||||
const selectorTimeout = { timeout: 30000 };
|
||||
|
||||
const scenarios = [
|
||||
{
|
||||
expectedSelector: "div[data-testid='cluster-overview-page'] div.label",
|
||||
parentSidebarItemTestId: null,
|
||||
sidebarItemTestId: "sidebar-item-link-for-cluster-overview",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: null,
|
||||
sidebarItemTestId: "sidebar-item-link-for-nodes",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: 'h5 >> text="Overview"',
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||
sidebarItemTestId: "sidebar-item-link-for-overview",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||
sidebarItemTestId: "sidebar-item-link-for-pods",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||
sidebarItemTestId: "sidebar-item-link-for-deployments",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||
sidebarItemTestId: "sidebar-item-link-for-daemon-sets",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||
sidebarItemTestId: "sidebar-item-link-for-stateful-sets",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||
sidebarItemTestId: "sidebar-item-link-for-replica-sets",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||
sidebarItemTestId: "sidebar-item-link-for-jobs",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||
sidebarItemTestId: "sidebar-item-link-for-cron-jobs",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||
sidebarItemTestId: "sidebar-item-link-for-config-maps",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||
sidebarItemTestId: "sidebar-item-link-for-secrets",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||
sidebarItemTestId: "sidebar-item-link-for-resource-quotas",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||
sidebarItemTestId: "sidebar-item-link-for-limit-ranges",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||
sidebarItemTestId: "sidebar-item-link-for-horizontal-pod-auto-scalers",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||
sidebarItemTestId: "sidebar-item-link-for-pod-disruption-budgets",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-network",
|
||||
sidebarItemTestId: "sidebar-item-link-for-services",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-network",
|
||||
sidebarItemTestId: "sidebar-item-link-for-endpoints",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-network",
|
||||
sidebarItemTestId: "sidebar-item-link-for-ingresses",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-network",
|
||||
sidebarItemTestId: "sidebar-item-link-for-network-policies",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-storage",
|
||||
sidebarItemTestId: "sidebar-item-link-for-persistent-volume-claims",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-storage",
|
||||
sidebarItemTestId: "sidebar-item-link-for-persistent-volumes",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-storage",
|
||||
sidebarItemTestId: "sidebar-item-link-for-storage-classes",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: null,
|
||||
sidebarItemTestId: "sidebar-item-link-for-namespaces",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: null,
|
||||
sidebarItemTestId: "sidebar-item-link-for-events",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "div.HelmCharts input",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-helm",
|
||||
sidebarItemTestId: "sidebar-item-link-for-charts",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-helm",
|
||||
sidebarItemTestId: "sidebar-item-link-for-releases",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||
sidebarItemTestId: "sidebar-item-link-for-service-accounts",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||
sidebarItemTestId: "sidebar-item-link-for-cluster-roles",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||
sidebarItemTestId: "sidebar-item-link-for-roles",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||
sidebarItemTestId: "sidebar-item-link-for-cluster-role-bindings",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||
sidebarItemTestId: "sidebar-item-link-for-role-bindings",
|
||||
},
|
||||
|
||||
{
|
||||
expectedSelector: "h5.title",
|
||||
parentSidebarItemTestId: null,
|
||||
sidebarItemTestId: "sidebar-item-link-for-custom-resources",
|
||||
},
|
||||
];
|
||||
|
||||
const navigateToPods = async (frame: Frame) => {
|
||||
await frame.click(`[data-testid="sidebar-item-link-for-workloads"]`);
|
||||
await frame.click(`[data-testid="sidebar-item-link-for-pods"]`);
|
||||
};
|
||||
|
||||
const navigateToNamespaces = async (frame: Frame) => {
|
||||
await frame.click(`[data-testid="sidebar-item-link-for-namespaces"]`);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ElectronApplication, Page } from "playwright";
|
||||
import * as utils from "../helpers/utils";
|
||||
|
||||
describe("Lens command palette", () => {
|
||||
let window: Page;
|
||||
let cleanup: undefined | (() => Promise<void>);
|
||||
let app: ElectronApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
({ window, cleanup, app } = await utils.start());
|
||||
await utils.clickWelcomeButton(window);
|
||||
}, 10*60*1000);
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanup?.();
|
||||
}, 10*60*1000);
|
||||
|
||||
describe("menu", () => {
|
||||
it("opens command dialog from menu", async () => {
|
||||
await app.evaluate(async ({ app }) => {
|
||||
await app.applicationMenu
|
||||
?.getMenuItemById("view")
|
||||
?.submenu?.getMenuItemById("open-command-palette")
|
||||
?.click();
|
||||
});
|
||||
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
|
||||
}, 10*60*1000);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { spawnSync } from "child_process";
|
||||
|
||||
export function minikubeReady(testNamespace: string): boolean {
|
||||
// determine if minikube is running
|
||||
{
|
||||
const { status } = spawnSync("minikube status", { shell: true });
|
||||
|
||||
if (status !== 0) {
|
||||
console.warn("minikube not running");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove TEST_NAMESPACE if it already exists
|
||||
{
|
||||
const { status } = spawnSync(`minikube kubectl -- get namespace ${testNamespace}`, { shell: true });
|
||||
|
||||
if (status === 0) {
|
||||
console.warn(`Removing existing ${testNamespace} namespace`);
|
||||
|
||||
const { status, stdout, stderr } = spawnSync(
|
||||
`minikube kubectl -- delete namespace ${testNamespace}`,
|
||||
{ shell: true },
|
||||
);
|
||||
|
||||
if (status !== 0) {
|
||||
console.warn(`Error removing ${testNamespace} namespace: ${stderr.toString()}`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(stdout.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { createHash } from "crypto";
|
||||
import { mkdirp, remove } from "fs-extra";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import * as uuid from "uuid";
|
||||
import type { ElectronApplication, Frame, Page } from "playwright";
|
||||
import { _electron as electron } from "playwright";
|
||||
import { noop } from "lodash";
|
||||
import { disposer } from "../../../core/src/common/utils";
|
||||
|
||||
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
|
||||
"win32": "./dist/win-unpacked/OpenLens.exe",
|
||||
"linux": "./dist/linux-unpacked/open-lens",
|
||||
"darwin": `./dist/mac${ process.arch === "arm64" ? "-arm64" : "" }/OpenLens.app/Contents/MacOS/OpenLens`,
|
||||
};
|
||||
|
||||
async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise<Page> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cleanup = disposer();
|
||||
let stdoutBuf = "";
|
||||
|
||||
const onWindow = (page: Page) => {
|
||||
console.log(`Page opened: ${page.url()}`);
|
||||
|
||||
if (page.url().startsWith("https://lens.app")) {
|
||||
cleanup();
|
||||
console.log(stdoutBuf);
|
||||
resolve(page);
|
||||
}
|
||||
};
|
||||
|
||||
app.on("window", onWindow);
|
||||
cleanup.push(() => app.off("window", onWindow));
|
||||
|
||||
const onClose = () => {
|
||||
cleanup();
|
||||
reject(new Error("App has unnexpectedly closed"));
|
||||
};
|
||||
|
||||
app.on("close", onClose);
|
||||
cleanup.push(() => app.off("close", onClose));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const stdout = app.process().stdout!;
|
||||
const onData = (chunk: any) => stdoutBuf += chunk.toString();
|
||||
|
||||
stdout.on("data", onData);
|
||||
cleanup.push(() => stdout.off("data", onData));
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
cleanup();
|
||||
console.log(stdoutBuf);
|
||||
reject(new Error(`Lens did not open the main window within ${timeout}ms`));
|
||||
}, timeout);
|
||||
|
||||
cleanup.push(() => clearTimeout(timeoutId));
|
||||
});
|
||||
}
|
||||
|
||||
async function attemptStart() {
|
||||
const CICD = path.join(os.tmpdir(), "lens-integration-testing", uuid.v4());
|
||||
|
||||
// Make sure that the directory is clear
|
||||
await remove(CICD).catch(noop);
|
||||
await mkdirp(CICD);
|
||||
|
||||
const app = await electron.launch({
|
||||
args: ["--integration-testing"], // this argument turns off the blocking of quit
|
||||
executablePath: appPaths[process.platform],
|
||||
bypassCSP: true,
|
||||
env: {
|
||||
CICD,
|
||||
...process.env,
|
||||
},
|
||||
timeout: 100_000,
|
||||
});
|
||||
|
||||
try {
|
||||
const window = await getMainWindow(app);
|
||||
|
||||
return {
|
||||
app,
|
||||
window,
|
||||
cleanup: async () => {
|
||||
await app.close();
|
||||
await remove(CICD).catch(noop);
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
await app.close();
|
||||
await remove(CICD).catch(noop);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function start() {
|
||||
// this is an attempted workaround for an issue with playwright not always getting the main window when using Electron 14.2.4 (observed on windows)
|
||||
for (let i = 0; ; i++) {
|
||||
try {
|
||||
return await attemptStart();
|
||||
} catch (error) {
|
||||
if (i === 4) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function clickWelcomeButton(window: Page) {
|
||||
await window.click("[data-testid=welcome-menu-container] li a");
|
||||
}
|
||||
|
||||
function minikubeEntityId() {
|
||||
return createHash("md5").update(`${path.join(os.homedir(), ".kube", "config")}:minikube`).digest("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
* From the catalog, click the minikube entity and wait for it to connect, returning its frame
|
||||
*/
|
||||
export async function lauchMinikubeClusterFromCatalog(window: Page): Promise<Frame> {
|
||||
await window.click("div.TableCell >> text='minikube'");
|
||||
|
||||
const minikubeFrame = await window.waitForSelector(`#cluster-frame-${minikubeEntityId()}`);
|
||||
|
||||
const frame = await minikubeFrame.contentFrame();
|
||||
|
||||
if (!frame) {
|
||||
throw new Error("No iframe for minikube found");
|
||||
}
|
||||
|
||||
await frame.waitForSelector("[data-testid=cluster-sidebar]");
|
||||
|
||||
return frame;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": [
|
||||
"./**/*",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"name": "open-lens",
|
||||
"private": true,
|
||||
"productName": "OpenLens",
|
||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"version": "6.4.0-alpha.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lensapp/lens.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"bugs": {
|
||||
"url": "https://github.com/lensapp/lens/issues"
|
||||
},
|
||||
"main": "static/build/main.js",
|
||||
"copyright": "© 2023 OpenLens Authors",
|
||||
"license": "MIT",
|
||||
"author": "OpenLens Authors <info@k8slens.dev>",
|
||||
"scripts": {
|
||||
"clean": "rm -rf binaries/ dist/ static/build",
|
||||
"build": "npm run compile",
|
||||
"postbuild": "npm run build:tray-icons && npm run download:binaries",
|
||||
"build:app": "electron-builder --publish onTag",
|
||||
"prebuild:app": "run-script-os",
|
||||
"prebuild:app:default": "exit 0",
|
||||
"prebuild:app:win32": "rm -rf node_modules/win-ca/pem",
|
||||
"build:dir": "npm run compile && electron-builder --dir",
|
||||
"compile": "env NODE_ENV=production webpack --config webpack/webpack.ts --progress",
|
||||
"postcompile": "npm run build:tray-icons && npm run download:binaries",
|
||||
"predev": "rm -rf static/build/ && npm run build:tray-icons && npm run download:binaries",
|
||||
"dev": "concurrently -i -k \"yarn run dev-run -C\" yarn:dev:*",
|
||||
"dev-run": "nodemon --watch ./static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"",
|
||||
"dev:main": "env NODE_ENV=development webpack --config webpack/main.ts --progress --watch",
|
||||
"dev:renderer": "env NODE_ENV=development ts-node ./webpack/dev-server.ts",
|
||||
"test:integration": "jest -xyz --runInBand --detectOpenHandles --forceExit --modulePaths=[\"<rootDir>/integration/\"];",
|
||||
"build:tray-icons": "ts-node build/generate-tray-icons.ts",
|
||||
"download:binaries": "ts-node build/download_binaries.ts"
|
||||
},
|
||||
"config": {
|
||||
"k8sProxyVersion": "0.3.0",
|
||||
"bundledKubectlVersion": "1.23.3",
|
||||
"bundledHelmVersion": "3.7.2",
|
||||
"sentryDsn": "",
|
||||
"contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src https://*.lens.app:*/; img-src * data:",
|
||||
"welcomeRoute": "/welcome"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 <17"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverage": false,
|
||||
"verbose": true,
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": [
|
||||
"@swc/jest"
|
||||
]
|
||||
}
|
||||
},
|
||||
"nx": {
|
||||
"targets": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
"{workspaceRoot}/binaries/",
|
||||
"{workspaceRoot}/build/webpack/",
|
||||
"{workspaceRoot}/static/build/"
|
||||
]
|
||||
},
|
||||
"dev": {
|
||||
"dependsOn": [
|
||||
"^prepare:dev"
|
||||
],
|
||||
"outputs": [
|
||||
"{workspaceRoot}/binaries/",
|
||||
"{workspaceRoot}/static/build/"
|
||||
]
|
||||
},
|
||||
"build:app": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"outputs": [
|
||||
"{workspaceRoot}/dist/"
|
||||
]
|
||||
},
|
||||
"test:integration": {
|
||||
"dependsOn": [
|
||||
"build:app"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"generateUpdatesFilesForAllChannels": true,
|
||||
"files": [
|
||||
"static/**/*",
|
||||
"!node_modules/@k8slens/open-lens/node_modules/**/*",
|
||||
"!node_modules/@k8slens/open-lens/src"
|
||||
],
|
||||
"afterSign": "build/notarize.js",
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "templates/",
|
||||
"to": "./templates/",
|
||||
"filter": "**/*.yaml"
|
||||
},
|
||||
"LICENSE"
|
||||
],
|
||||
"linux": {
|
||||
"category": "Network",
|
||||
"artifactName": "${productName}-${version}.${arch}.${ext}",
|
||||
"target": [
|
||||
"deb",
|
||||
"rpm",
|
||||
"AppImage"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "binaries/client/linux/${arch}/kubectl",
|
||||
"to": "./${arch}/kubectl"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/linux/${arch}/lens-k8s-proxy",
|
||||
"to": "./${arch}/lens-k8s-proxy"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/linux/${arch}/helm",
|
||||
"to": "./${arch}/helm"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rpm": {
|
||||
"fpm": [
|
||||
"--rpm-rpmbuild-define=%define _build_id_links none"
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"hardenedRuntime": true,
|
||||
"gatekeeperAssess": false,
|
||||
"entitlements": "build/entitlements.mac.plist",
|
||||
"entitlementsInherit": "build/entitlements.mac.plist",
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "binaries/client/darwin/${arch}/kubectl",
|
||||
"to": "./${arch}/kubectl"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/darwin/${arch}/lens-k8s-proxy",
|
||||
"to": "./${arch}/lens-k8s-proxy"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/darwin/${arch}/helm",
|
||||
"to": "./${arch}/helm"
|
||||
}
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "binaries/client/windows/${arch}/kubectl.exe",
|
||||
"to": "./${arch}/kubectl.exe"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/windows/${arch}/lens-k8s-proxy.exe",
|
||||
"to": "./${arch}/lens-k8s-proxy.exe"
|
||||
},
|
||||
{
|
||||
"from": "binaries/client/windows/${arch}/helm.exe",
|
||||
"to": "./${arch}/helm.exe"
|
||||
}
|
||||
]
|
||||
},
|
||||
"nsis": {
|
||||
"include": "build/installer.nsh",
|
||||
"oneClick": false,
|
||||
"allowElevation": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"protocols": {
|
||||
"name": "Lens Protocol Handler",
|
||||
"schemes": [
|
||||
"lens"
|
||||
],
|
||||
"role": "Viewer"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@k8slens/open-lens": "^6.4.0-alpha.2",
|
||||
"@ogre-tools/fp": "^12.0.1",
|
||||
"@ogre-tools/injectable": "^12.0.1",
|
||||
"@ogre-tools/injectable-extension-for-auto-registration": "^12.0.1",
|
||||
"@ogre-tools/injectable-extension-for-mobx": "^12.0.1",
|
||||
"@ogre-tools/injectable-react": "^12.0.1",
|
||||
"mobx": "^6.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@swc/core": "^1.3.26",
|
||||
"@swc/jest": "^0.2.24",
|
||||
"@types/byline": "^4.2.33",
|
||||
"@types/chart.js": "^2.9.36",
|
||||
"@types/cli-progress": "^3.11.0",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/crypto-js": "^3.1.47",
|
||||
"@types/gunzip-maybe": "^1.4.0",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^16.18.6",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
"@types/react-dom": "^17.0.16",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/request-promise-native": "^1.0.18",
|
||||
"@types/tar": "^6.1.3",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/tcp-port-used": "^1.0.1",
|
||||
"@types/url-parse": "^1.4.8",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-dev-server": "^4.7.2",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
"@types/webpack-node-externals": "2.5.3",
|
||||
"abort-controller": "^3.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
"cli-progress": "^3.11.2",
|
||||
"concurrently": "^7.6.0",
|
||||
"css-loader": "^6.7.2",
|
||||
"electron": "^19.1.9",
|
||||
"electron-builder": "^23.6.0",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"esbuild-loader": "^2.20.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.14",
|
||||
"gunzip-maybe": "^1.4.2",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"jest": "^28.1.3",
|
||||
"jest-environment-jsdom": "^28.1.3",
|
||||
"jsdom": "^20.0.3",
|
||||
"jsonfile": "^6.1.0",
|
||||
"mini-css-extract-plugin": "^2.7.1",
|
||||
"monaco-editor": "^0.29.1",
|
||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"playwright": "^1.29.2",
|
||||
"react-refresh": "^0.14.0",
|
||||
"react-refresh-typescript": "^2.0.7",
|
||||
"react-select": "^5.7.0",
|
||||
"run-script-os": "^1.1.6",
|
||||
"sharp": "^0.31.2",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"type-fest": "^2.14.0",
|
||||
"typed-emitter": "^1.4.0",
|
||||
"typescript": "^4.9.4",
|
||||
"typescript-plugin-css-modules": "^4.1.1",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.11.1",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"xterm-addon-fit": "^0.5.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { applicationInformationToken, ApplicationInformation } from "@k8slens/open-lens/common";
|
||||
import packageJson from "../../package.json";
|
||||
|
||||
const applicationInformationInjectable = getInjectable({
|
||||
id: "application-information",
|
||||
injectionToken: applicationInformationToken,
|
||||
instantiate: () => {
|
||||
const { version, config, productName, build, copyright, description, name } = packageJson;
|
||||
|
||||
return { version, config, productName, build, copyright, description, name } as ApplicationInformation;
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default applicationInformationInjectable;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
|
||||
import { runInAction } from "mobx";
|
||||
import { createApp, extensionApi } from "@k8slens/open-lens/main";
|
||||
|
||||
const di = createContainer("main");
|
||||
const app = createApp({
|
||||
di,
|
||||
mode: process.env.NODE_ENV || "development"
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
try {
|
||||
autoRegister({
|
||||
di,
|
||||
requireContexts: [
|
||||
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
|
||||
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
|
||||
],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
app.start().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
})
|
||||
|
||||
const { Mobx, LensExtensions, Pty } = extensionApi;
|
||||
|
||||
export {
|
||||
Mobx,
|
||||
LensExtensions,
|
||||
Pty
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import "@k8slens/open-lens/styles";
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import { runInAction } from "mobx";
|
||||
import { createApp, extensionApi } from "@k8slens/open-lens/renderer";
|
||||
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
|
||||
|
||||
const di = createContainer("renderer");
|
||||
const app = createApp({
|
||||
di,
|
||||
mode: process.env.NODE_ENV || "development"
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
autoRegister({
|
||||
di,
|
||||
requireContexts: [
|
||||
require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
|
||||
require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
app.start();
|
||||
|
||||
const {
|
||||
React,
|
||||
ReactDOM,
|
||||
ReactRouter,
|
||||
ReactRouterDom,
|
||||
Mobx,
|
||||
MobxReact,
|
||||
LensExtensions
|
||||
} = extensionApi;
|
||||
|
||||
export {
|
||||
React,
|
||||
ReactDOM,
|
||||
ReactRouter,
|
||||
ReactRouterDom,
|
||||
Mobx,
|
||||
MobxReact,
|
||||
LensExtensions
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html, body{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
background: #1e2124;
|
||||
color: #6c757d;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.aligner-center-center{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.aligner-content{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 80px;
|
||||
}
|
||||
.icon {
|
||||
position: relative;
|
||||
font-size: 36px;
|
||||
line-height: 54px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.icon svg{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
top: 14px;
|
||||
}
|
||||
.icon .icon-text{
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
display: inline;
|
||||
}
|
||||
svg{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.loading{
|
||||
padding-top: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.bar{
|
||||
position: relative;;
|
||||
height: 2px;
|
||||
background: #262b2f;
|
||||
}
|
||||
.progress{
|
||||
position: relative;
|
||||
height: 2px;
|
||||
background: #3d90ce;
|
||||
box-shadow: 0 0 20px #3d90ce;
|
||||
animation-name: loader-animation;
|
||||
animation-duration: 1.6s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes loader-animation{
|
||||
0% {
|
||||
left: 0;
|
||||
width: 0px;
|
||||
}
|
||||
50% {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
width: 0px;
|
||||
}
|
||||
}
|
||||
.glow-wrapper{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.glow{
|
||||
height: 100%;
|
||||
display: flex;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.glow div{
|
||||
flex: 1;
|
||||
background: #3d90ce;
|
||||
animation: glow 0.8s infinite alternate ease;
|
||||
box-shadow: 0 0 20px #3d90ce;
|
||||
}
|
||||
|
||||
.glow div.b1{ animation-delay: -0.72s; }
|
||||
.glow div.b2{ animation-delay: -0.64s; }
|
||||
.glow div.b3{ animation-delay: -0.56s; }
|
||||
.glow div.b4{ animation-delay: -0.48s; }
|
||||
.glow div.b5{ animation-delay: -0.40s; }
|
||||
.glow div.b6{ animation-delay: -0.32s; }
|
||||
.glow div.b7{ animation-delay: -0.24s; }
|
||||
.glow div.b8{ animation-delay: -0.16s; }
|
||||
.glow div.b9{ animation-delay: -0.08s; }
|
||||
|
||||
|
||||
@keyframes glow{
|
||||
100% {
|
||||
background: transparent;
|
||||
flex: 10;
|
||||
box-shadow: 0 0 0 transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.text{
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="aligner-center-center">
|
||||
<div class="aligner-content">
|
||||
<div class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#3f90ce"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M14.25 2.26l-.08-.04-.01.02C13.46 2.09 12.74 2 12 2 6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10c0-4.75-3.31-8.72-7.75-9.74zM19.41 9h-7.99l2.71-4.7c2.4.66 4.35 2.42 5.28 4.7zM13.1 4.08L10.27 9l-1.15 2L6.4 6.3C7.84 4.88 9.82 4 12 4c.37 0 .74.03 1.1.08zM5.7 7.09L8.54 12l1.15 2H4.26C4.1 13.36 4 12.69 4 12c0-1.85.64-3.55 1.7-4.91zM4.59 15h7.98l-2.71 4.7c-2.4-.67-4.34-2.42-5.27-4.7zm6.31 4.91L14.89 13l2.72 4.7C16.16 19.12 14.18 20 12 20c-.38 0-.74-.04-1.1-.09zm7.4-3l-4-6.91h5.43c.17.64.27 1.31.27 2 0 1.85-.64 3.55-1.7 4.91z"/></svg>
|
||||
<div class="icon-text">OpenLens</div>
|
||||
</div>
|
||||
<div class="loading">
|
||||
<div class="bar">
|
||||
<div class="progress"></div>
|
||||
</div>
|
||||
<div class="glow-wrapper">
|
||||
<div class="glow">
|
||||
<div class="b1"></div>
|
||||
<div class="b2"></div>
|
||||
<div class="b3"></div>
|
||||
<div class="b4"></div>
|
||||
<div class="b5"></div>
|
||||
<div class="b6"></div>
|
||||
<div class="b7"></div>
|
||||
<div class="b8"></div>
|
||||
<div class="b9"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,206 @@
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: read-all-clusterrole
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
- /metrics
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- bindings
|
||||
- componentstatuses
|
||||
- configmaps
|
||||
- endpoints
|
||||
- events
|
||||
- limitranges
|
||||
- namespaces
|
||||
- namespaces/finalize
|
||||
- namespaces/status
|
||||
- nodes
|
||||
- nodes/proxy
|
||||
- nodes/status
|
||||
- persistentvolumeclaims
|
||||
- persistentvolumeclaims/status
|
||||
- persistentvolumes
|
||||
- persistentvolumes/status
|
||||
- pods
|
||||
- pods/attach
|
||||
- pods/binding
|
||||
- pods/eviction
|
||||
- pods/exec
|
||||
- pods/log
|
||||
- pods/proxy
|
||||
- pods/status
|
||||
- podtemplates
|
||||
- replicationcontrollers
|
||||
- replicationcontrollers/scale
|
||||
- replicationcontrollers/status
|
||||
- resourcequotas
|
||||
- resourcequotas/status
|
||||
- serviceaccounts
|
||||
- services
|
||||
- services/proxy
|
||||
- services/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- controllerrevisions
|
||||
- daemonsets
|
||||
- daemonsets/status
|
||||
- deployments
|
||||
- deployments/scale
|
||||
- deployments/status
|
||||
- replicasets
|
||||
- replicasets/scale
|
||||
- replicasets/status
|
||||
- statefulsets
|
||||
- statefulsets/scale
|
||||
- statefulsets/status
|
||||
verbs:
|
||||
- list
|
||||
- get
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs
|
||||
- jobs/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- autoscaling
|
||||
resources:
|
||||
- horizontalpodautoscalers
|
||||
- horizontalpodautoscalers/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- storage.k8s.io
|
||||
resources:
|
||||
- csidrivers
|
||||
- csinodes
|
||||
- storageclasses
|
||||
- volumeattachments
|
||||
- volumeattachments/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- networkpolicies
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- scheduling.k8s.io
|
||||
resources:
|
||||
- priorityclasses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- node.k8s.io
|
||||
resources:
|
||||
- runtimeclasses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- events.k8s.io
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apiextensions.k8s.io
|
||||
resources:
|
||||
- customresourcedefinitions
|
||||
- customresourcedefinitions/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apiregistration.k8s.io
|
||||
resources:
|
||||
- apiservices
|
||||
- apiservices/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- discovery.k8s.io
|
||||
resources:
|
||||
- endpointslices
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- metrics.k8s.io
|
||||
resources:
|
||||
- pods
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- policy
|
||||
resources:
|
||||
- poddisruptionbudgets
|
||||
- poddisruptionbudgets/status
|
||||
- podsecuritypolicies
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- clusterrolebindings
|
||||
- clusterroles
|
||||
- rolebindings
|
||||
- roles
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- scheduling.k8s.io
|
||||
resources:
|
||||
- priorityclass
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: developer-read-all
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: developer
|
||||
namespace: default
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: read-all-clusterrole
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: game-demo
|
||||
data:
|
||||
# property-like keys; each key maps to a simple value
|
||||
player_initial_lives: "3"
|
||||
ui_properties_file_name: "user-interface.properties"
|
||||
|
||||
# file-like keys
|
||||
game.properties: |
|
||||
enemy.types=aliens,monsters
|
||||
player.maximum-lives=5
|
||||
user-interface.properties: |
|
||||
color.good=purple
|
||||
color.bad=yellow
|
||||
allow.textmode=true
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: hello
|
||||
spec:
|
||||
schedule: "*/1 * * * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: busybox
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- date; echo Hello from the Kubernetes cluster
|
||||
restartPolicy: OnFailure
|
||||
@@ -0,0 +1,44 @@
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: fluentd-elasticsearch
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: fluentd-logging
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: fluentd-elasticsearch
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: fluentd-elasticsearch
|
||||
spec:
|
||||
tolerations:
|
||||
# this toleration is to have the daemonset runnable on master nodes
|
||||
# remove it if your masters can't run pods
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: fluentd-elasticsearch
|
||||
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
|
||||
resources:
|
||||
limits:
|
||||
memory: 200Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- name: varlog
|
||||
mountPath: /var/log
|
||||
- name: varlibdockercontainers
|
||||
mountPath: /var/lib/docker/containers
|
||||
readOnly: true
|
||||
terminationGracePeriodSeconds: 30
|
||||
volumes:
|
||||
- name: varlog
|
||||
hostPath:
|
||||
path: /var/log
|
||||
- name: varlibdockercontainers
|
||||
hostPath:
|
||||
path: /var/lib/docker/containers
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.14.2
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: minimal-ingress
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /testpath
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: test
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: pi
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: pi
|
||||
image: perl
|
||||
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
|
||||
restartPolicy: Never
|
||||
backoffLimit: 4
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: test-network-policy
|
||||
namespace: default
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
role: db
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 172.17.0.0/16
|
||||
except:
|
||||
- 172.17.1.0/24
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
project: myproject
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
role: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 6379
|
||||
egress:
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.0.0.0/24
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5978
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: pv0003
|
||||
spec:
|
||||
capacity:
|
||||
storage: 5Gi
|
||||
volumeMode: Filesystem
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
persistentVolumeReclaimPolicy: Recycle
|
||||
storageClassName: slow
|
||||
mountOptions:
|
||||
- hard
|
||||
- nfsvers=4.1
|
||||
nfs:
|
||||
path: /tmp
|
||||
server: 172.17.0.2
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: myclaim
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
volumeMode: Filesystem
|
||||
resources:
|
||||
requests:
|
||||
storage: 8Gi
|
||||
storageClassName: slow
|
||||
selector:
|
||||
matchLabels:
|
||||
release: "stable"
|
||||
matchExpressions:
|
||||
- {key: environment, operator: In, values: [dev]}
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: static-web
|
||||
labels:
|
||||
role: myrole
|
||||
spec:
|
||||
containers:
|
||||
- name: web
|
||||
image: nginx
|
||||
ports:
|
||||
- name: web
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: scheduling.k8s.io/v1
|
||||
kind: PriorityClass
|
||||
metadata:
|
||||
name: overprovisioning
|
||||
description: Priority class used by overprovisioning.
|
||||
preemptionPolicy: PreemptLowerPriority
|
||||
globalDefault: false
|
||||
value: -1
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: apps/v1
|
||||
kind: ReplicaSet
|
||||
metadata:
|
||||
name: frontend
|
||||
labels:
|
||||
app: guestbook
|
||||
tier: frontend
|
||||
spec:
|
||||
# modify replicas according to your case
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
tier: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
tier: frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: php-redis
|
||||
image: gcr.io/google_samples/gb-frontend:v3
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: default
|
||||
name: pod-reader
|
||||
rules:
|
||||
- apiGroups: [""] # "" indicates the core API group
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
# This role binding allows "jane" to read pods in the "default" namespace.
|
||||
# You need to already have a Role named "pod-reader" in that namespace.
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: read-pods
|
||||
namespace: default
|
||||
subjects:
|
||||
# You can specify more than one "subject"
|
||||
- kind: User
|
||||
name: jane # "name" is case sensitive
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
# "roleRef" specifies the binding to a Role / ClusterRole
|
||||
kind: Role #this must be Role or ClusterRole
|
||||
name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: node.k8s.io/v1
|
||||
kind: RuntimeClass
|
||||
metadata:
|
||||
name: spot
|
||||
handler: runc
|
||||
scheduling:
|
||||
nodeSelector:
|
||||
eks.amazonaws.com/capacityType: "SPOT"
|
||||
tolerations:
|
||||
- key: eks.amazonaws.com/capacityType
|
||||
value: SPOT
|
||||
effect: NoSchedule
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-basic-auth
|
||||
type: kubernetes.io/basic-auth
|
||||
stringData:
|
||||
username: admin
|
||||
password: t0p-Secret
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: my-service
|
||||
spec:
|
||||
selector:
|
||||
app: MyApp
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 9376
|
||||
@@ -0,0 +1,34 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: web
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx # has to match .spec.template.metadata.labels
|
||||
serviceName: "nginx"
|
||||
replicas: 3 # by default is 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx # has to match .spec.selector.matchLabels
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 10
|
||||
containers:
|
||||
- name: nginx
|
||||
image: k8s.gcr.io/nginx-slim:0.8
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: web
|
||||
volumeMounts:
|
||||
- name: www
|
||||
mountPath: /usr/share/nginx/html
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: www
|
||||
spec:
|
||||
accessModes: [ "ReadWriteOnce" ]
|
||||
storageClassName: "my-storage-class"
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*",
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "typescript-plugin-css-modules"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../core/types/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"out",
|
||||
"dist",
|
||||
"coverage",
|
||||
"binaries",
|
||||
"static"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../core"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import Webpack from "webpack";
|
||||
import WebpackDevServer from "webpack-dev-server";
|
||||
import renderer from "./renderer";
|
||||
import { buildDir, webpackDevServerPort } from "./vars";
|
||||
|
||||
/**
|
||||
* API docs:
|
||||
* @url https://webpack.js.org/configuration/dev-server/
|
||||
* @url https://github.com/chimurai/http-proxy-middleware
|
||||
*/
|
||||
const compiler = Webpack(renderer);
|
||||
|
||||
const server = new WebpackDevServer({
|
||||
setupExitSignals: true,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
allowedHosts: ".lens.app",
|
||||
host: "localhost",
|
||||
port: webpackDevServerPort,
|
||||
static: buildDir, // aka `devServer.contentBase` in webpack@4
|
||||
hot: "only", // use HMR only without errors
|
||||
liveReload: false,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
index: "index.html",
|
||||
publicPath: "/build",
|
||||
},
|
||||
proxy: {
|
||||
"^/$": "/build/",
|
||||
},
|
||||
client: {
|
||||
overlay: false, // don't show warnings and errors on top of rendered app view
|
||||
logging: "error",
|
||||
},
|
||||
}, compiler);
|
||||
|
||||
console.info(`[WEBPACK-DEV-SERVER]: created with options`, server.options);
|
||||
|
||||
server.start();
|
||||
@@ -0,0 +1,59 @@
|
||||
import path from "path";
|
||||
import type webpack from "webpack";
|
||||
import { DefinePlugin } from "webpack";
|
||||
import nodeExternals from "webpack-node-externals";
|
||||
import { iconsAndImagesWebpackRules } from "./renderer";
|
||||
import { buildDir, isDevelopment, mainDir } from "./vars";
|
||||
import { platform } from "process";
|
||||
|
||||
const main: webpack.Configuration = ({
|
||||
name: "lens-app-main",
|
||||
context: __dirname,
|
||||
target: "electron-main",
|
||||
mode: isDevelopment ? "development" : "production",
|
||||
devtool: isDevelopment ? "cheap-module-source-map" : "source-map",
|
||||
cache: isDevelopment ? { type: "filesystem" } : false,
|
||||
entry: {
|
||||
main: path.resolve(mainDir, "index.ts"),
|
||||
},
|
||||
output: {
|
||||
libraryTarget: "global",
|
||||
path: buildDir,
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".json", ".js", ".ts"],
|
||||
},
|
||||
externals: [
|
||||
nodeExternals(),
|
||||
],
|
||||
module: {
|
||||
parser: {
|
||||
javascript: {
|
||||
commonjsMagicComments: true,
|
||||
},
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: "node-loader",
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: "ts-loader",
|
||||
options: {},
|
||||
},
|
||||
...iconsAndImagesWebpackRules(),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new DefinePlugin({
|
||||
CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
CONTEXT_MATCHER_FOR_FEATURES: `/\\/(renderer|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export default main;
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import type webpack from "webpack";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import MonacoWebpackPlugin from "monaco-editor-webpack-plugin";
|
||||
import CircularDependencyPlugin from "circular-dependency-plugin";
|
||||
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
|
||||
import type { WebpackPluginInstance } from "webpack";
|
||||
import { DefinePlugin } from "webpack";
|
||||
import { assetsFolderName, isDevelopment, rendererDir, buildDir, htmlTemplate, publicPath, sassCommonVars } from "./vars";
|
||||
import { platform } from "process";
|
||||
|
||||
const renderer: webpack.Configuration = {
|
||||
target: "electron-renderer",
|
||||
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 ? { type: "filesystem" } : false,
|
||||
entry: {
|
||||
lens: path.resolve(rendererDir, "index.ts"),
|
||||
},
|
||||
output: {
|
||||
libraryTarget: "global",
|
||||
globalObject: "this",
|
||||
publicPath,
|
||||
path: buildDir,
|
||||
filename: "[name].js",
|
||||
chunkFilename: "chunks/[name].js",
|
||||
assetModuleFilename: `${assetsFolderName}/[name][ext][query]`,
|
||||
},
|
||||
watchOptions: {
|
||||
ignored: /node_modules/, // https://webpack.js.org/configuration/watch/
|
||||
},
|
||||
ignoreWarnings: [
|
||||
/Critical dependency: the request of a dependency is an expression/,
|
||||
/require.extensions is not supported by webpack./, // handlebars
|
||||
/\[ReactRefreshPlugin] .*?HMR.*? is not enabled/, // enabled in webpack.dev-server
|
||||
],
|
||||
resolve: {
|
||||
extensions: [
|
||||
".js", ".jsx", ".json",
|
||||
".ts", ".tsx",
|
||||
],
|
||||
},
|
||||
externals: [
|
||||
"node-fetch",
|
||||
"npm",
|
||||
"win-ca"
|
||||
],
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
module: {
|
||||
parser: {
|
||||
javascript: {
|
||||
commonjsMagicComments: true,
|
||||
},
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: "node-loader",
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: "ts-loader",
|
||||
options: {},
|
||||
},
|
||||
cssModulesWebpackRule(),
|
||||
...iconsAndImagesWebpackRules(),
|
||||
...fontsLoaderWebpackRules(),
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new DefinePlugin({
|
||||
CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
CONTEXT_MATCHER_FOR_FEATURES: `/\\/(renderer|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`,
|
||||
}),
|
||||
new ForkTsCheckerPlugin(),
|
||||
|
||||
// see also: https://github.com/Microsoft/monaco-editor-webpack-plugin#options
|
||||
new MonacoWebpackPlugin({
|
||||
// publicPath: "/",
|
||||
// filename: "[name].worker.js",
|
||||
languages: ["json", "yaml"],
|
||||
globalAPI: isDevelopment,
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "index.html",
|
||||
template: htmlTemplate,
|
||||
inject: true,
|
||||
hash: true,
|
||||
templateParameters: {
|
||||
assetPath: `${publicPath}${assetsFolderName}`,
|
||||
},
|
||||
}),
|
||||
|
||||
new CircularDependencyPlugin({
|
||||
cwd: __dirname,
|
||||
exclude: /node_modules/,
|
||||
failOnError: true,
|
||||
}) as unknown as WebpackPluginInstance,
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].css",
|
||||
}),
|
||||
|
||||
...(
|
||||
isDevelopment
|
||||
? [new ReactRefreshWebpackPlugin()]
|
||||
: []
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Import icons and image files.
|
||||
* Read more about asset types: https://webpack.js.org/guides/asset-modules/
|
||||
*/
|
||||
export function iconsAndImagesWebpackRules(): webpack.RuleSetRule[] {
|
||||
return [
|
||||
{
|
||||
test: /\.svg$/,
|
||||
type: "asset/source", // exports the source code of the asset, so we get XML
|
||||
},
|
||||
{
|
||||
test: /\.(jpg|png|ico)$/,
|
||||
type: "asset/resource", // path to file, e.g. "/static/build/assets/*"
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Import custom fonts as URL.
|
||||
*/
|
||||
export function fontsLoaderWebpackRules(): webpack.RuleSetRule[] {
|
||||
return [
|
||||
{
|
||||
test: /\.(ttf|eot|woff2?)$/,
|
||||
type: "asset/resource",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export interface CssModulesWebpackRuleOptions {
|
||||
styleLoader?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import CSS or SASS styles with modules support (*.module.scss)
|
||||
*/
|
||||
export function cssModulesWebpackRule({ styleLoader }: CssModulesWebpackRuleOptions = {}): webpack.RuleSetRule {
|
||||
styleLoader ??= isDevelopment
|
||||
? "style-loader"
|
||||
: MiniCssExtractPlugin.loader;
|
||||
|
||||
return {
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
styleLoader,
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
sourceMap: isDevelopment,
|
||||
modules: {
|
||||
auto: /\.module\./i, // https://github.com/webpack-contrib/css-loader#auto
|
||||
mode: "local", // :local(.selector) by default
|
||||
localIdentName: "[name]__[local]--[hash:base64:5]",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export default renderer;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import assert from "assert";
|
||||
import path from "path";
|
||||
|
||||
export const isDevelopment = process.env.NODE_ENV !== "production";
|
||||
export const mainDir = path.join(process.cwd(), "src", "main");
|
||||
export const buildDir = path.join(process.cwd(), "static", "build");
|
||||
export const assetsFolderName = "assets";
|
||||
export const rendererDir = path.join(process.cwd(), "src", "renderer");
|
||||
export const htmlTemplate = path.resolve(__dirname, "..", "node_modules", "@k8slens", "open-lens", "src/renderer", "template.html");
|
||||
export const publicPath = "/build/";
|
||||
export const sassCommonVars = path.resolve(__dirname, "..", "node_modules", "@k8slens", "open-lens", "src", "renderer", "components/vars.scss");
|
||||
export const webpackDevServerPort = Number(process.env.WEBPACK_DEV_SERVER_PORT) || 9191;
|
||||
|
||||
assert(Number.isInteger(webpackDevServerPort), "WEBPACK_DEV_SERVER_PORT environment variable must only be an integer");
|
||||
@@ -0,0 +1,4 @@
|
||||
import main from "./main";
|
||||
import renderer from "./renderer";
|
||||
|
||||
export default [main, renderer];
|
||||