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

Merge branch 'master' into ui-table-empty-state-lines

This commit is contained in:
Alex Andreev 2022-02-21 10:45:06 +03:00
commit 1d99ea36c4
214 changed files with 19492 additions and 6072 deletions

View File

@ -129,6 +129,16 @@ module.exports = {
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/member-delimiter-style": ["error", {
"multiline": {
"delimiter": "semi",
"requireLast": true,
},
"singleline": {
"delimiter": "semi",
"requireLast": false,
},
}],
"react/display-name": "off", "react/display-name": "off",
"space-before-function-paren": "off", "space-before-function-paren": "off",
"@typescript-eslint/space-before-function-paren": ["error", { "@typescript-eslint/space-before-function-paren": ["error", {

View File

@ -42,7 +42,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- uses: nick-invision/retry@v2 - uses: nick-fields/retry@v2
name: Install dependencies name: Install dependencies
with: with:
timeout_minutes: 10 timeout_minutes: 10
@ -53,7 +53,7 @@ jobs:
- run: make build-npm - run: make build-npm
name: Generate npm package name: Generate npm package
- uses: nick-invision/retry@v2 - uses: nick-fields/retry@v2
name: Build bundled extensions name: Build bundled extensions
with: with:
timeout_minutes: 15 timeout_minutes: 15

View File

@ -3,4 +3,4 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
export default ""; // mostly path to bundled file or data-url (webpack) export default {};

File diff suppressed because it is too large Load Diff

View File

@ -18,8 +18,8 @@
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"ts-loader": "latest", "ts-loader": "^8.0.4",
"typescript": "latest", "typescript": "^4.3.2",
"webpack": "latest" "webpack": "^4.46.0"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,10 @@
], ],
"devDependencies": { "devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "^26.6.3",
"semver": "^7.3.2", "semver": "^7.3.2",
"jest": "latest", "ts-loader": "^8.0.4",
"ts-loader": "latest", "typescript": "^4.3.2",
"typescript": "latest", "webpack": "^4.44.2"
"webpack": "latest"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -18,9 +18,9 @@
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "latest", "jest": "^26.6.3",
"ts-loader": "latest", "ts-loader": "^8.0.4",
"typescript": "latest", "typescript": "^4.3.2",
"webpack": "latest" "webpack": "^4.46.0"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -18,9 +18,9 @@
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@k8slens/extensions": "file:../../src/extensions/npm/extensions", "@k8slens/extensions": "file:../../src/extensions/npm/extensions",
"jest": "latest", "jest": "^26.6.3",
"ts-loader": "latest", "ts-loader": "^8.0.4",
"typescript": "latest", "typescript": "^4.3.2",
"webpack": "latest" "webpack": "^4.46.0"
} }
} }

View File

@ -60,9 +60,7 @@ describe("preferences page tests", () => {
// Skipping, but will turn it on again in the follow up PR // Skipping, but will turn it on again in the follow up PR
it.skip("ensures helm repos", async () => { it.skip("ensures helm repos", async () => {
await window.click("[data-testid=kubernetes-tab]"); await window.click("[data-testid=kubernetes-tab]");
await window.waitForSelector("[data-testid=repository-name]", { await window.waitForSelector("[data-testid=repository-name]");
timeout: 140_000,
});
await window.click("#HelmRepoSelect"); await window.click("#HelmRepoSelect");
await window.waitForSelector("div.Select__option"); await window.waitForSelector("div.Select__option");
}, 10*60*1000); }, 10*60*1000);

View File

@ -3,7 +3,7 @@
"productName": "OpenLens", "productName": "OpenLens",
"description": "OpenLens - Open Source IDE for Kubernetes", "description": "OpenLens - Open Source IDE for Kubernetes",
"homepage": "https://github.com/lensapp/lens", "homepage": "https://github.com/lensapp/lens",
"version": "5.4.0-beta.1", "version": "5.4.0-beta.2",
"main": "static/build/main.js", "main": "static/build/main.js",
"copyright": "© 2021 OpenLens Authors", "copyright": "© 2021 OpenLens Authors",
"license": "MIT", "license": "MIT",
@ -16,9 +16,9 @@
"dev-build": "concurrently yarn:compile:*", "dev-build": "concurrently yarn:compile:*",
"debug-build": "concurrently yarn:compile:main yarn:compile:extension-types", "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:main": "yarn run compile:main --watch",
"dev:renderer": "yarn run compile:renderer --watch --progress", "dev:renderer": "yarn run webpack-dev-server --config webpack.renderer.ts",
"dev:extension-types": "yarn run compile:extension-types --watch --progress", "dev:extension-types": "yarn run compile:extension-types --watch",
"compile": "env NODE_ENV=production concurrently yarn:compile:*", "compile": "env NODE_ENV=production concurrently yarn:compile:*",
"compile:main": "yarn run webpack --config webpack.main.ts", "compile:main": "yarn run webpack --config webpack.main.ts",
"compile:renderer": "yarn run webpack --config webpack.renderer.ts", "compile:renderer": "yarn run webpack --config webpack.renderer.ts",
@ -62,7 +62,7 @@
}, },
"moduleNameMapper": { "moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts", "\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts",
"\\.(svg|png|jpg|eot|woff2?|ttf)$": "<rootDir>/__mocks__/assetMock.ts" "\\.(svg)$": "<rootDir>/__mocks__/imageMock.ts"
}, },
"modulePathIgnorePatterns": [ "modulePathIgnorePatterns": [
"<rootDir>/dist", "<rootDir>/dist",
@ -268,7 +268,7 @@
"@material-ui/core": "^4.12.3", "@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60", "@material-ui/lab": "^4.0.0-alpha.60",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@sentry/types": "^6.14.1", "@sentry/types": "^6.14.1",
"@testing-library/jest-dom": "^5.16.1", "@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^11.2.7", "@testing-library/react": "^11.2.7",
@ -291,11 +291,12 @@
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",
"@types/marked": "^4.0.1", "@types/marked": "^4.0.1",
"@types/md5-file": "^4.0.2", "@types/md5-file": "^4.0.2",
"@types/mini-css-extract-plugin": "^2.4.0", "@types/mini-css-extract-plugin": "^0.9.1",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "14.17.33", "@types/node": "14.17.33",
"@types/node-fetch": "^2.5.12", "@types/node-fetch": "^2.5.12",
"@types/npm": "^2.0.32", "@types/npm": "^2.0.32",
"@types/progress-bar-webpack-plugin": "^2.1.2",
"@types/proper-lockfile": "^4.1.2", "@types/proper-lockfile": "^4.1.2",
"@types/randomcolor": "^0.5.6", "@types/randomcolor": "^0.5.6",
"@types/react": "^17.0.34", "@types/react": "^17.0.34",
@ -318,10 +319,10 @@
"@types/triple-beam": "^1.3.2", "@types/triple-beam": "^1.3.2",
"@types/url-parse": "^1.4.5", "@types/url-parse": "^1.4.5",
"@types/uuid": "^8.3.3", "@types/uuid": "^8.3.3",
"@types/webpack": "^5.28.0", "@types/webpack": "^4.41.32",
"@types/webpack-dev-server": "^4.7.2", "@types/webpack-dev-server": "^3.11.6",
"@types/webpack-env": "^1.16.3", "@types/webpack-env": "^1.16.3",
"@types/webpack-node-externals": "^2.5.3", "@types/webpack-node-externals": "^1.7.1",
"@typescript-eslint/eslint-plugin": "^5.10.1", "@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1", "@typescript-eslint/parser": "^5.10.1",
"ansi_up": "^5.1.0", "ansi_up": "^5.1.0",
@ -329,24 +330,25 @@
"circular-dependency-plugin": "^5.2.2", "circular-dependency-plugin": "^5.2.2",
"color": "^3.2.1", "color": "^3.2.1",
"concurrently": "^5.3.0", "concurrently": "^5.3.0",
"css-loader": "^6.5.1", "css-loader": "^5.2.7",
"deepdash": "^5.3.9", "deepdash": "^5.3.9",
"dompurify": "^2.3.4", "dompurify": "^2.3.4",
"electron": "^14.2.4", "electron": "^14.2.4",
"electron-builder": "^22.14.5", "electron-builder": "^22.14.5",
"electron-notarize": "^0.3.0", "electron-notarize": "^0.3.0",
"esbuild": "^0.13.15", "esbuild": "^0.13.15",
"esbuild-loader": "^2.18.0", "esbuild-loader": "^2.16.0",
"eslint": "^8.7.0", "eslint": "^8.7.0",
"eslint-plugin-header": "^3.1.1", "eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.25.4", "eslint-plugin-import": "^2.25.4",
"eslint-plugin-react": "^7.28.0", "eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
"file-loader": "^6.2.0",
"flex.box": "^3.4.4", "flex.box": "^3.4.4",
"fork-ts-checker-webpack-plugin": "^6.5.0", "fork-ts-checker-webpack-plugin": "^5.2.1",
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^4.5.2",
"ignore-loader": "^0.1.2", "ignore-loader": "^0.1.2",
"include-media": "^1.4.9", "include-media": "^1.4.9",
"jest": "26.6.3", "jest": "26.6.3",
@ -354,41 +356,43 @@
"jest-fetch-mock": "^3.0.3", "jest-fetch-mock": "^3.0.3",
"jest-mock-extended": "^1.0.18", "jest-mock-extended": "^1.0.18",
"make-plural": "^6.2.2", "make-plural": "^6.2.2",
"mini-css-extract-plugin": "^2.5.2", "mini-css-extract-plugin": "^1.6.2",
"node-gyp": "7.1.2", "node-gyp": "7.1.2",
"node-loader": "^2.0.0", "node-loader": "^1.0.3",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"playwright": "^1.17.1", "playwright": "^1.17.1",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"postcss-loader": "^6.2.1", "postcss-loader": "^4.3.0",
"progress-bar-webpack-plugin": "^2.1.0",
"randomcolor": "^0.6.2", "randomcolor": "^0.6.2",
"raw-loader": "^4.0.2",
"react-beautiful-dnd": "^13.1.0", "react-beautiful-dnd": "^13.1.0",
"react-refresh": "^0.11.0", "react-refresh": "^0.9.0",
"react-refresh-typescript": "^2.0.3",
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-select": "3.2.0", "react-select": "3.2.0",
"react-select-event": "^5.1.0", "react-select-event": "^5.1.0",
"react-table": "^7.7.0", "react-table": "^7.7.0",
"react-window": "^1.8.6", "react-window": "^1.8.6",
"sass": "^1.45.1", "sass": "^1.45.1",
"sass-loader": "^12.4.0", "sass-loader": "^10.2.0",
"sharp": "^0.29.3", "sharp": "^0.29.3",
"style-loader": "^3.3.1", "style-loader": "^2.0.0",
"tailwindcss": "^3.0.7", "tailwindcss": "^3.0.7",
"ts-jest": "26.5.6", "ts-jest": "26.5.6",
"ts-loader": "^9.2.6", "ts-loader": "^7.0.5",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",
"type-fest": "^1.0.2", "type-fest": "^1.0.2",
"typed-emitter": "^1.4.0", "typed-emitter": "^1.4.0",
"typedoc": "0.22.10", "typedoc": "0.22.10",
"typedoc-plugin-markdown": "^3.11.12", "typedoc-plugin-markdown": "^3.11.3",
"typeface-roboto": "^1.1.13", "typeface-roboto": "^1.1.13",
"typescript": "^4.5.2", "typescript": "^4.5.2",
"typescript-plugin-css-modules": "^3.4.0", "typescript-plugin-css-modules": "^3.4.0",
"webpack": "^5.67.0", "url-loader": "^4.1.1",
"webpack-cli": "^4.9.1", "webpack": "^4.46.0",
"webpack-dev-server": "^4.7.4", "webpack-cli": "^3.3.12",
"webpack-node-externals": "^3.0.0", "webpack-dev-server": "^3.11.3",
"webpack-node-externals": "^1.7.2",
"xterm": "^4.15.0", "xterm": "^4.15.0",
"xterm-addon-fit": "^0.5.0" "xterm-addon-fit": "^0.5.0"
} }

