mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Cherry-pick from 4.2.3 (#2628)
* Fix: logs data disapearing causing crashes (#2566) Signed-off-by: Sebastian Malton <sebastian@malton.name> * Refactor helm-chart.api and improve kube validation and error handling (#2265) Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix: HPA's not sortable by age (#2565) Signed-off-by: Sebastian Malton <sebastian@malton.name> * Conditionally render status icon for kube meta (#2298) Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix custom resource loading spinner appears above extensions' cluster menus (#2344) Signed-off-by: Sebastian Malton <sebastian@malton.name> * Lens should point to the release docs (#2268) Signed-off-by: Sebastian Malton <sebastian@malton.name> * Refactor the Extensions settings page (#2221) Signed-off-by: Sebastian Malton <sebastian@malton.name> * try and get jest to not core dump Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
7fde8125eb
commit
1f854d0a0f
42 files changed
+1777
-1067
No files matched your search
@@ -43,13 +43,17 @@ jest.mock("electron", () => {
|
||||
},
|
||||
ipcMain: {
|
||||
handle: jest.fn(),
|
||||
on: jest.fn()
|
||||
on: jest.fn(),
|
||||
removeAllListeners: jest.fn(),
|
||||
off: jest.fn(),
|
||||
send: jest.fn(),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
describe("empty config", () => {
|
||||
beforeEach(async () => {
|
||||
ClusterStore.getInstance(false)?.unregisterIpcListener();
|
||||
ClusterStore.resetInstance();
|
||||
const mockOpts = {
|
||||
"tmp": {
|
||||
|
||||
@@ -124,8 +124,8 @@ export abstract class BaseStore<T = any> extends Singleton {
|
||||
}
|
||||
|
||||
unregisterIpcListener() {
|
||||
ipcRenderer.removeAllListeners(this.syncMainChannel);
|
||||
ipcRenderer.removeAllListeners(this.syncRendererChannel);
|
||||
ipcRenderer?.removeAllListeners(this.syncMainChannel);
|
||||
ipcRenderer?.removeAllListeners(this.syncRendererChannel);
|
||||
}
|
||||
|
||||
disableSync() {
|
||||
@@ -167,7 +167,7 @@ export abstract class BaseStore<T = any> extends Singleton {
|
||||
|
||||
/**
|
||||
* toJSON is called when syncing the store to the filesystem. It should
|
||||
* produce a JSON serializable object representaion of the current state.
|
||||
* produce a JSON serializable object representation of the current state.
|
||||
*
|
||||
* It is recommended that a round trip is valid. Namely, calling
|
||||
* `this.fromStore(this.toJSON())` shouldn't change the state.
|
||||
|
||||
+16
-11
@@ -1,5 +1,5 @@
|
||||
import path from "path";
|
||||
import { app, ipcRenderer, remote, webFrame } from "electron";
|
||||
import { app, ipcMain, ipcRenderer, remote, webFrame } from "electron";
|
||||
import { unlink } from "fs-extra";
|
||||
import { action, comparer, computed, observable, reaction, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
@@ -12,6 +12,7 @@ import { saveToAppFiles } from "./utils/saveToAppFiles";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
||||
import { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting";
|
||||
import { disposer, noop } from "./utils";
|
||||
|
||||
export interface ClusterIconUpload {
|
||||
clusterId: string;
|
||||
@@ -111,6 +112,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
|
||||
private static stateRequestChannel = "cluster:states";
|
||||
protected disposer = disposer();
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
@@ -143,7 +145,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
cluster.setState(clusterState.state);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
} else if (ipcMain) {
|
||||
handleRequest(ClusterStore.stateRequestChannel, (): clusterStateSync[] => {
|
||||
const states: clusterStateSync[] = [];
|
||||
|
||||
@@ -160,13 +162,16 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
}
|
||||
|
||||
protected pushStateToViewsAutomatically() {
|
||||
if (!ipcRenderer) {
|
||||
reaction(() => this.enabledClustersList, () => {
|
||||
this.pushState();
|
||||
});
|
||||
reaction(() => this.connectedClustersList, () => {
|
||||
this.pushState();
|
||||
});
|
||||
if (ipcMain) {
|
||||
this.disposer.push(
|
||||
reaction(() => this.enabledClustersList, () => {
|
||||
this.pushState();
|
||||
}),
|
||||
reaction(() => this.connectedClustersList, () => {
|
||||
this.pushState();
|
||||
}),
|
||||
() => unsubscribeAllFromBroadcast("cluster:state"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +185,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
unregisterIpcListener() {
|
||||
super.unregisterIpcListener();
|
||||
unsubscribeAllFromBroadcast("cluster:state");
|
||||
this.disposer();
|
||||
}
|
||||
|
||||
pushState() {
|
||||
@@ -288,7 +293,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
// remove only custom kubeconfigs (pasted as text)
|
||||
if (cluster.kubeConfigPath == ClusterStore.getCustomKubeConfigPath(clusterId)) {
|
||||
unlink(cluster.kubeConfigPath).catch(() => null);
|
||||
await unlink(cluster.kubeConfigPath).catch(noop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function broadcastMessage(channel: string, ...args: any[]) {
|
||||
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.send(channel, ...args);
|
||||
} else {
|
||||
} else if (ipcMain) {
|
||||
ipcMain.emit(channel, ...args);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export async function broadcastMessage(channel: string, ...args: any[]) {
|
||||
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.on(channel, listener);
|
||||
} else {
|
||||
} else if (ipcMain) {
|
||||
ipcMain.on(channel, listener);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function subscribeToBroadcast(channel: string, listener: (...args: any[])
|
||||
export function unsubscribeFromBroadcast(channel: string, listener: (...args: any[]) => any) {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.off(channel, listener);
|
||||
} else {
|
||||
} else if (ipcMain) {
|
||||
ipcMain.off(channel, listener);
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export function unsubscribeFromBroadcast(channel: string, listener: (...args: an
|
||||
export function unsubscribeAllFromBroadcast(channel: string) {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.removeAllListeners(channel);
|
||||
} else {
|
||||
} else if (ipcMain) {
|
||||
ipcMain.removeAllListeners(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ export const ProtocolHandlerExtension= `${ProtocolHandlerIpcPrefix}:extension`;
|
||||
* Though under the current (2021/01/18) implementation, these are never matched
|
||||
* against in the final matching so their names are less of a concern.
|
||||
*/
|
||||
const EXTENSION_PUBLISHER_MATCH = "LENS_INTERNAL_EXTENSION_PUBLISHER_MATCH";
|
||||
const EXTENSION_NAME_MATCH = "LENS_INTERNAL_EXTENSION_NAME_MATCH";
|
||||
export const EXTENSION_PUBLISHER_MATCH = "LENS_INTERNAL_EXTENSION_PUBLISHER_MATCH";
|
||||
export const EXTENSION_NAME_MATCH = "LENS_INTERNAL_EXTENSION_NAME_MATCH";
|
||||
|
||||
export abstract class LensProtocolRouter extends Singleton {
|
||||
// Map between path schemas and the handlers
|
||||
@@ -32,7 +32,7 @@ export abstract class LensProtocolRouter extends Singleton {
|
||||
|
||||
public static readonly LoggingPrefix = "[PROTOCOL ROUTER]";
|
||||
|
||||
protected static readonly ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}(\@[A-Za-z0-9_]+)?/:${EXTENSION_NAME_MATCH}`;
|
||||
static readonly ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}(\@[A-Za-z0-9_]+)?/:${EXTENSION_NAME_MATCH}`;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export type Disposer = () => void;
|
||||
|
||||
interface Extendable<T> {
|
||||
push(...vals: T[]): void;
|
||||
}
|
||||
|
||||
export type ExtendableDisposer = Disposer & Extendable<Disposer>;
|
||||
|
||||
export function disposer(...args: Disposer[]): ExtendableDisposer {
|
||||
const res = () => {
|
||||
args.forEach(dispose => dispose?.());
|
||||
args.length = 0;
|
||||
};
|
||||
|
||||
res.push = (...vals: Disposer[]) => {
|
||||
args.push(...vals);
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -6,13 +6,13 @@ export interface DownloadFileOptions {
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface DownloadFileTicket {
|
||||
export interface DownloadFileTicket<T> {
|
||||
url: string;
|
||||
promise: Promise<Buffer>;
|
||||
promise: Promise<T>;
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
export function downloadFile({ url, timeout, gzip = true }: DownloadFileOptions): DownloadFileTicket {
|
||||
export function downloadFile({ url, timeout, gzip = true }: DownloadFileOptions): DownloadFileTicket<Buffer> {
|
||||
const fileChunks: Buffer[] = [];
|
||||
const req = request(url, { gzip, timeout });
|
||||
const promise: Promise<Buffer> = new Promise((resolve, reject) => {
|
||||
@@ -35,3 +35,12 @@ export function downloadFile({ url, timeout, gzip = true }: DownloadFileOptions)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function downloadJson(args: DownloadFileOptions): DownloadFileTicket<any> {
|
||||
const { promise, ...rest } = downloadFile(args);
|
||||
|
||||
return {
|
||||
promise: promise.then(res => JSON.parse(res.toString())),
|
||||
...rest
|
||||
};
|
||||
}
|
||||
@@ -19,6 +19,8 @@ export * from "./downloadFile";
|
||||
export * from "./escapeRegExp";
|
||||
export * from "./tar";
|
||||
export * from "./type-narrowing";
|
||||
export * from "./disposer";
|
||||
|
||||
import * as iter from "./iter";
|
||||
|
||||
export { iter };
|
||||
@@ -1,13 +1,89 @@
|
||||
/**
|
||||
* Narrows `val` to include the property `key` (if true is returned)
|
||||
* @param val The object to be tested
|
||||
* @param key The key to test if it is present on the object
|
||||
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||
*/
|
||||
export function hasOwnProperty<V extends object, K extends PropertyKey>(val: V, key: K): val is (V & { [key in K]: unknown }) {
|
||||
export function hasOwnProperty<S extends object, K extends PropertyKey>(val: S, key: K): val is (S & { [key in K]: unknown }) {
|
||||
// this call syntax is for when `val` was created by `Object.create(null)`
|
||||
return Object.prototype.hasOwnProperty.call(val, key);
|
||||
}
|
||||
|
||||
export function hasOwnProperties<V extends object, K extends PropertyKey>(val: V, ...keys: K[]): val is (V & { [key in K]: unknown}) {
|
||||
/**
|
||||
* Narrows `val` to a static type that includes fields of names in `keys`
|
||||
* @param val the value that we are trying to type narrow
|
||||
* @param keys the key names (must be literals for tsc to do any meaningful typing)
|
||||
*/
|
||||
export function hasOwnProperties<S extends object, K extends PropertyKey>(val: S, ...keys: K[]): val is (S & { [key in K]: unknown }) {
|
||||
return keys.every(key => hasOwnProperty(val, key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows `val` to include the property `key` with type `V`
|
||||
* @param val the value that we are trying to type narrow
|
||||
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||
* @param isValid a function to check if the field is valid
|
||||
*/
|
||||
export function hasTypedProperty<S extends object, K extends PropertyKey, V>(val: S, key: K, isValid: (value: unknown) => value is V): val is (S & { [key in K]: V }) {
|
||||
return hasOwnProperty(val, key) && isValid(val[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows `val` to include the property `key` with type `V | undefined` or doesn't contain it
|
||||
* @param val the value that we are trying to type narrow
|
||||
* @param key The key to test if it is present on the object (must be a literal for tsc to do any meaningful typing)
|
||||
* @param isValid a function to check if the field (when present) is valid
|
||||
*/
|
||||
export function hasOptionalProperty<S extends object, K extends PropertyKey, V>(val: S, key: K, isValid: (value: unknown) => value is V): val is (S & { [key in K]?: V }) {
|
||||
if (hasOwnProperty(val, key)) {
|
||||
return typeof val[key] === "undefined" || isValid(val[key]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* isRecord checks if `val` matches the signature `Record<T, V>` or `{ [label in T]: V }`
|
||||
* @param val The value to be checked
|
||||
* @param isKey a function for checking if the key is of the correct type
|
||||
* @param isValue a function for checking if a value is of the correct type
|
||||
*/
|
||||
export function isRecord<T extends PropertyKey, V>(val: unknown, isKey: (key: unknown) => key is T, isValue: (value: unknown) => value is V): val is Record<T, V> {
|
||||
return isObject(val) && Object.entries(val).every(([key, value]) => isKey(key) && isValue(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* isTypedArray checks if `val` is an array and all of its entries are of type `T`
|
||||
* @param val The value to be checked
|
||||
* @param isEntry a function for checking if an entry is the correct type
|
||||
*/
|
||||
export function isTypedArray<T>(val: unknown, isEntry: (entry: unknown) => entry is T): val is T[] {
|
||||
return Array.isArray(val) && val.every(isEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if val is of type string
|
||||
* @param val the value to be checked
|
||||
*/
|
||||
export function isString(val: unknown): val is string {
|
||||
return typeof val === "string";
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if val is of type object and isn't null
|
||||
* @param val the value to be checked
|
||||
*/
|
||||
export function isObject(val: unknown): val is object {
|
||||
return typeof val === "object" && val !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new predicate function (with the same predicate) from `fn`. Such
|
||||
* that it can be called with just the value to be tested.
|
||||
*
|
||||
* This is useful for when using `hasOptionalProperty` and `hasTypedProperty`
|
||||
* @param fn A typescript user predicate function to be bound
|
||||
* @param boundArgs the set of arguments to be passed to `fn` in the new function
|
||||
*/
|
||||
export function bindPredicate<FnArgs extends any[], T>(fn: (arg1: unknown, ...args: FnArgs) => arg1 is T, ...boundArgs: FnArgs): (arg1: unknown) => arg1 is T {
|
||||
return (arg1: unknown): arg1 is T => fn(arg1, ...boundArgs);
|
||||
}
|
||||
+8
-1
@@ -1,5 +1,6 @@
|
||||
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
||||
import path from "path";
|
||||
import { SemVer } from "semver";
|
||||
import packageInfo from "../../package.json";
|
||||
import { defineGlobal } from "./utils/defineGlobal";
|
||||
|
||||
@@ -44,5 +45,11 @@ export const apiKubePrefix = "/api-kube"; // k8s cluster apis
|
||||
// Links
|
||||
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues";
|
||||
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI";
|
||||
export const docsUrl = "https://docs.k8slens.dev/";
|
||||
export const supportUrl = "https://docs.k8slens.dev/latest/support/";
|
||||
|
||||
// This explicitly ignores the prerelease info on the package version
|
||||
const { major, minor, patch } = new SemVer(packageInfo.version);
|
||||
const mmpVersion = [major, minor, patch].join(".");
|
||||
const docsVersion = isProduction ? `v${mmpVersion}` : "latest";
|
||||
|
||||
export const docsUrl = `https://docs.k8slens.dev/${docsVersion}`;
|
||||
Reference in new issue
Block a user