View File

@ -44,26 +44,26 @@ users:
`; `;
interface kubeconfig { interface kubeconfig {
apiVersion: string, apiVersion: string;
clusters: [{ clusters: [{
name: string, name: string;
cluster: { cluster: {
server: string server: string;
} };
}], }];
contexts: [{ contexts: [{
context: { context: {
cluster: string, cluster: string;
user: string, user: string;
}, };
name: string name: string;
}], }];
users: [{ users: [{
name: string name: string;
}], }];
kind: string, kind: string;
"current-context": string, "current-context": string;
preferences: {} preferences: {};
} }
let mockKubeConfig: kubeconfig; let mockKubeConfig: kubeconfig;

View File

@ -11,7 +11,6 @@ import { app } from "electron";
import type { CatalogEntitySpec } from "../catalog/catalog-entity"; import type { CatalogEntitySpec } from "../catalog/catalog-entity";
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events"; import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import { requestClusterActivation, requestClusterDisconnection } from "../../renderer/ipc"; import { requestClusterActivation, requestClusterDisconnection } from "../../renderer/ipc";
import KubeClusterCategoryIcon from "./icons/kubernetes.svg";
export interface KubernetesClusterPrometheusMetrics { export interface KubernetesClusterPrometheusMetrics {
address?: { address?: {
@ -139,7 +138,7 @@ class KubernetesClusterCategory extends CatalogCategory {
public readonly kind = "CatalogCategory"; public readonly kind = "CatalogCategory";
public metadata = { public metadata = {
name: "Clusters", name: "Clusters",
icon: KubeClusterCategoryIcon, icon: require(`!!raw-loader!./icons/kubernetes.svg`).default, // eslint-disable-line
}; };
public spec: CatalogCategorySpec = { public spec: CatalogCategorySpec = {
group: "entity.k8slens.dev", group: "entity.k8slens.dev",

View File

@ -3,16 +3,17 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { action, computed, observable, makeObservable } from "mobx";
import type { Disposer } from "../utils";
import { strictSet, iter, getOrInsertMap } from "../utils";
import { once } from "lodash"; import { once } from "lodash";
import { action, computed, makeObservable, observable } from "mobx";
import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity"; import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
import { ExtendedMap, iter, type Disposer } from "../utils";
export type CategoryFilter = (category: CatalogCategory) => any; export type CategoryFilter = (category: CatalogCategory) => any;
export class CatalogCategoryRegistry { export class CatalogCategoryRegistry {
protected categories = observable.set<CatalogCategory>(); protected categories = observable.set<CatalogCategory>();
protected groupKinds = new ExtendedMap<string, ExtendedMap<string, CatalogCategory>>(); protected groupKinds = new Map<string, Map<string, CatalogCategory>>();
protected filters = observable.set<CategoryFilter>([], { protected filters = observable.set<CategoryFilter>([], {
deep: false, deep: false,
}); });
@ -22,14 +23,14 @@ export class CatalogCategoryRegistry {
} }
@action add(category: CatalogCategory): Disposer { @action add(category: CatalogCategory): Disposer {
const byGroup = getOrInsertMap(this.groupKinds, category.spec.group);
this.categories.add(category); this.categories.add(category);
this.groupKinds strictSet(byGroup, category.spec.names.kind, category);
.getOrInsert(category.spec.group, ExtendedMap.new)
.strictSet(category.spec.names.kind, category);
return () => { return () => {
this.categories.delete(category); this.categories.delete(category);
this.groupKinds.get(category.spec.group).delete(category.spec.names.kind); byGroup.delete(category.spec.names.kind);
}; };
} }

View File

@ -165,7 +165,7 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm
* @param id The id of a category is parse * @param id The id of a category is parse
* @returns The group and kind parts of the ID * @returns The group and kind parts of the ID
*/ */
public static parseId(id: string): { group?: string, kind?: string } { public static parseId(id: string): { group?: string; kind?: string } {
const [group, kind] = id.split("/") ?? []; const [group, kind] = id.split("/") ?? [];
return { group, kind }; return { group, kind };
@ -250,7 +250,7 @@ export interface CatalogEntityContextMenu {
*/ */
confirm?: { confirm?: {
message: string; message: string;
} };
} }
export interface CatalogEntityAddMenu extends CatalogEntityContextMenu { export interface CatalogEntityAddMenu extends CatalogEntityContextMenu {
@ -262,7 +262,7 @@ export interface CatalogEntitySettingsMenu {
group?: string; group?: string;
title: string; title: string;
components: { components: {
View: React.ComponentType<any> View: React.ComponentType<any>;
}; };
} }

View File

@ -7,7 +7,7 @@ import { observable } from "mobx";
export interface ClusterFrameInfo { export interface ClusterFrameInfo {
frameId: number; frameId: number;
processId: number processId: number;
} }
export const clusterFrameMap = observable.map<string, ClusterFrameInfo>(); export const clusterFrameMap = observable.map<string, ClusterFrameInfo>();

View File

@ -22,7 +22,7 @@ export interface ClusterStoreModel {
} }
interface Dependencies { interface Dependencies {
createCluster: (model: ClusterModel) => Cluster createCluster: (model: ClusterModel) => Cluster;
} }
export class ClusterStore extends BaseStore<ClusterStoreModel> { export class ClusterStore extends BaseStore<ClusterStoreModel> {

View File

@ -157,7 +157,7 @@ export const initialNodeShellImage = "docker.io/alpine:3.13";
* The arguments for requesting to refresh a cluster's metadata * The arguments for requesting to refresh a cluster's metadata
*/ */
export interface ClusterRefreshOptions { export interface ClusterRefreshOptions {
refreshMetadata?: boolean refreshMetadata?: boolean;
} }
/** /**
@ -170,7 +170,7 @@ export interface ClusterState {
accessible: boolean; accessible: boolean;
ready: boolean; ready: boolean;
isAdmin: boolean; isAdmin: boolean;
allowedNamespaces: string[] allowedNamespaces: string[];
allowedResources: string[] allowedResources: string[];
isGlobalWatchEnabled: boolean; isGlobalWatchEnabled: boolean;
} }

View File

@ -4,17 +4,18 @@
*/ */
import esbuild from "esbuild"; import esbuild from "esbuild";
import type { Options as TSLoaderOptions } from "ts-loader";
/** /**
* A function returning webpack ts/tsx loader * A function returning webpack ts/tsx loader
*
* depends on env LENS_DEV_USE_ESBUILD_LOADER to use esbuild-loader (faster) or good-old ts-loader * depends on env LENS_DEV_USE_ESBUILD_LOADER to use esbuild-loader (faster) or good-old ts-loader
*
* @param testRegExp - the regex for webpack to conditional find the files
* @returns ts/tsx webpack loader configuration object * @returns ts/tsx webpack loader configuration object
*/ */
const getTSLoader = (options: Partial<TSLoaderOptions> = {}, testRegExp?: RegExp) => { const getTSLoader = (
testRegExp ??= /\.tsx?$/; // by default covers react/jsx-stuff testRegExp: RegExp, transpileOnly = true,
options.transpileOnly ??= true; ) => {
if (process.env.LENS_DEV_USE_ESBUILD_LOADER === "true") { if (process.env.LENS_DEV_USE_ESBUILD_LOADER === "true") {
console.info(`\n🚀 using esbuild-loader for ts(x)`); console.info(`\n🚀 using esbuild-loader for ts(x)`);
@ -34,7 +35,9 @@ const getTSLoader = (options: Partial<TSLoaderOptions> = {}, testRegExp?: RegExp
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: "ts-loader", loader: "ts-loader",
options, options: {
transpileOnly,
},
}, },
}; };
}; };

View File

@ -14,7 +14,7 @@ export interface HotbarItem {
}; };
params?: { params?: {
[key: string]: string; [key: string]: string;
} };
} }
export type Hotbar = Required<CreateHotbarData>; export type Hotbar = Required<CreateHotbarData>;

View File

@ -29,10 +29,10 @@ export function onceCorrect<
listener, listener,
verifier, verifier,
}: { }: {
source: IPC, source: IPC;
channel: string, channel: string;
listener: Listener, listener: Listener;
verifier: ListVerifier<Rest<Parameters<Listener>>>, verifier: ListVerifier<Rest<Parameters<Listener>>>;
}): void { }): void {
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]): void { function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]): void {
if (verifier(args)) { if (verifier(args)) {
@ -63,10 +63,10 @@ export function onCorrect<
listener, listener,
verifier, verifier,
}: { }: {
source: IPC, source: IPC;
channel: string, channel: string;
listener: Listener, listener: Listener;
verifier: ListVerifier<Rest<Parameters<Listener>>>, verifier: ListVerifier<Rest<Parameters<Listener>>>;
}): Disposer { }): Disposer {
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]) { function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]) {
if (verifier(args)) { if (verifier(args)) {
@ -89,9 +89,9 @@ export function handleCorrect<
handler, handler,
verifier, verifier,
}: { }: {
channel: string, channel: string;
handler: Handler, handler: Handler;
verifier: ListVerifier<Rest<Parameters<Handler>>>, verifier: ListVerifier<Rest<Parameters<Handler>>>;
}): Disposer { }): Disposer {
function wrappedHandler(event: Electron.IpcMainInvokeEvent, ...args: unknown[]): ReturnType<Handler> { function wrappedHandler(event: Electron.IpcMainInvokeEvent, ...args: unknown[]): ReturnType<Handler> {
if (verifier(args)) { if (verifier(args)) {

View File

@ -90,7 +90,7 @@ export interface RawHelmChart {
urls?: string[]; urls?: string[];
maintainers?: HelmChartMaintainer[]; maintainers?: HelmChartMaintainer[];
dependencies?: RawHelmChartDependency[]; dependencies?: RawHelmChartDependency[];
annotations?: Record<string, string>, annotations?: Record<string, string>;
} }
const helmChartMaintainerValidator = Joi.object<HelmChartMaintainer>({ const helmChartMaintainerValidator = Joi.object<HelmChartMaintainer>({

View File

@ -70,8 +70,8 @@ export interface IReleaseRevision {
type EndpointParams = {} type EndpointParams = {}
| { namespace: string } | { namespace: string }
| { namespace: string, name: string } | { namespace: string; name: string }
| { namespace: string, name: string, route: string }; | { namespace: string; name: string; route: string };
interface EndpointQuery { interface EndpointQuery {
all?: boolean; all?: boolean;
@ -163,13 +163,13 @@ interface HelmReleaseDto {
} }
export interface HelmRelease extends HelmReleaseDto, ItemObject { export interface HelmRelease extends HelmReleaseDto, ItemObject {
getNs: () => string getNs: () => string;
getChart: (withVersion?: boolean) => string getChart: (withVersion?: boolean) => string;
getRevision: () => number getRevision: () => number;
getStatus: () => string getStatus: () => string;
getVersion: () => string getVersion: () => string;
getUpdated: (humanize?: boolean, compact?: boolean) => string | number getUpdated: (humanize?: boolean, compact?: boolean) => string | number;
getRepo: () => Promise<string> getRepo: () => Promise<string>;
} }
const toHelmRelease = (release: HelmReleaseDto) : HelmRelease => ({ const toHelmRelease = (release: HelmReleaseDto) : HelmRelease => ({

View File

@ -58,7 +58,7 @@ export interface IIngressService {
port: { port: {
name?: string; name?: string;
number?: number; number?: number;
} };
} }
export const getBackendServiceNamePort = (backend: IIngressBackend) => { export const getBackendServiceNamePort = (backend: IIngressBackend) => {
@ -96,8 +96,8 @@ export interface Ingress {
apiGroup: string; apiGroup: string;
kind: string; kind: string;
name: string; name: string;
} };
}> }>;
}; };
status: { status: {
loadBalancer: { loadBalancer: {

View File

@ -33,7 +33,7 @@ export enum LimitPart {
type LimitRangeParts = Partial<Record<LimitPart, Record<string, string>>>; type LimitRangeParts = Partial<Record<LimitPart, Record<string, string>>>;
export interface LimitRangeItem extends LimitRangeParts { export interface LimitRangeItem extends LimitRangeParts {
type: string type: string;
} }
export interface LimitRange { export interface LimitRange {

View File

@ -101,8 +101,8 @@ export interface Node {
daemonEndpoints?: { daemonEndpoints?: {
kubeletEndpoint: { kubeletEndpoint: {
Port: number; //it must be uppercase for backwards compatibility Port: number; //it must be uppercase for backwards compatibility
} };
} };
nodeInfo?: { nodeInfo?: {
machineID: string; machineID: string;
systemUUID: string; systemUUID: string;

View File

@ -16,10 +16,10 @@ export interface PodDisruptionBudget {
selector: LabelSelector; selector: LabelSelector;
}; };
status: { status: {
currentHealthy: number currentHealthy: number;
desiredHealthy: number desiredHealthy: number;
disruptionsAllowed: number disruptionsAllowed: number;
expectedPods: number expectedPods: number;
}; };
} }

View File

@ -43,9 +43,9 @@ export interface IPodMetrics<T = IMetrics> {
[metric: string]: T; [metric: string]: T;
cpuUsage: T; cpuUsage: T;
memoryUsage: T; memoryUsage: T;
fsUsage: T, fsUsage: T;
fsWrites: T, fsWrites: T;
fsReads: T, fsReads: T;
networkReceive: T; networkReceive: T;
networkTransmit: T; networkTransmit: T;
cpuRequests?: T; cpuRequests?: T;
@ -117,7 +117,7 @@ export interface IPodContainer extends Partial<Record<PodContainerProbe, IContai
}; };
secretRef?: { secretRef?: {
name: string; name: string;
} };
}[]; }[];
volumeMounts?: { volumeMounts?: {
name: string; name: string;
@ -273,7 +273,7 @@ export class Pod extends WorkloadKubeObject {
hostIP: string; hostIP: string;
podIP: string; podIP: string;
podIPs?: { podIPs?: {
ip: string ip: string;
}[]; }[];
startTime: string; startTime: string;
initContainerStatuses?: IPodContainerStatus[]; initContainerStatuses?: IPodContainerStatus[];

View File

@ -88,7 +88,7 @@ export interface KubeApiListOptions {
export interface IKubePreferredVersion { export interface IKubePreferredVersion {
preferredVersion?: { preferredVersion?: {
version: string; version: string;
} };
} }
export interface IKubeResourceList { export interface IKubeResourceList {
@ -105,7 +105,7 @@ export interface IKubeResourceList {
export interface ILocalKubeApiConfig { export interface ILocalKubeApiConfig {
metadata: { metadata: {
uid: string; uid: string;
} };
} }
export type PropagationPolicy = undefined | "Orphan" | "Foreground" | "Background"; export type PropagationPolicy = undefined | "Orphan" | "Foreground" | "Background";
@ -116,7 +116,7 @@ export type PropagationPolicy = undefined | "Orphan" | "Foreground" | "Backgroun
export interface IKubeApiCluster extends ILocalKubeApiConfig { } export interface IKubeApiCluster extends ILocalKubeApiConfig { }
export type PartialKubeObject<T extends KubeObject> = Partial<Omit<T, "metadata">> & { export type PartialKubeObject<T extends KubeObject> = Partial<Omit<T, "metadata">> & {
metadata?: Partial<T["metadata"]>, metadata?: Partial<T["metadata"]>;
}; };
export interface IRemoteKubeApiConfig { export interface IRemoteKubeApiConfig {
@ -124,12 +124,12 @@ export interface IRemoteKubeApiConfig {
server: string; server: string;
caData?: string; caData?: string;
skipTLSVerify?: boolean; skipTLSVerify?: boolean;
} };
user: { user: {
token?: string | (() => Promise<string>); token?: string | (() => Promise<string>);
clientCertificateData?: string; clientCertificateData?: string;
clientKeyData?: string; clientKeyData?: string;
} };
} }
export function forCluster<T extends KubeObject, Y extends KubeApi<T> = KubeApi<T>>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<T>, apiClass: new (apiOpts: IKubeApiOptions<T>) => Y = null): KubeApi<T> { export function forCluster<T extends KubeObject, Y extends KubeApi<T> = KubeApi<T>>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<T>, apiClass: new (apiOpts: IKubeApiOptions<T>) => Y = null): KubeApi<T> {
@ -219,7 +219,7 @@ export type KubeApiWatchCallback = (data: IKubeWatchEvent<KubeJsonApiData>, erro
export type KubeApiWatchOptions = { export type KubeApiWatchOptions = {
namespace: string; namespace: string;
callback?: KubeApiWatchCallback; callback?: KubeApiWatchCallback;
abortController?: AbortController abortController?: AbortController;
watchId?: string; watchId?: string;
retry?: boolean; retry?: boolean;

View File

@ -247,7 +247,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
} }
@action @action
async reloadAll(opts: { force?: boolean, namespaces?: string[], merge?: boolean } = {}) { async reloadAll(opts: { force?: boolean; namespaces?: string[]; merge?: boolean } = {}) {
const { force = false, ...loadingOptions } = opts; const { force = false, ...loadingOptions } = opts;
if (this.isLoading || (this.isLoaded && !force)) { if (this.isLoading || (this.isLoaded && !force)) {

View File

@ -147,7 +147,7 @@ export function loadConfigFromString(content: string): ConfigResult {
} }
export interface SplitConfigEntry { export interface SplitConfigEntry {
config: KubeConfig, config: KubeConfig;
error?: string; error?: string;
} }

View File

@ -63,8 +63,8 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R
} }
interface Dependencies { interface Dependencies {
extensionLoader: ExtensionLoader extensionLoader: ExtensionLoader;
extensionsStore: ExtensionsStore extensionsStore: ExtensionsStore;
} }
export abstract class LensProtocolRouter { export abstract class LensProtocolRouter {

View File

@ -327,7 +327,7 @@ export type ExtensionRegistry = {
location: ExtensionRegistryLocation.DEFAULT | ExtensionRegistryLocation.NPMRC; location: ExtensionRegistryLocation.DEFAULT | ExtensionRegistryLocation.NPMRC;
customUrl?: undefined; customUrl?: undefined;
} | { } | {
location: ExtensionRegistryLocation.CUSTOM, location: ExtensionRegistryLocation.CUSTOM;
customUrl: string; customUrl: string;
}; };

View File

@ -5,13 +5,13 @@
import { app, ipcMain } from "electron"; import { app, ipcMain } from "electron";
import semver, { SemVer } from "semver"; import semver, { SemVer } from "semver";
import { action, computed, makeObservable, observable, reaction } from "mobx"; import { action, computed, observable, reaction, makeObservable, isObservableArray, isObservableSet, isObservableMap } from "mobx";
import { BaseStore } from "../base-store"; import { BaseStore } from "../base-store";
import migrations, { fileNameMigration } from "../../migrations/user-store"; import migrations, { fileNameMigration } from "../../migrations/user-store";
import { getAppVersion } from "../utils/app-version"; import { getAppVersion } from "../utils/app-version";
import { kubeConfigDefaultPath } from "../kube-helpers"; import { kubeConfigDefaultPath } from "../kube-helpers";
import { appEventBus } from "../app-event-bus/event-bus"; import { appEventBus } from "../app-event-bus/event-bus";
import { getOrInsertSet, toggle, toJS } from "../../renderer/utils"; import { getOrInsertSet, toggle, toJS, entries, fromEntries } from "../../renderer/utils";
import { DESCRIPTORS } from "./preferences-helpers"; import { DESCRIPTORS } from "./preferences-helpers";
import type { EditorConfiguration, ExtensionRegistry, KubeconfigSyncValue, UserPreferencesModel, TerminalConfig } from "./preferences-helpers"; import type { EditorConfiguration, ExtensionRegistry, KubeconfigSyncValue, UserPreferencesModel, TerminalConfig } from "./preferences-helpers";
import logger from "../../main/logger"; import logger from "../../main/logger";
@ -165,55 +165,31 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
this.lastSeenAppVersion = lastSeenAppVersion; this.lastSeenAppVersion = lastSeenAppVersion;
} }
this.httpsProxy = DESCRIPTORS.httpsProxy.fromStore(preferences?.httpsProxy); for (const [key, { fromStore }] of entries(DESCRIPTORS)) {
this.shell = DESCRIPTORS.shell.fromStore(preferences?.shell); const curVal = this[key];
this.colorTheme = DESCRIPTORS.colorTheme.fromStore(preferences?.colorTheme); const newVal = fromStore((preferences)?.[key] as never) as never;
this.terminalTheme = DESCRIPTORS.terminalTheme.fromStore(preferences?.terminalTheme);
this.localeTimezone = DESCRIPTORS.localeTimezone.fromStore(preferences?.localeTimezone); if (
this.allowUntrustedCAs = DESCRIPTORS.allowUntrustedCAs.fromStore(preferences?.allowUntrustedCAs); isObservableArray(curVal)
this.allowTelemetry = DESCRIPTORS.allowTelemetry.fromStore(preferences?.allowTelemetry); || isObservableSet(curVal)
this.allowErrorReporting = DESCRIPTORS.allowErrorReporting.fromStore(preferences?.allowErrorReporting); || isObservableMap(curVal)
this.downloadMirror = DESCRIPTORS.downloadMirror.fromStore(preferences?.downloadMirror); ) {
this.downloadKubectlBinaries = DESCRIPTORS.downloadKubectlBinaries.fromStore(preferences?.downloadKubectlBinaries); curVal.replace(newVal);
this.downloadBinariesPath = DESCRIPTORS.downloadBinariesPath.fromStore(preferences?.downloadBinariesPath); } else {
this.kubectlBinariesPath = DESCRIPTORS.kubectlBinariesPath.fromStore(preferences?.kubectlBinariesPath); this[key] = newVal;
this.openAtLogin = DESCRIPTORS.openAtLogin.fromStore(preferences?.openAtLogin); }
this.hiddenTableColumns.replace(DESCRIPTORS.hiddenTableColumns.fromStore(preferences?.hiddenTableColumns)); }
this.syncKubeconfigEntries.replace(DESCRIPTORS.syncKubeconfigEntries.fromStore(preferences?.syncKubeconfigEntries));
this.editorConfiguration = DESCRIPTORS.editorConfiguration.fromStore(preferences?.editorConfiguration);
this.terminalCopyOnSelect = DESCRIPTORS.terminalCopyOnSelect.fromStore(preferences?.terminalCopyOnSelect);
this.terminalConfig = DESCRIPTORS.terminalConfig.fromStore(preferences?.terminalConfig);
this.updateChannel = DESCRIPTORS.updateChannel.fromStore(preferences?.updateChannel);
this.extensionRegistryUrl = DESCRIPTORS.extensionRegistryUrl.fromStore(preferences?.extensionRegistryUrl);
} }
toJSON(): UserStoreModel { toJSON(): UserStoreModel {
const model: UserStoreModel = { const preferences = fromEntries(
lastSeenAppVersion: this.lastSeenAppVersion, entries(DESCRIPTORS)
preferences: { .map(([key, { toStore }]) => [key, toStore(this[key] as never)]),
httpsProxy: DESCRIPTORS.httpsProxy.toStore(this.httpsProxy), ) as UserPreferencesModel;
shell: DESCRIPTORS.shell.toStore(this.shell),
colorTheme: DESCRIPTORS.colorTheme.toStore(this.colorTheme),
terminalTheme: DESCRIPTORS.terminalTheme.toStore(this.terminalTheme),
localeTimezone: DESCRIPTORS.localeTimezone.toStore(this.localeTimezone),
allowUntrustedCAs: DESCRIPTORS.allowUntrustedCAs.toStore(this.allowUntrustedCAs),
allowTelemetry: DESCRIPTORS.allowTelemetry.toStore(this.allowTelemetry),
allowErrorReporting: DESCRIPTORS.allowErrorReporting.toStore(this.allowErrorReporting),
downloadMirror: DESCRIPTORS.downloadMirror.toStore(this.downloadMirror),
downloadKubectlBinaries: DESCRIPTORS.downloadKubectlBinaries.toStore(this.downloadKubectlBinaries),
downloadBinariesPath: DESCRIPTORS.downloadBinariesPath.toStore(this.downloadBinariesPath),
kubectlBinariesPath: DESCRIPTORS.kubectlBinariesPath.toStore(this.kubectlBinariesPath),
openAtLogin: DESCRIPTORS.openAtLogin.toStore(this.openAtLogin),
hiddenTableColumns: DESCRIPTORS.hiddenTableColumns.toStore(this.hiddenTableColumns),
syncKubeconfigEntries: DESCRIPTORS.syncKubeconfigEntries.toStore(this.syncKubeconfigEntries),
editorConfiguration: DESCRIPTORS.editorConfiguration.toStore(this.editorConfiguration),
terminalCopyOnSelect: DESCRIPTORS.terminalCopyOnSelect.toStore(this.terminalCopyOnSelect),
terminalConfig: DESCRIPTORS.terminalConfig.toStore(this.terminalConfig),
updateChannel: DESCRIPTORS.updateChannel.toStore(this.updateChannel),
extensionRegistryUrl: DESCRIPTORS.extensionRegistryUrl.toStore(this.extensionRegistryUrl),
},
};
return toJS(model); return toJS({
lastSeenAppVersion: this.lastSeenAppVersion,
preferences,
});
} }
} }

View File

@ -21,8 +21,8 @@ export function getOrInsert<K, V>(map: Map<K, V>, key: K, value: V): V {
} }
/** /**
* Like `getOrInsert` but specifically for when `V` is `Map<any, any>` so that * Like `getOrInsert` but specifically for when `V` is `Map<MK, MV>` so that
* the typings are inferred. * the typings are inferred correctly.
*/ */
export function getOrInsertMap<K, MK, MV>(map: Map<K, Map<MK, MV>>, key: K): Map<MK, MV> { export function getOrInsertMap<K, MK, MV>(map: Map<K, Map<MK, MV>>, key: K): Map<MK, MV> {
return getOrInsert(map, key, new Map<MK, MV>()); return getOrInsert(map, key, new Map<MK, MV>());
@ -37,11 +37,39 @@ export function getOrInsertSet<K, SK>(map: Map<K, Set<SK>>, key: K): Set<SK> {
} }
/** /**
* Like `getOrInsert` but with delayed creation of the item * Like `getOrInsert` but with delayed creation of the item. Which is useful
* if it is very expensive to create the initial value.
*/ */
export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, value: () => V): V { export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, builder: () => V): V {
if (!map.has(key)) { if (!map.has(key)) {
map.set(key, value()); map.set(key, builder());
}
return map.get(key);
}
/**
* Set the value associated with `key` iff there was not a previous value
* @param map The map to interact with
* @throws if `key` already in map
* @returns `this` so that `strictSet` can be chained
*/
export function strictSet<K, V>(map: Map<K, V>, key: K, val: V): typeof map {
if (map.has(key)) {
throw new TypeError("Duplicate key in map");
}
return map.set(key, val);
}
/**
* Get the value associated with `key`
* @param map The map to interact with
* @throws if `key` did not a value associated with it
*/
export function strictGet<K, V>(map: Map<K, V>, key: K): V {
if (!map.has(key)) {
throw new TypeError("key not in map");
} }
return map.get(key); return map.get(key);

View File

@ -1,70 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, ObservableMap, runInAction } from "mobx";
export function multiSet<T, V>(map: Map<T, V>, newEntries: [T, V][]): void {
runInAction(() => {
for (const [key, val] of newEntries) {
map.set(key, val);
}
});
}
export class ExtendedMap<K, V> extends Map<K, V> {
static new<K, V>(entries?: readonly (readonly [K, V])[] | null): ExtendedMap<K, V> {
return new ExtendedMap<K, V>(entries);
}
/**
* Get the value behind `key`. If it was not present, first insert the value returned by `getVal`
* @param key The key to insert into the map with
* @param getVal A function that returns a new instance of `V`.
* @returns The value in the map
*/
getOrInsert(key: K, getVal: () => V): V {
if (this.has(key)) {
return this.get(key);
}
return this.set(key, getVal()).get(key);
}
/**
* Set the value associated with `key` iff there was not a previous value
* @throws if `key` already in map
* @returns `this` so that `strictSet` can be chained
*/
strictSet(key: K, val: V): this {
if (this.has(key)) {
throw new TypeError("Duplicate key in map");
}
return this.set(key, val);
}
/**
* Get the value associated with `key`
* @throws if `key` did not a value associated with it
*/
strictGet(key: K): V {
if (!this.has(key)) {
throw new TypeError("key not in map");
}
return this.get(key);
}
}
export class ExtendedObservableMap<K, V> extends ObservableMap<K, V> {
@action
getOrInsert(key: K, getVal: () => V): V {
if (this.has(key)) {
return this.get(key);
}
return this.set(key, getVal()).get(key);
}
}

View File

@ -25,13 +25,12 @@ export * from "./delay";
export * from "./disposer"; export * from "./disposer";
export * from "./downloadFile"; export * from "./downloadFile";
export * from "./escapeRegExp"; export * from "./escapeRegExp";
export * from "./extended-map";
export * from "./formatDuration"; export * from "./formatDuration";
export * from "./getRandId"; export * from "./getRandId";
export * from "./hash-set"; export * from "./hash-set";
export * from "./n-fircate"; export * from "./n-fircate";
export * from "./objects"; export * from "./objects";
export * from "./openExternal"; export * from "./openBrowser";
export * from "./paths"; export * from "./paths";
export * from "./promise-exec"; export * from "./promise-exec";
export * from "./reject-promise"; export * from "./reject-promise";

View File

@ -7,6 +7,10 @@
* A better typed version of `Object.fromEntries` where the keys are known to * A better typed version of `Object.fromEntries` where the keys are known to
* be a specific subset * be a specific subset
*/ */
export function fromEntries<T, Key extends string>(entries: Iterable<readonly [Key, T]>): { [k in Key]: T } { export function fromEntries<T, Key extends string>(entries: Iterable<readonly [Key, T]>): Record<Key, T> {
return Object.fromEntries(entries) as { [k in Key]: T }; return Object.fromEntries(entries) as { [k in Key]: T };
} }
export function entries<T extends Record<string, any>>(obj: T): [keyof T, T[keyof T]][] {
return Object.entries(obj);
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { shell } from "electron";
const allowedProtocols = new Set(["http:", "https:"]);
/**
* Opens a link using the program configured as the default browser
* on the target platform. Will reject URLs with a scheme other than
* http or https to prevent programs other than the default browser
* running.
*
* @param url The URL to open
*/
export function openBrowser(url: string): Promise<void> {
if (allowedProtocols.has(new URL(url).protocol)) {
return shell.openExternal(url);
}
return Promise.reject(new TypeError("not an http(s) URL"));
}
/**
* @deprecated use openBrowser
*/
export const openExternal = openBrowser;

View File

@ -1,11 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Opens a link in external browser
import { shell } from "electron";
export function openExternal(url: string) {
return shell.openExternal(url);
}

View File

@ -3,6 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
export { Singleton, openExternal } from "../../common/utils"; export { Singleton, openExternal, openBrowser } from "../../common/utils";
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault"; export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault";
export { cssNames } from "../../renderer/utils/cssNames"; export { cssNames } from "../../renderer/utils/cssNames";

View File

@ -32,7 +32,7 @@ interface Dependencies {
isCompatibleExtension: (manifest: LensExtensionManifest) => boolean; isCompatibleExtension: (manifest: LensExtensionManifest) => boolean;
installExtension: (name: string) => Promise<void>; installExtension: (name: string) => Promise<void>;
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void> installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>;
extensionPackageRootDirectory: string; extensionPackageRootDirectory: string;
} }

View File

@ -13,7 +13,7 @@ import type { PackageJson } from "type-fest";
const logModule = "[EXTENSION-INSTALLER]"; const logModule = "[EXTENSION-INSTALLER]";
interface Dependencies { interface Dependencies {
extensionPackageRootDirectory: string extensionPackageRootDirectory: string;
} }
/** /**

View File

@ -17,7 +17,7 @@ interface FSProvisionModel {
} }
interface Dependencies { interface Dependencies {
directoryForExtensionData: string directoryForExtensionData: string;
} }
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> { export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {

View File

@ -23,13 +23,13 @@ import { requestExtensionLoaderInitialState } from "../../renderer/ipc";
const logModule = "[EXTENSIONS-LOADER]"; const logModule = "[EXTENSIONS-LOADER]";
interface Dependencies { interface Dependencies {
updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void;
createExtensionInstance: (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension, createExtensionInstance: (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension;
} }
export interface ExtensionLoading { export interface ExtensionLoading {
isBundled: boolean, isBundled: boolean;
loaded: Promise<void> loaded: Promise<void>;
} }
/** /**

View File

@ -36,7 +36,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
protected state = observable.map<LensExtensionId, LensExtensionState>(); protected state = observable.map<LensExtensionId, LensExtensionState>();
isEnabled({ id, isBundled }: { id: string, isBundled: boolean }): boolean { isEnabled({ id, isBundled }: { id: string; isBundled: boolean }): boolean {
// By default false, so that copied extensions are disabled by default. // By default false, so that copied extensions are disabled by default.
// If user installs the extension from the UI, the Extensions component will specifically enable it. // If user installs the extension from the UI, the Extensions component will specifically enable it.
return isBundled || Boolean(this.state.get(id)?.enabled); return isBundled || Boolean(this.state.get(id)?.enabled);

View File

@ -10,5 +10,5 @@ import type { FileSystemProvisionerStore } from "./extension-loader/create-exten
export const setLensExtensionDependencies = Symbol("set-lens-extension-dependencies"); export const setLensExtensionDependencies = Symbol("set-lens-extension-dependencies");
export interface LensExtensionDependencies { export interface LensExtensionDependencies {
fileSystemProvisionerStore: FileSystemProvisionerStore fileSystemProvisionerStore: FileSystemProvisionerStore;
} }

View File

@ -38,7 +38,7 @@ export interface PageParams<V = any> {
export interface PageComponentProps<P extends PageParams = {}> { export interface PageComponentProps<P extends PageParams = {}> {
params?: { params?: {
[N in keyof P]: PageParam<P[N]>; [N in keyof P]: PageParam<P[N]>;
} };
} }
export interface RegisteredPage { export interface RegisteredPage {

View File

@ -7,7 +7,7 @@ import { pathNames, PathName } from "../../common/app-paths/app-path-names";
import type { AppPaths } from "../../common/app-paths/app-path-injection-token"; import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
interface Dependencies { interface Dependencies {
getAppPath: (name: PathName) => string getAppPath: (name: PathName) => string;
} }
export const getAppPaths = ({ getAppPath }: Dependencies) => export const getAppPaths = ({ getAppPath }: Dependencies) =>

View File

@ -10,7 +10,7 @@ import { FSWatcher, watch } from "chokidar";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import type stream from "stream"; import type stream from "stream";
import { bytesToUnits, Disposer, ExtendedObservableMap, iter, noop } from "../../../common/utils"; import { bytesToUnits, Disposer, getOrInsertWith, iter, noop } from "../../../common/utils";
import logger from "../../logger"; import logger from "../../logger";
import type { KubeConfig } from "@kubernetes/client-node"; import type { KubeConfig } from "@kubernetes/client-node";
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers"; import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
@ -48,8 +48,8 @@ const folderSyncMaxAllowedFileReadSize = 2 * 1024 * 1024; // 2 MiB
const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; // 32 MiB const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; // 32 MiB
interface Dependencies { interface Dependencies {
directoryForKubeConfigs: string directoryForKubeConfigs: string;
createCluster: (model: ClusterModel) => Cluster createCluster: (model: ClusterModel) => Cluster;
} }
const kubeConfigSyncName = "lens:kube-sync"; const kubeConfigSyncName = "lens:kube-sync";
@ -294,7 +294,7 @@ const diffChangedConfigFor = (dependencies: Dependencies) => ({ filePath, source
}; };
const watchFileChanges = (filePath: string, dependencies: Dependencies): [IComputedValue<CatalogEntity[]>, Disposer] => { const watchFileChanges = (filePath: string, dependencies: Dependencies): [IComputedValue<CatalogEntity[]>, Disposer] => {
const rootSource = new ExtendedObservableMap<string, ObservableMap<string, RootSourceValue>>(); const rootSource = observable.map<string, ObservableMap<string, RootSourceValue>>();
const derivedSource = computed(() => Array.from(iter.flatMap(rootSource.values(), from => iter.map(from.values(), child => child[1])))); const derivedSource = computed(() => Array.from(iter.flatMap(rootSource.values(), from => iter.map(from.values(), child => child[1]))));
let watcher: FSWatcher; let watcher: FSWatcher;
@ -335,7 +335,7 @@ const watchFileChanges = (filePath: string, dependencies: Dependencies): [ICompu
cleanup(); cleanup();
cleanupFns.set(childFilePath, diffChangedConfig({ cleanupFns.set(childFilePath, diffChangedConfig({
filePath: childFilePath, filePath: childFilePath,
source: rootSource.getOrInsert(childFilePath, observable.map), source: getOrInsertWith(rootSource, childFilePath, observable.map),
stats, stats,
maxAllowedFileReadSize, maxAllowedFileReadSize,
})); }));
@ -353,7 +353,7 @@ const watchFileChanges = (filePath: string, dependencies: Dependencies): [ICompu
cleanupFns.set(childFilePath, diffChangedConfig({ cleanupFns.set(childFilePath, diffChangedConfig({
filePath: childFilePath, filePath: childFilePath,
source: rootSource.getOrInsert(childFilePath, observable.map), source: getOrInsertWith(rootSource, childFilePath, observable.map),
stats, stats,
maxAllowedFileReadSize, maxAllowedFileReadSize,
})); }));

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { action, computed, type IComputedValue, type IObservableArray, makeObservable, observable } from "mobx"; import { action, computed, IComputedValue, IObservableArray, makeObservable, observable } from "mobx";
import { CatalogCategoryRegistry, catalogCategoryRegistry, CatalogEntity, CatalogEntityConstructor } from "../../common/catalog"; import { CatalogCategoryRegistry, catalogCategoryRegistry, CatalogEntity, CatalogEntityConstructor } from "../../common/catalog";
import { iter } from "../../common/utils"; import { iter } from "../../common/utils";

View File

@ -8,8 +8,8 @@ import type { Cluster } from "../../common/cluster/cluster";
import { k8sRequest } from "../k8s-request"; import { k8sRequest } from "../k8s-request";
export type ClusterDetectionResult = { export type ClusterDetectionResult = {
value: string | number | boolean value: string | number | boolean;
accuracy: number accuracy: number;
}; };
export class BaseClusterDetector { export class BaseClusterDetector {

View File

@ -26,7 +26,7 @@ interface PrometheusServicePreferences {
} }
interface Dependencies { interface Dependencies {
createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
} }
export class ContextHandler { export class ContextHandler {

View File

@ -14,8 +14,8 @@ import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-char
import { iter, sortCharts } from "../../common/utils"; import { iter, sortCharts } from "../../common/utils";
interface ChartCacheEntry { interface ChartCacheEntry {
data: Buffer, data: Buffer;
mtimeMs: number, mtimeMs: number;
} }
export interface HelmCacheFile { export interface HelmCacheFile {

View File

@ -104,10 +104,8 @@ export async function upgradeRelease(name: string, chart: string, values: any, n
]; ];
try { try {
const output = await execHelm(args);
return { return {
log: output, log: await execHelm(args),
release: getRelease(name, namespace, kubeconfigPath, kubectlPath), release: getRelease(name, namespace, kubeconfigPath, kubectlPath),
}; };
} finally { } finally {

View File

@ -19,19 +19,19 @@ export type HelmEnv = Record<string, string> & {
}; };
export interface HelmRepoConfig { export interface HelmRepoConfig {
repositories: HelmRepo[] repositories: HelmRepo[];
} }
export interface HelmRepo { export interface HelmRepo {
name: string; name: string;
url: string; url: string;
cacheFilePath?: string cacheFilePath?: string;
caFile?: string, caFile?: string;
certFile?: string, certFile?: string;
insecureSkipTlsVerify?: boolean, insecureSkipTlsVerify?: boolean;
keyFile?: string, keyFile?: string;
username?: string, username?: string;
password?: string, password?: string;
} }
async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise<string> { async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise<string> {
@ -49,9 +49,9 @@ async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFile
export class HelmRepoManager extends Singleton { export class HelmRepoManager extends Singleton {
protected repos: HelmRepo[]; protected repos: HelmRepo[];
protected helmEnv: HelmEnv; protected helmEnv: HelmEnv;
protected initialized: boolean; protected didUpdateOnce: boolean;
public static async loadAvailableRepos(): Promise<HelmRepo[]> { public async loadAvailableRepos(): Promise<HelmRepo[]> {
const res = await customRequestPromise({ const res = await customRequestPromise({
uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json", uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json",
json: true, json: true,
@ -59,21 +59,31 @@ export class HelmRepoManager extends Singleton {
timeout: 10000, timeout: 10000,
}); });
return orderBy<HelmRepo>(res.body, repo => repo.name); return orderBy(res.body as HelmRepo[], repo => repo.name);
} }
private async init() { private async ensureInitialized() {
helmCli.setLogger(logger); helmCli.setLogger(logger);
await helmCli.ensureBinary(); await helmCli.ensureBinary();
if (!this.initialized) { this.helmEnv ??= await this.parseHelmEnv();
this.helmEnv = await HelmRepoManager.parseHelmEnv();
await HelmRepoManager.update(); const repos = await this.list();
this.initialized = true;
if (repos.length === 0) {
await this.addRepo({
name: "bitnami",
url: "https://charts.bitnami.com/bitnami",
});
}
if (!this.didUpdateOnce) {
await this.update();
this.didUpdateOnce = true;
} }
} }
protected static async parseHelmEnv() { protected async parseHelmEnv() {
const output = await execHelm(["env"]); const output = await execHelm(["env"]);
const lines = output.split(/\r?\n/); // split by new line feed const lines = output.split(/\r?\n/); // split by new line feed
const env: HelmEnv = {}; const env: HelmEnv = {};
@ -95,38 +105,28 @@ export class HelmRepoManager extends Singleton {
return repos.find(repo => repo.name === name); return repos.find(repo => repo.name === name);
} }
private async readConfig(): Promise<HelmRepoConfig> { private async list(): Promise<HelmRepo[]> {
try { try {
const rawConfig = await readFile(this.helmEnv.HELM_REPOSITORY_CONFIG, "utf8"); const rawConfig = await readFile(this.helmEnv.HELM_REPOSITORY_CONFIG, "utf8");
const parsedConfig = yaml.load(rawConfig); const parsedConfig = yaml.load(rawConfig) as HelmRepoConfig;
if (typeof parsedConfig === "object" && parsedConfig) { if (typeof parsedConfig === "object" && parsedConfig) {
return parsedConfig as HelmRepoConfig; return parsedConfig.repositories;
} }
} catch { } catch {
// ignore error // ignore error
} }
return { return [];
repositories: [],
};
} }
public async repositories(): Promise<HelmRepo[]> { public async repositories(): Promise<HelmRepo[]> {
try { try {
if (!this.initialized) { await this.ensureInitialized();
await this.init();
}
const { repositories } = await this.readConfig(); const repos = await this.list();
if (!repositories.length) { return repos.map(repo => ({
await HelmRepoManager.addRepo({ name: "bitnami", url: "https://charts.bitnami.com/bitnami" });
return await this.repositories();
}
return repositories.map(repo => ({
...repo, ...repo,
cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`, cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`,
})); }));
@ -137,25 +137,14 @@ export class HelmRepoManager extends Singleton {
} }
} }
public static async update() { public async update() {
return execHelm([ return execHelm([
"repo", "repo",
"update", "update",
]); ]);
} }
public static async addRepo({ name, url }: HelmRepo) { public async addRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) {
logger.info(`[HELM]: adding repo "${name}" from ${url}`);
return execHelm([
"repo",
"add",
name,
url,
]);
}
public static async addCustomRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) {
logger.info(`[HELM]: adding repo ${name} from ${url}`); logger.info(`[HELM]: adding repo ${name} from ${url}`);
const args = [ const args = [
"repo", "repo",
@ -191,7 +180,7 @@ export class HelmRepoManager extends Singleton {
return execHelm(args); return execHelm(args);
} }
public static async removeRepo({ name, url }: HelmRepo): Promise<string> { public async removeRepo({ name, url }: HelmRepo): Promise<string> {
logger.info(`[HELM]: removing repo ${name} (${url})`); logger.info(`[HELM]: removing repo ${name} (${url})`);
return execHelm([ return execHelm([

View File

@ -10,7 +10,7 @@ import * as Mobx from "mobx";
import * as LensExtensionsCommonApi from "../extensions/common-api"; import * as LensExtensionsCommonApi from "../extensions/common-api";
import * as LensExtensionsMainApi from "../extensions/main-api"; import * as LensExtensionsMainApi from "../extensions/main-api";
import { app, autoUpdater, dialog, powerMonitor } from "electron"; 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 { LensProxy } from "./lens-proxy";
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import { ClusterManager } from "./cluster-manager"; import { ClusterManager } from "./cluster-manager";
@ -176,7 +176,7 @@ di.runSetups().then(() => {
try { try {
logger.info("🔌 Starting LensProxy"); logger.info("🔌 Starting LensProxy");
await lensProxy.listen(); // lensProxy.port available await lensProxy.listen();
} catch (error) { } catch (error) {
dialog.showErrorBox("Lens Error", `Could not start proxy: ${error?.message || "unknown error"}`); dialog.showErrorBox("Lens Error", `Could not start proxy: ${error?.message || "unknown error"}`);
@ -228,16 +228,6 @@ di.runSetups().then(() => {
logger.info("🖥️ Starting WindowManager"); logger.info("🖥️ Starting WindowManager");
const windowManager = WindowManager.createInstance(); 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);
windowManager.mainContentUrl = `http://localhost:${devServer.options.port}`;
await devServer.start();
}
const menuItems = di.inject(electronMenuItemsInjectable); const menuItems = di.inject(electronMenuItemsInjectable);
const trayMenuItems = di.inject(trayMenuItemsInjectable); const trayMenuItems = di.inject(trayMenuItemsInjectable);

View File

@ -26,7 +26,7 @@ import { showOpenDialog } from "../../ipc/dialog";
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../common/ipc/window"; import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../common/ipc/window";
interface Dependencies { interface Dependencies {
electronMenuItems: IComputedValue<MenuRegistration[]>, electronMenuItems: IComputedValue<MenuRegistration[]>;
directoryForLensLocalStorage: string; directoryForLensLocalStorage: string;
} }

View File

@ -5,7 +5,7 @@
import { BrowserWindow, dialog, OpenDialogOptions } from "electron"; import { BrowserWindow, dialog, OpenDialogOptions } from "electron";
export async function showOpenDialog(dialogOptions: OpenDialogOptions): Promise<{ canceled: boolean; filePaths: string[]; }> { export async function showOpenDialog(dialogOptions: OpenDialogOptions): Promise<{ canceled: boolean; filePaths: string[] }> {
const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), dialogOptions); const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), dialogOptions);
return { canceled, filePaths }; return { canceled, filePaths };

View File

@ -13,7 +13,7 @@ import logger from "../logger";
import { LensProxy } from "../lens-proxy"; import { LensProxy } from "../lens-proxy";
interface Dependencies { interface Dependencies {
directoryForTemp: string directoryForTemp: string;
} }
export class KubeconfigManager { export class KubeconfigManager {

View File

@ -64,10 +64,10 @@ interface Dependencies {
directoryForKubectlBinaries: string; directoryForKubectlBinaries: string;
userStore: { userStore: {
kubectlBinariesPath?: string kubectlBinariesPath?: string;
downloadBinariesPath?: string downloadBinariesPath?: string;
downloadKubectlBinaries: boolean downloadKubectlBinaries: boolean;
downloadMirror: string downloadMirror: string;
}; };
} }

View File

@ -20,7 +20,7 @@ import { getBoolean } from "./utils/parse-query";
type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | null; type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | null;
export interface LensProxyFunctions { export interface LensProxyFunctions {
getClusterForRequest: GetClusterForRequest, getClusterForRequest: GetClusterForRequest;
shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>; shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
kubeApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>; kubeApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
} }

View File

@ -2,14 +2,15 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, webContents, shell } from "electron"; import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, webContents } from "electron";
import { autorun, IComputedValue } from "mobx"; import { autorun, IComputedValue } from "mobx";
import type { WindowManager } from "../window-manager"; import type { WindowManager } from "../window-manager";
import { appName, isMac, isWindows, docsUrl, supportUrl, productName } from "../../common/vars"; import { appName, isMac, isWindows, docsUrl, supportUrl, productName } from "../../common/vars";
import logger from "../logger"; import logger from "../logger";
import { exitApp } from "../exit-app"; import { exitApp } from "../exit-app";
import { broadcastMessage } from "../../common/ipc"; import { broadcastMessage } from "../../common/ipc";
import packageJson from "../../../package.json"; import { openBrowser } from "../../common/utils";
import * as packageJson from "../../../package.json";
import { preferencesURL, extensionsURL, addClusterURL, catalogURL, welcomeURL } from "../../common/routes"; import { preferencesURL, extensionsURL, addClusterURL, catalogURL, welcomeURL } from "../../common/routes";
import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater"; import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater";
import type { MenuRegistration } from "./menu-registration"; import type { MenuRegistration } from "./menu-registration";
@ -261,14 +262,18 @@ export function getAppMenu(
label: "Documentation", label: "Documentation",
id: "documentation", id: "documentation",
click: async () => { click: async () => {
shell.openExternal(docsUrl); openBrowser(docsUrl).catch(error => {
logger.error("[MENU]: failed to open browser", { error });
});
}, },
}, },
{ {
label: "Support", label: "Support",
id: "support", id: "support",
click: async () => { click: async () => {
shell.openExternal(supportUrl); openBrowser(supportUrl).catch(error => {
logger.error("[MENU]: failed to open browser", { error });
});
}, },
}, },
...ignoreIf(isMac, [ ...ignoreIf(isMac, [

View File

@ -37,8 +37,8 @@ function checkHost<Query>(url: URLParse<Query>): boolean {
} }
interface Dependencies { interface Dependencies {
extensionLoader: ExtensionLoader extensionLoader: ExtensionLoader;
extensionsStore: ExtensionsStore extensionsStore: ExtensionsStore;
} }
export class LensProtocolRouterMain extends proto.LensProtocolRouter { export class LensProtocolRouterMain extends proto.LensProtocolRouter {

View File

@ -12,7 +12,7 @@ import type { Cluster } from "../../../common/cluster/cluster";
import type { ClusterId } from "../../../common/cluster-types"; import type { ClusterId } from "../../../common/cluster-types";
interface Dependencies { interface Dependencies {
authenticateRequest: (clusterId: ClusterId, tabId: string, shellToken: string) => boolean, authenticateRequest: (clusterId: ClusterId, tabId: string, shellToken: string) => boolean;
createShellSession: (args: { createShellSession: (args: {
webSocket: WebSocket; webSocket: WebSocket;

View File

@ -2,7 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { ExtendedMap } from "../../../../common/utils"; import { getOrInsertMap } from "../../../../common/utils";
import type { ClusterId } from "../../../../common/cluster-types"; import type { ClusterId } from "../../../../common/cluster-types";
import { ipcMainHandle } from "../../../../common/ipc"; import { ipcMainHandle } from "../../../../common/ipc";
import crypto from "crypto"; import crypto from "crypto";
@ -11,15 +11,14 @@ import { promisify } from "util";
const randomBytes = promisify(crypto.randomBytes); const randomBytes = promisify(crypto.randomBytes);
export class ShellRequestAuthenticator { export class ShellRequestAuthenticator {
private tokens = new ExtendedMap<ClusterId, Map<string, Uint8Array>>(); private tokens = new Map<ClusterId, Map<string, Uint8Array>>();
init() { init() {
ipcMainHandle("cluster:shell-api", async (event, clusterId, tabId) => { ipcMainHandle("cluster:shell-api", async (event, clusterId, tabId) => {
const authToken = Uint8Array.from(await randomBytes(128)); const authToken = Uint8Array.from(await randomBytes(128));
const forCluster = getOrInsertMap(this.tokens, clusterId);
this.tokens forCluster.set(tabId, authToken);
.getOrInsert(clusterId, () => new Map())
.set(tabId, authToken);
return authToken; return authToken;
}); });

View File

@ -8,8 +8,8 @@ import type net from "net";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
export interface ProxyApiRequestArgs { export interface ProxyApiRequestArgs {
req: http.IncomingMessage, req: http.IncomingMessage;
socket: net.Socket, socket: net.Socket;
head: Buffer, head: Buffer;
cluster: Cluster, cluster: Cluster;
} }

View File

@ -9,7 +9,7 @@ import type http from "http";
import path from "path"; import path from "path";
import { readFile } from "fs-extra"; import { readFile } from "fs-extra";
import type { Cluster } from "../common/cluster/cluster"; import type { Cluster } from "../common/cluster/cluster";
import { apiPrefix, appName, publicPath } from "../common/vars"; import { apiPrefix, appName, publicPath, isDevelopment, webpackDevServerPort } from "../common/vars";
import { HelmApiRoute, KubeconfigRoute, MetricsRoute, PortForwardRoute, ResourceApplierApiRoute, VersionRoute } from "./routes"; import { HelmApiRoute, KubeconfigRoute, MetricsRoute, PortForwardRoute, ResourceApplierApiRoute, VersionRoute } from "./routes";
import logger from "./logger"; import logger from "./logger";
@ -40,7 +40,7 @@ export interface LensApiRequest<P = any> {
query: URLSearchParams; query: URLSearchParams;
raw: { raw: {
req: http.IncomingMessage; req: http.IncomingMessage;
} };
} }
function getMimeType(filename: string) { function getMimeType(filename: string) {
@ -61,7 +61,7 @@ function getMimeType(filename: string) {
} }
interface Dependencies { interface Dependencies {
routePortForward: (request: LensApiRequest) => Promise<void> routePortForward: (request: LensApiRequest) => Promise<void>;
} }
export class Router { export class Router {
@ -110,7 +110,7 @@ export class Router {
}; };
} }
protected static async handleStaticFile({ params, response }: LensApiRequest): Promise<void> { protected static async handleStaticFile({ params, response, raw: { req }}: LensApiRequest): Promise<void> {
let filePath = params.path; let filePath = params.path;
for (let retryCount = 0; retryCount < 5; retryCount += 1) { for (let retryCount = 0; retryCount < 5; retryCount += 1) {
@ -124,6 +124,19 @@ export class Router {
} }
try { try {
const filename = path.basename(req.url);
// redirect requests to [appName].js, [appName].html /sockjs-node/ to webpack-dev-server (for hot-reload support)
const toWebpackDevServer = filename.includes(appName) || filename.includes("hot-update") || req.url.includes("sockjs-node");
if (isDevelopment && toWebpackDevServer) {
const redirectLocation = `http://localhost:${webpackDevServerPort}${req.url}`;
response.statusCode = 307;
response.setHeader("Location", redirectLocation);
return response.end();
}
const data = await readFile(asset); const data = await readFile(asset);
response.setHeader("Content-Type", getMimeType(asset)); response.setHeader("Content-Type", getMimeType(asset));

View File

@ -19,7 +19,7 @@ export interface PortForwardArgs {
} }
interface Dependencies { interface Dependencies {
getKubectlBinPath: (bundled: boolean) => Promise<string> getKubectlBinPath: (bundled: boolean) => Promise<string>;
} }
export class PortForward { export class PortForward {

View File

@ -133,7 +133,7 @@ export abstract class ShellSession {
protected abstract get cwd(): string | undefined; protected abstract get cwd(): string | undefined;
protected ensureShellProcess(shell: string, args: string[], env: Record<string, string>, cwd: string): { shellProcess: pty.IPty, resume: boolean } { protected ensureShellProcess(shell: string, args: string[], env: Record<string, string>, cwd: string): { shellProcess: pty.IPty; resume: boolean } {
const resume = ShellSession.processes.has(this.terminalId); const resume = ShellSession.processes.has(this.terminalId);
if (!resume) { if (!resume) {

View File

@ -7,8 +7,8 @@ export interface TrayMenuRegistration {
label?: string; label?: string;
click?: (menuItem: TrayMenuRegistration) => void; click?: (menuItem: TrayMenuRegistration) => void;
id?: string; id?: string;
type?: "normal" | "separator" | "submenu" type?: "normal" | "separator" | "submenu";
toolTip?: string; toolTip?: string;
enabled?: boolean; enabled?: boolean;
submenu?: TrayMenuRegistration[] submenu?: TrayMenuRegistration[];
} }

View File

@ -5,11 +5,11 @@
import type { ClusterId } from "../common/cluster-types"; import type { ClusterId } from "../common/cluster-types";
import { makeObservable, observable } from "mobx"; import { makeObservable, observable } from "mobx";
import { app, BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"; import { app, BrowserWindow, dialog, ipcMain, webContents } from "electron";
import windowStateKeeper from "electron-window-state"; import windowStateKeeper from "electron-window-state";
import { appEventBus } from "../common/app-event-bus/event-bus"; import { appEventBus } from "../common/app-event-bus/event-bus";
import { ipcMainOn } from "../common/ipc"; import { ipcMainOn } from "../common/ipc";
import { delay, iter, Singleton } from "../common/utils"; import { delay, iter, Singleton, openBrowser } from "../common/utils";
import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames"; import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames";
import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
import logger from "./logger"; import logger from "./logger";
@ -28,8 +28,6 @@ export interface SendToViewArgs {
} }
export class WindowManager extends Singleton { export class WindowManager extends Singleton {
public mainContentUrl = `http://localhost:${LensProxy.getInstance().port}`;
protected mainWindow: BrowserWindow; protected mainWindow: BrowserWindow;
protected splashWindow: BrowserWindow; protected splashWindow: BrowserWindow;
protected windowState: windowStateKeeper.State; protected windowState: windowStateKeeper.State;
@ -43,6 +41,10 @@ export class WindowManager extends Singleton {
this.bindEvents(); this.bindEvents();
} }
get mainUrl() {
return `http://localhost:${LensProxy.getInstance().port}`;
}
private async initMainWindow(showSplash: boolean) { private async initMainWindow(showSplash: boolean) {
// Manage main window size and position with state persistence // Manage main window size and position with state persistence
if (!this.windowState) { if (!this.windowState) {
@ -132,7 +134,9 @@ export class WindowManager extends Singleton {
webPreferences.nodeIntegration = false; webPreferences.nodeIntegration = false;
}) })
.setWindowOpenHandler((details) => { .setWindowOpenHandler((details) => {
shell.openExternal(details.url); openBrowser(details.url).catch(error => {
logger.error("[WINDOW-MANAGER]: failed to open browser", { error });
});
return { action: "deny" }; return { action: "deny" };
}); });
@ -140,8 +144,8 @@ export class WindowManager extends Singleton {
try { try {
if (showSplash) await this.showSplash(); if (showSplash) await this.showSplash();
logger.info(`[WINDOW-MANAGER]: Loading Main window from url: ${this.mainContentUrl} ...`); logger.info(`[WINDOW-MANAGER]: Loading Main window from url: ${this.mainUrl} ...`);
await this.mainWindow.loadURL(this.mainContentUrl); await this.mainWindow.loadURL(this.mainUrl);
} catch (error) { } catch (error) {
logger.error("Loading main window failed", { error }); logger.error("Loading main window failed", { error });
dialog.showErrorBox("ERROR!", error.toString()); dialog.showErrorBox("ERROR!", error.toString());

View File

@ -75,7 +75,7 @@ function mergeClusterModel(prev: ClusterModel, right: Omit<ClusterModel, "id">):
}; };
} }
function moveStorageFolder({ folder, newId, oldId }: { folder: string, newId: string, oldId: string }): void { function moveStorageFolder({ folder, newId, oldId }: { folder: string; newId: string; oldId: string }): void {
const oldPath = path.resolve(folder, `${oldId}.json`); const oldPath = path.resolve(folder, `${oldId}.json`);
const newPath = path.resolve(folder, `${newId}.json`); const newPath = path.resolve(folder, `${newId}.json`);

View File

@ -5,7 +5,7 @@
import type Conf from "conf"; import type Conf from "conf";
import type { Migrations } from "conf/dist/source/types"; import type { Migrations } from "conf/dist/source/types";
import { ExtendedMap, iter } from "../common/utils"; import { getOrInsert, iter } from "../common/utils";
import { isTestEnv } from "../common/vars"; import { isTestEnv } from "../common/vars";
export function migrationLog(...args: any[]) { export function migrationLog(...args: any[]) {
@ -15,15 +15,15 @@ export function migrationLog(...args: any[]) {
} }
export interface MigrationDeclaration { export interface MigrationDeclaration {
version: string, version: string;
run(store: Conf<any>): void; run(store: Conf<any>): void;
} }
export function joinMigrations(...declarations: MigrationDeclaration[]): Migrations<any> { export function joinMigrations(...declarations: MigrationDeclaration[]): Migrations<any> {
const migrations = new ExtendedMap<string, ((store: Conf<any>) => void)[]>(); const migrations = new Map<string, ((store: Conf<any>) => void)[]>();
for (const decl of declarations) { for (const decl of declarations) {
migrations.getOrInsert(decl.version, () => []).push(decl.run); getOrInsert(migrations, decl.version, []).push(decl.run);
} }
return Object.fromEntries( return Object.fromEntries(

View File

@ -5,6 +5,6 @@
import { createHash } from "crypto"; import { createHash } from "crypto";
export function generateNewIdFor(cluster: { kubeConfigPath: string, contextName: string }): string { export function generateNewIdFor(cluster: { kubeConfigPath: string; contextName: string }): string {
return createHash("md5").update(`${cluster.kubeConfigPath}:${cluster.contextName}`).digest("hex"); return createHash("md5").update(`${cluster.kubeConfigPath}:${cluster.contextName}`).digest("hex");
} }

View File

@ -9,7 +9,7 @@ import { CatalogCategory, CatalogEntity, CatalogEntityData, catalogCategoryRegis
import "../../common/catalog-entities"; import "../../common/catalog-entities";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import { ClusterStore } from "../../common/cluster-store/cluster-store"; import { ClusterStore } from "../../common/cluster-store/cluster-store";
import { type Disposer, iter } from "../utils"; import { Disposer, iter } from "../utils";
import { once } from "lodash"; import { once } from "lodash";
import logger from "../../common/logger"; import logger from "../../common/logger";
import { CatalogRunEvent } from "../../common/catalog/catalog-run-event"; import { CatalogRunEvent } from "../../common/catalog/catalog-run-event";

View File

@ -21,19 +21,19 @@ export enum TerminalChannels {
} }
export type TerminalMessage = { export type TerminalMessage = {
type: TerminalChannels.STDIN, type: TerminalChannels.STDIN;
data: string, data: string;
} | { } | {
type: TerminalChannels.STDOUT, type: TerminalChannels.STDOUT;
data: string, data: string;
} | { } | {
type: TerminalChannels.CONNECTED type: TerminalChannels.CONNECTED;
} | { } | {
type: TerminalChannels.RESIZE, type: TerminalChannels.RESIZE;
data: { data: {
width: number, width: number;
height: number, height: number;
}, };
}; };
enum TerminalColor { enum TerminalColor {

View File

@ -57,7 +57,7 @@ export enum WebSocketApiState {
} }
export interface WebSocketEvents { export interface WebSocketEvents {
open: () => void, open: () => void;
data: (message: string) => void; data: (message: string) => void;
close: () => void; close: () => void;
} }
@ -65,7 +65,7 @@ export interface WebSocketEvents {
type Defaulted<Params, DefaultParams extends keyof Params> = Required<Pick<Params, DefaultParams>> & Omit<Params, DefaultParams>; type Defaulted<Params, DefaultParams extends keyof Params> = Required<Pick<Params, DefaultParams>> & Omit<Params, DefaultParams>;
export class WebSocketApi<Events extends WebSocketEvents> extends (EventEmitter as { new<T>(): TypedEventEmitter<T> })<Events> { export class WebSocketApi<Events extends WebSocketEvents> extends (EventEmitter as { new<T>(): TypedEventEmitter<T> })<Events> {
protected socket: WebSocket; protected socket?: WebSocket | null;
protected pendingCommands: (string | ArrayBufferLike | Blob | ArrayBufferView)[] = []; protected pendingCommands: (string | ArrayBufferLike | Blob | ArrayBufferView)[] = [];
protected reconnectTimer?: any; protected reconnectTimer?: any;
protected pingTimer?: any; protected pingTimer?: any;
@ -181,7 +181,7 @@ export class WebSocketApi<Events extends WebSocketEvents> extends (EventEmitter
if (error) { if (error) {
const { reconnectDelay } = this.params; const { reconnectDelay } = this.params;
if (reconnectDelay) { if (reconnectDelay && this.socket) {
const url = this.socket.url; const url = this.socket.url;
this.writeLog("will reconnect in", `${reconnectDelay}s`); this.writeLog("will reconnect in", `${reconnectDelay}s`);

View File

@ -136,14 +136,7 @@ export async function bootstrap(di: DependencyInjectionContainer) {
App = (await import("./frames/cluster-frame/cluster-frame")).ClusterFrame; App = (await import("./frames/cluster-frame/cluster-frame")).ClusterFrame;
} }
try { await initializeApp(rootElem);
await initializeApp(rootElem);
} catch (error) {
console.error(`[BOOTSTRAP]: view initialization error: ${error}`, {
origin: location.href,
isTopFrameView: process.isMainFrame,
});
}
render( render(
<DiContextProvider value={{ di }}> <DiContextProvider value={{ di }}>

View File

@ -9,7 +9,7 @@ import type { ClusterContext } from "../../common/k8s-api/cluster-context";
import { computed, makeObservable } from "mobx"; import { computed, makeObservable } from "mobx";
interface Dependencies { interface Dependencies {
namespaceStore: NamespaceStore namespaceStore: NamespaceStore;
} }
export class ClusterFrameContext implements ClusterContext { export class ClusterFrameContext implements ClusterContext {

View File

@ -34,7 +34,7 @@ interface Option {
} }
interface Dependencies { interface Dependencies {
getCustomKubeConfigDirectory: (directoryName: string) => string getCustomKubeConfigDirectory: (directoryName: string) => string;
} }
function getContexts(config: KubeConfig): Map<string, Option> { function getContexts(config: KubeConfig): Map<string, Option> {

View File

@ -16,7 +16,7 @@ import { navigate } from "../../navigation";
import { catalogCategoryRegistry } from "../../api/catalog-category-registry"; import { catalogCategoryRegistry } from "../../api/catalog-category-registry";
export type CatalogAddButtonProps = { export type CatalogAddButtonProps = {
category: CatalogCategory category: CatalogCategory;
}; };
type CategoryId = string; type CategoryId = string;

View File

@ -25,7 +25,7 @@ interface Props<T extends CatalogEntity> {
@observer @observer
export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Props<T>> { export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Props<T>> {
categoryIcon(category: CatalogCategory) { categoryIcon(category: CatalogCategory) {
if (Icon.isSvg(category.metadata.icon)) { if (category.metadata.icon.includes("<svg")) {
return <Icon svg={category.metadata.icon} smallest />; return <Icon svg={category.metadata.icon} smallest />;
} else { } else {
return <Icon material={category.metadata.icon} smallest />; return <Icon material={category.metadata.icon} smallest />;

View File

@ -65,7 +65,7 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
continue; continue;
} }
const key = Icon.isSvg(menuItem.icon) ? "svg" : "material"; const key = menuItem.icon.includes("<svg") ? "svg" : "material";
items.push( items.push(
<MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem)}> <MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem)}>

View File

@ -11,7 +11,7 @@ import { CatalogCategory, catalogCategoryRegistry } from "../../../../common/cat
import { autoBind, disposer } from "../../../../common/utils"; import { autoBind, disposer } from "../../../../common/utils";
interface Dependencies { interface Dependencies {
registry: CatalogEntityRegistry registry: CatalogEntityRegistry;
} }
export class CatalogEntityStore extends ItemStore<CatalogEntity> { export class CatalogEntityStore extends ItemStore<CatalogEntity> {

View File

@ -28,7 +28,7 @@ function getCategoryIcon(category: CatalogCategory) {
const { icon } = category.metadata ?? {}; const { icon } = category.metadata ?? {};
if (typeof icon === "string") { if (typeof icon === "string") {
return Icon.isSvg(icon) return icon.includes("<svg")
? <Icon small svg={icon}/> ? <Icon small svg={icon}/>
: <Icon small material={icon}/>; : <Icon small material={icon}/>;
} }

View File

@ -9,7 +9,7 @@ import { MenuItem } from "../menu";
import type { CatalogEntity } from "../../api/catalog-entity"; import type { CatalogEntity } from "../../api/catalog-entity";
export function HotbarToggleMenuItem(props: { entity: CatalogEntity, addContent: ReactNode, removeContent: ReactNode }) { export function HotbarToggleMenuItem(props: { entity: CatalogEntity; addContent: ReactNode; removeContent: ReactNode }) {
const store = HotbarStore.getInstance(); const store = HotbarStore.getInstance();
const [itemInHotbar, setItemInHotbar] = useState(store.isAddedToActive(props.entity)); const [itemInHotbar, setItemInHotbar] = useState(store.isAddedToActive(props.entity));

View File

@ -13,7 +13,7 @@ import clusterOverviewStoreInjectable from "./cluster-overview-store/cluster-ove
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
interface Dependencies { interface Dependencies {
clusterOverviewStore: ClusterOverviewStore clusterOverviewStore: ClusterOverviewStore;
} }
const NonInjectedClusterMetricSwitchers = observer(({ clusterOverviewStore }: Dependencies) => { const NonInjectedClusterMetricSwitchers = observer(({ clusterOverviewStore }: Dependencies) => {

View File

@ -21,7 +21,7 @@ import clusterOverviewStoreInjectable
from "./cluster-overview-store/cluster-overview-store.injectable"; from "./cluster-overview-store/cluster-overview-store.injectable";
interface Dependencies { interface Dependencies {
clusterOverviewStore: ClusterOverviewStore clusterOverviewStore: ClusterOverviewStore;
} }
const NonInjectedClusterMetrics = observer(({ clusterOverviewStore: { metricType, metricNodeRole, getMetricsValues, metricsLoaded, metrics }}: Dependencies) => { const NonInjectedClusterMetrics = observer(({ clusterOverviewStore: { metricType, metricNodeRole, getMetricsValues, metricsLoaded, metrics }}: Dependencies) => {

View File

@ -5,9 +5,9 @@
import { action, observable, reaction, when, makeObservable } from "mobx"; import { action, observable, reaction, when, makeObservable } from "mobx";
import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
import { Cluster, clusterApi, getMetricsByNodeNames, type IClusterMetrics } from "../../../../common/k8s-api/endpoints"; import { Cluster, clusterApi, getMetricsByNodeNames, IClusterMetrics } from "../../../../common/k8s-api/endpoints";
import { autoBind, StorageHelper } from "../../../utils"; import { autoBind, StorageHelper } from "../../../utils";
import { type IMetricsReqParams, normalizeMetrics } from "../../../../common/k8s-api/endpoints/metrics.api"; import { IMetricsReqParams, normalizeMetrics } from "../../../../common/k8s-api/endpoints/metrics.api";
import { nodesStore } from "../../+nodes/nodes.store"; import { nodesStore } from "../../+nodes/nodes.store";
export enum MetricType { export enum MetricType {
@ -22,11 +22,11 @@ export enum MetricNodeRole {
export interface ClusterOverviewStorageState { export interface ClusterOverviewStorageState {
metricType: MetricType; metricType: MetricType;
metricNodeRole: MetricNodeRole, metricNodeRole: MetricNodeRole;
} }
interface Dependencies { interface Dependencies {
storage: StorageHelper<ClusterOverviewStorageState> storage: StorageHelper<ClusterOverviewStorageState>;
} }
export class ClusterOverviewStore extends KubeObjectStore<Cluster> implements ClusterOverviewStorageState { export class ClusterOverviewStore extends KubeObjectStore<Cluster> implements ClusterOverviewStorageState {

View File

@ -28,8 +28,8 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
import clusterOverviewStoreInjectable from "./cluster-overview-store/cluster-overview-store.injectable"; import clusterOverviewStoreInjectable from "./cluster-overview-store/cluster-overview-store.injectable";
interface Dependencies { interface Dependencies {
subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer, subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer;
clusterOverviewStore: ClusterOverviewStore clusterOverviewStore: ClusterOverviewStore;
} }
@observer @observer

View File

@ -24,7 +24,7 @@ function createLabels(rawLabelData: [string, number | undefined][]): string[] {
} }
interface Dependencies { interface Dependencies {
clusterOverviewStore: ClusterOverviewStore clusterOverviewStore: ClusterOverviewStore;
} }
const NonInjectedClusterPieCharts = observer(({ clusterOverviewStore }: Dependencies) => { const NonInjectedClusterPieCharts = observer(({ clusterOverviewStore }: Dependencies) => {

View File

@ -27,7 +27,7 @@ interface Props extends KubeObjectDetailsProps<Secret> {
export class SecretDetails extends React.Component<Props> { export class SecretDetails extends React.Component<Props> {
@observable isSaving = false; @observable isSaving = false;
@observable data: { [name: string]: string } = {}; @observable data: { [name: string]: string } = {};
revealSecret = new Set<string>(); @observable revealSecret = observable.set<string>();
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);

View File

@ -23,7 +23,7 @@ export interface KubeEventDetailsProps {
} }
interface Dependencies { interface Dependencies {
subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer;
} }
@observer @observer

View File

@ -22,7 +22,7 @@ export interface ExtensionInfo {
interface Dependencies { interface Dependencies {
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>; attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
getBaseRegistryUrl: () => Promise<string>; getBaseRegistryUrl: () => Promise<string>;
extensionInstallationStateStore: ExtensionInstallationStateStore extensionInstallationStateStore: ExtensionInstallationStateStore;
} }
export const attemptInstallByInfo = ({ attemptInstall, getBaseRegistryUrl, extensionInstallationStateStore }: Dependencies) => async ({ export const attemptInstallByInfo = ({ attemptInstall, getBaseRegistryUrl, extensionInstallationStateStore }: Dependencies) => async ({

View File

@ -34,9 +34,9 @@ interface Dependencies {
installRequest: InstallRequest, installRequest: InstallRequest,
) => Promise<InstallRequestValidated | null>; ) => Promise<InstallRequestValidated | null>;
getExtensionDestFolder: (name: string) => string getExtensionDestFolder: (name: string) => string;
extensionInstallationStateStore: ExtensionInstallationStateStore extensionInstallationStateStore: ExtensionInstallationStateStore;
} }
export const attemptInstall = export const attemptInstall =

View File

@ -26,7 +26,7 @@ export interface InstallRequestValidated {
} }
interface Dependencies { interface Dependencies {
extensionDiscovery: ExtensionDiscovery extensionDiscovery: ExtensionDiscovery;
} }
export const createTempFilesAndValidate = export const createTempFilesAndValidate =

View File

@ -16,9 +16,9 @@ import React from "react";
import type { ExtensionInstallationStateStore } from "../../../../../extensions/extension-installation-state-store/extension-installation-state-store"; import type { ExtensionInstallationStateStore } from "../../../../../extensions/extension-installation-state-store/extension-installation-state-store";
interface Dependencies { interface Dependencies {
extensionLoader: ExtensionLoader extensionLoader: ExtensionLoader;
getExtensionDestFolder: (name: string) => string getExtensionDestFolder: (name: string) => string;
extensionInstallationStateStore: ExtensionInstallationStateStore extensionInstallationStateStore: ExtensionInstallationStateStore;
} }
export const unpackExtension = export const unpackExtension =

Some files were not shown because too many files have changed in this diff Show More