mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into fix/namespace-filter-jumping-scroll
This commit is contained in:
commit
8b81442edd
17
.dependabot/config.yml
Normal file
17
.dependabot/config.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# See https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
# for config options
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
open-pull-requests-limit: 4
|
||||||
|
reviewers:
|
||||||
|
- "lensapp/lens-maintainers"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
versioning-strategy:
|
||||||
|
lockfile-only: false
|
||||||
|
increase: true
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 23 KiB |
BIN
build/icons/512x512@2x.png
Normal file
BIN
build/icons/512x512@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@ -43,6 +43,7 @@ You can use theme-based CSS Variables to style an extension according to the act
|
|||||||
## Button Colors
|
## Button Colors
|
||||||
- `--buttonPrimaryBackground`: button background color for primary actions.
|
- `--buttonPrimaryBackground`: button background color for primary actions.
|
||||||
- `--buttonDefaultBackground`: default button background color.
|
- `--buttonDefaultBackground`: default button background color.
|
||||||
|
- `--buttonLightBackground`: light button background color.
|
||||||
- `--buttonAccentBackground`: accent button background color.
|
- `--buttonAccentBackground`: accent button background color.
|
||||||
- `--buttonDisabledBackground`: disabled button background color.
|
- `--buttonDisabledBackground`: disabled button background color.
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "kontena-lens",
|
"name": "kontena-lens",
|
||||||
"productName": "Lens",
|
"productName": "Lens",
|
||||||
"description": "Lens - The Kubernetes IDE",
|
"description": "Lens - The Kubernetes IDE",
|
||||||
"version": "4.1.0-alpha.2",
|
"version": "4.1.0-beta.1",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2020, Mirantis, Inc.",
|
"copyright": "© 2020, Mirantis, Inc.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -16,7 +16,7 @@
|
|||||||
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
|
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
|
||||||
"dev:main": "yarn run compile:main --watch",
|
"dev:main": "yarn run compile:main --watch",
|
||||||
"dev:renderer": "yarn run webpack-dev-server --config webpack.renderer.ts",
|
"dev:renderer": "yarn run webpack-dev-server --config webpack.renderer.ts",
|
||||||
"dev:extension-types": "yarn run compile:extension-types --watch",
|
"dev:extension-types": "yarn run compile:extension-types --watch --progress",
|
||||||
"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",
|
||||||
|
|||||||
126
src/common/ipc/__tests__/type-enforced-ipc.test.ts
Normal file
126
src/common/ipc/__tests__/type-enforced-ipc.test.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { EventEmitter } from "events";
|
||||||
|
import { onCorrect, onceCorrect } from "../type-enforced-ipc";
|
||||||
|
|
||||||
|
describe("type enforced ipc tests", () => {
|
||||||
|
describe("onCorrect tests", () => {
|
||||||
|
it("should call the handler if the args are valid", () => {
|
||||||
|
let called = false;
|
||||||
|
const source = new EventEmitter();
|
||||||
|
const listener = () => called = true;
|
||||||
|
const verifier = (args: unknown[]): args is [] => true;
|
||||||
|
const channel = "foobar";
|
||||||
|
|
||||||
|
onCorrect({ source, listener, verifier, channel });
|
||||||
|
|
||||||
|
source.emit(channel);
|
||||||
|
expect(called).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not call the handler if the args are not valid", () => {
|
||||||
|
let called = false;
|
||||||
|
const source = new EventEmitter();
|
||||||
|
const listener = () => called = true;
|
||||||
|
const verifier = (args: unknown[]): args is [] => false;
|
||||||
|
const channel = "foobar";
|
||||||
|
|
||||||
|
onCorrect({ source, listener, verifier, channel });
|
||||||
|
|
||||||
|
source.emit(channel);
|
||||||
|
expect(called).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call the handler twice if the args are valid on two emits", () => {
|
||||||
|
let called = 0;
|
||||||
|
const source = new EventEmitter();
|
||||||
|
const listener = () => called += 1;
|
||||||
|
const verifier = (args: unknown[]): args is [] => true;
|
||||||
|
const channel = "foobar";
|
||||||
|
|
||||||
|
onCorrect({ source, listener, verifier, channel });
|
||||||
|
|
||||||
|
source.emit(channel);
|
||||||
|
source.emit(channel);
|
||||||
|
expect(called).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call the handler twice if the args are [valid, invalid, valid]", () => {
|
||||||
|
let called = 0;
|
||||||
|
const source = new EventEmitter();
|
||||||
|
const listener = () => called += 1;
|
||||||
|
const results = [true, false, true];
|
||||||
|
const verifier = (args: unknown[]): args is [] => results.pop();
|
||||||
|
const channel = "foobar";
|
||||||
|
|
||||||
|
onCorrect({ source, listener, verifier, channel });
|
||||||
|
|
||||||
|
source.emit(channel);
|
||||||
|
source.emit(channel);
|
||||||
|
source.emit(channel);
|
||||||
|
expect(called).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("onceCorrect tests", () => {
|
||||||
|
it("should call the handler if the args are valid", () => {
|
||||||
|
let called = false;
|
||||||
|
const source = new EventEmitter();
|
||||||
|
const listener = () => called = true;
|
||||||
|
const verifier = (args: unknown[]): args is [] => true;
|
||||||
|
const channel = "foobar";
|
||||||
|
|
||||||
|
onceCorrect({ source, listener, verifier, channel });
|
||||||
|
|
||||||
|
source.emit(channel);
|
||||||
|
expect(called).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not call the handler if the args are not valid", () => {
|
||||||
|
let called = false;
|
||||||
|
const source = new EventEmitter();
|
||||||
|
const listener = () => called = true;
|
||||||
|
const verifier = (args: unknown[]): args is [] => false;
|
||||||
|
const channel = "foobar";
|
||||||
|
|
||||||
|
onceCorrect({ source, listener, verifier, channel });
|
||||||
|
|
||||||
|
source.emit(channel);
|
||||||
|
expect(called).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call the handler only once even if args are valid multiple times", () => {
|
||||||
|
let called = 0;
|
||||||
|
const source = new EventEmitter();
|
||||||
|
const listener = () => called += 1;
|
||||||
|
const verifier = (args: unknown[]): args is [] => true;
|
||||||
|
const channel = "foobar";
|
||||||
|
|
||||||
|
onceCorrect({ source, listener, verifier, channel });
|
||||||
|
|
||||||
|
source.emit(channel);
|
||||||
|
source.emit(channel);
|
||||||
|
expect(called).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call the handler on only the first valid set of args", () => {
|
||||||
|
let called = "";
|
||||||
|
let verifierCalled = 0;
|
||||||
|
const source = new EventEmitter();
|
||||||
|
const listener = (info: any, arg: string) => called = arg;
|
||||||
|
const verifier = (args: unknown[]): args is [string] => (++verifierCalled) % 3 === 0;
|
||||||
|
const channel = "foobar";
|
||||||
|
|
||||||
|
onceCorrect({ source, listener, verifier, channel });
|
||||||
|
|
||||||
|
source.emit(channel, {}, "a");
|
||||||
|
source.emit(channel, {}, "b");
|
||||||
|
source.emit(channel, {}, "c");
|
||||||
|
source.emit(channel, {}, "d");
|
||||||
|
source.emit(channel, {}, "e");
|
||||||
|
source.emit(channel, {}, "f");
|
||||||
|
source.emit(channel, {}, "g");
|
||||||
|
source.emit(channel, {}, "h");
|
||||||
|
source.emit(channel, {}, "i");
|
||||||
|
expect(called).toBe("c");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
3
src/common/ipc/index.ts
Normal file
3
src/common/ipc/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./ipc";
|
||||||
|
export * from "./update-available";
|
||||||
|
export * from "./type-enforced-ipc";
|
||||||
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
import { ipcMain, ipcRenderer, webContents, remote } from "electron";
|
import { ipcMain, ipcRenderer, webContents, remote } from "electron";
|
||||||
import { toJS } from "mobx";
|
import { toJS } from "mobx";
|
||||||
import logger from "../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { ClusterFrameInfo, clusterFrameMap } from "./cluster-frames";
|
import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames";
|
||||||
|
|
||||||
const subFramesChannel = "ipc:get-sub-frames";
|
const subFramesChannel = "ipc:get-sub-frames";
|
||||||
|
|
||||||
export function handleRequest(channel: string, listener: (...args: any[]) => any) {
|
export function handleRequest(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) {
|
||||||
ipcMain.handle(channel, listener);
|
ipcMain.handle(channel, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
71
src/common/ipc/type-enforced-ipc.ts
Normal file
71
src/common/ipc/type-enforced-ipc.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { EventEmitter } from "events";
|
||||||
|
import logger from "../../main/logger";
|
||||||
|
|
||||||
|
export type HandlerEvent<EM extends EventEmitter> = Parameters<Parameters<EM["on"]>[1]>[0];
|
||||||
|
export type ListVerifier<T extends any[]> = (args: unknown[]) => args is T;
|
||||||
|
export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener to `source` that waits for the first IPC message with the correct
|
||||||
|
* argument data is sent.
|
||||||
|
* @param channel The channel to be listened on
|
||||||
|
* @param listener The function for the channel to be called if the args of the correct type
|
||||||
|
* @param verifier The function to be called to verify that the args are the correct type
|
||||||
|
*/
|
||||||
|
export function onceCorrect<
|
||||||
|
EM extends EventEmitter,
|
||||||
|
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
|
||||||
|
>({
|
||||||
|
source,
|
||||||
|
channel,
|
||||||
|
listener,
|
||||||
|
verifier,
|
||||||
|
}: {
|
||||||
|
source: EM,
|
||||||
|
channel: string | symbol,
|
||||||
|
listener: L,
|
||||||
|
verifier: ListVerifier<Rest<Parameters<L>>>,
|
||||||
|
}): void {
|
||||||
|
function handler(event: HandlerEvent<EM>, ...args: unknown[]): void {
|
||||||
|
if (verifier(args)) {
|
||||||
|
source.removeListener(channel, handler); // remove immediately
|
||||||
|
|
||||||
|
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
||||||
|
.catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error }));
|
||||||
|
} else {
|
||||||
|
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source.on(channel, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener to `source` that checks to verify the arguments before calling the handler.
|
||||||
|
* @param channel The channel to be listened on
|
||||||
|
* @param listener The function for the channel to be called if the args of the correct type
|
||||||
|
* @param verifier The function to be called to verify that the args are the correct type
|
||||||
|
*/
|
||||||
|
export function onCorrect<
|
||||||
|
EM extends EventEmitter,
|
||||||
|
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
|
||||||
|
>({
|
||||||
|
source,
|
||||||
|
channel,
|
||||||
|
listener,
|
||||||
|
verifier,
|
||||||
|
}: {
|
||||||
|
source: EM,
|
||||||
|
channel: string | symbol,
|
||||||
|
listener: L,
|
||||||
|
verifier: ListVerifier<Rest<Parameters<L>>>,
|
||||||
|
}): void {
|
||||||
|
source.on(channel, (event, ...args: unknown[]) => {
|
||||||
|
if (verifier(args)) {
|
||||||
|
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
||||||
|
.catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error }));
|
||||||
|
} else {
|
||||||
|
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
48
src/common/ipc/update-available/index.ts
Normal file
48
src/common/ipc/update-available/index.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { UpdateInfo } from "electron-updater";
|
||||||
|
|
||||||
|
export const UpdateAvailableChannel = "update-available";
|
||||||
|
export const AutoUpdateLogPrefix = "[UPDATE-CHECKER]";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [<back-channel>, <update-info>]
|
||||||
|
*/
|
||||||
|
export type UpdateAvailableFromMain = [string, UpdateInfo];
|
||||||
|
|
||||||
|
export function areArgsUpdateAvailableFromMain(args: unknown[]): args is UpdateAvailableFromMain {
|
||||||
|
if (args.length !== 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof args[0] !== "string") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof args[1] !== "object" || args[1] === null) {
|
||||||
|
// TODO: improve this checking
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BackchannelArg = {
|
||||||
|
doUpdate: false;
|
||||||
|
} | {
|
||||||
|
doUpdate: true;
|
||||||
|
now: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateAvailableToBackchannel = [BackchannelArg];
|
||||||
|
|
||||||
|
export function areArgsUpdateAvailableToBackchannel(args: unknown[]): args is UpdateAvailableToBackchannel {
|
||||||
|
if (args.length !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof args[0] !== "object" || args[0] === null) {
|
||||||
|
// TODO: improve this checking
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
// Create async delay for provided timeout in milliseconds
|
/**
|
||||||
|
* Return a promise that will be resolved after at least `timeout` ms have
|
||||||
export async function delay(timeoutMs = 1000) {
|
* passed
|
||||||
if (!timeoutMs) return;
|
* @param timeout The number of milliseconds before resolving
|
||||||
await new Promise(resolve => setTimeout(resolve, timeoutMs));
|
*/
|
||||||
|
export function delay(timeout = 1000): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, timeout));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,3 +18,4 @@ export * from "./openExternal";
|
|||||||
export * from "./downloadFile";
|
export * from "./downloadFile";
|
||||||
export * from "./escapeRegExp";
|
export * from "./escapeRegExp";
|
||||||
export * from "./tar";
|
export * from "./tar";
|
||||||
|
export * from "./delay";
|
||||||
|
|||||||
@ -1,20 +1,78 @@
|
|||||||
import { autoUpdater } from "electron-updater";
|
import { autoUpdater, UpdateInfo } from "electron-updater";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
import { isDevelopment, isTestEnv } from "../common/vars";
|
||||||
|
import { delay } from "../common/utils";
|
||||||
|
import { areArgsUpdateAvailableToBackchannel, AutoUpdateLogPrefix, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
|
||||||
export class AppUpdater {
|
function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: UpdateAvailableToBackchannel) {
|
||||||
static readonly defaultUpdateIntervalMs = 1000 * 60 * 60 * 24; // once a day
|
if (arg.doUpdate) {
|
||||||
|
if (arg.now) {
|
||||||
static checkForUpdates() {
|
logger.info(`${AutoUpdateLogPrefix}: User chose to update now`);
|
||||||
return autoUpdater.checkForUpdatesAndNotify();
|
autoUpdater.downloadUpdate()
|
||||||
}
|
.then(() => autoUpdater.quitAndInstall())
|
||||||
|
.catch(error => logger.error(`${AutoUpdateLogPrefix}: Failed to download or install update`, { error }));
|
||||||
constructor(protected updateInterval = AppUpdater.defaultUpdateIntervalMs) {
|
} else {
|
||||||
autoUpdater.logger = logger;
|
logger.info(`${AutoUpdateLogPrefix}: User chose to update on quit`);
|
||||||
}
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
|
autoUpdater.downloadUpdate()
|
||||||
public start() {
|
.catch(error => logger.error(`${AutoUpdateLogPrefix}: Failed to download update`, { error }));
|
||||||
setInterval(AppUpdater.checkForUpdates, this.updateInterval);
|
}
|
||||||
|
} else {
|
||||||
return AppUpdater.checkForUpdates();
|
logger.info(`${AutoUpdateLogPrefix}: User chose not to update`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* starts the automatic update checking
|
||||||
|
* @param interval milliseconds between interval to check on, defaults to 24h
|
||||||
|
*/
|
||||||
|
export function startUpdateChecking(interval = 1000 * 60 * 60 * 24): void {
|
||||||
|
if (isDevelopment || isTestEnv) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoUpdater.logger = logger;
|
||||||
|
autoUpdater.autoDownload = false;
|
||||||
|
autoUpdater.autoInstallOnAppQuit = false;
|
||||||
|
|
||||||
|
autoUpdater
|
||||||
|
.on("update-available", (args: UpdateInfo) => {
|
||||||
|
try {
|
||||||
|
const backchannel = `auto-update:${args.version}`;
|
||||||
|
|
||||||
|
ipcMain.removeAllListeners(backchannel); // only one handler should be present
|
||||||
|
|
||||||
|
// make sure that the handler is in place before broadcasting (prevent race-condition)
|
||||||
|
onceCorrect({
|
||||||
|
source: ipcMain,
|
||||||
|
channel: backchannel,
|
||||||
|
listener: handleAutoUpdateBackChannel,
|
||||||
|
verifier: areArgsUpdateAvailableToBackchannel,
|
||||||
|
});
|
||||||
|
logger.info(`${AutoUpdateLogPrefix}: broadcasting update available`, { backchannel, version: args.version });
|
||||||
|
broadcastMessage(UpdateAvailableChannel, backchannel, args);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`${AutoUpdateLogPrefix}: broadcasting failed`, { error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function helper() {
|
||||||
|
while (true) {
|
||||||
|
await checkForUpdates();
|
||||||
|
await delay(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helper();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkForUpdates(): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info(`📡 Checking for app updates`);
|
||||||
|
|
||||||
|
await autoUpdater.checkForUpdates();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`${AutoUpdateLogPrefix}: failed with an error`, { error: String(error) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import path from "path";
|
|||||||
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";
|
||||||
import { AppUpdater } from "./app-updater";
|
|
||||||
import { shellSync } from "./shell-sync";
|
import { shellSync } from "./shell-sync";
|
||||||
import { getFreePort } from "./port";
|
import { getFreePort } from "./port";
|
||||||
import { mangleProxyEnv } from "./proxy-env";
|
import { mangleProxyEnv } from "./proxy-env";
|
||||||
@ -28,6 +27,7 @@ import { installDeveloperTools } from "./developer-tools";
|
|||||||
import { filesystemProvisionerStore } from "./extension-filesystem";
|
import { filesystemProvisionerStore } from "./extension-filesystem";
|
||||||
import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
|
import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
|
||||||
import { bindBroadcastHandlers } from "../common/ipc";
|
import { bindBroadcastHandlers } from "../common/ipc";
|
||||||
|
import { startUpdateChecking } from "./app-updater";
|
||||||
|
|
||||||
const workingDir = path.join(app.getPath("appData"), appName);
|
const workingDir = path.join(app.getPath("appData"), appName);
|
||||||
let proxyPort: number;
|
let proxyPort: number;
|
||||||
@ -72,11 +72,6 @@ app.on("ready", async () => {
|
|||||||
app.exit();
|
app.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(`📡 Checking for app updates`);
|
|
||||||
const updater = new AppUpdater();
|
|
||||||
|
|
||||||
updater.start();
|
|
||||||
|
|
||||||
registerFileProtocol("static", __static);
|
registerFileProtocol("static", __static);
|
||||||
|
|
||||||
await installDeveloperTools();
|
await installDeveloperTools();
|
||||||
@ -133,6 +128,7 @@ app.on("ready", async () => {
|
|||||||
|
|
||||||
logger.info("🖥️ Starting WindowManager");
|
logger.info("🖥️ Starting WindowManager");
|
||||||
windowManager = WindowManager.getInstance<WindowManager>(proxyPort);
|
windowManager = WindowManager.getInstance<WindowManager>(proxyPort);
|
||||||
|
windowManager.whenLoaded.then(() => startUpdateChecking());
|
||||||
|
|
||||||
logger.info("🧩 Initializing extensions");
|
logger.info("🧩 Initializing extensions");
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import packageInfo from "../../package.json";
|
import packageInfo from "../../package.json";
|
||||||
import { dialog, Menu, NativeImage, Tray } from "electron";
|
import { Menu, NativeImage, Tray } from "electron";
|
||||||
import { autorun } from "mobx";
|
import { autorun } from "mobx";
|
||||||
import { showAbout } from "./menu";
|
import { showAbout } from "./menu";
|
||||||
import { AppUpdater } from "./app-updater";
|
import { checkForUpdates } from "./app-updater";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { clusterStore } from "../common/cluster-store";
|
import { clusterStore } from "../common/cluster-store";
|
||||||
import { workspaceStore } from "../common/workspace-store";
|
import { workspaceStore } from "../common/workspace-store";
|
||||||
@ -102,16 +102,8 @@ function createTrayMenu(windowManager: WindowManager): Menu {
|
|||||||
{
|
{
|
||||||
label: "Check for updates",
|
label: "Check for updates",
|
||||||
async click() {
|
async click() {
|
||||||
const result = await AppUpdater.checkForUpdates();
|
await checkForUpdates();
|
||||||
|
await windowManager.ensureMainWindow();
|
||||||
if (!result) {
|
|
||||||
const browserWindow = await windowManager.ensureMainWindow();
|
|
||||||
|
|
||||||
dialog.showMessageBoxSync(browserWindow, {
|
|
||||||
message: "No updates available",
|
|
||||||
type: "info",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ClusterId } from "../common/cluster-store";
|
import type { ClusterId } from "../common/cluster-store";
|
||||||
import { observable } from "mobx";
|
import { observable, when } from "mobx";
|
||||||
import { app, BrowserWindow, dialog, shell, webContents } from "electron";
|
import { app, BrowserWindow, dialog, shell, webContents } from "electron";
|
||||||
import windowStateKeeper from "electron-window-state";
|
import windowStateKeeper from "electron-window-state";
|
||||||
import { appEventBus } from "../common/event-bus";
|
import { appEventBus } from "../common/event-bus";
|
||||||
@ -16,6 +16,9 @@ export class WindowManager extends Singleton {
|
|||||||
protected windowState: windowStateKeeper.State;
|
protected windowState: windowStateKeeper.State;
|
||||||
protected disposers: Record<string, Function> = {};
|
protected disposers: Record<string, Function> = {};
|
||||||
|
|
||||||
|
@observable mainViewInitiallyLoaded = false;
|
||||||
|
whenLoaded = when(() => this.mainViewInitiallyLoaded);
|
||||||
|
|
||||||
@observable activeClusterId: ClusterId;
|
@observable activeClusterId: ClusterId;
|
||||||
|
|
||||||
constructor(protected proxyPort: number) {
|
constructor(protected proxyPort: number) {
|
||||||
@ -101,6 +104,7 @@ export class WindowManager extends Singleton {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
appEventBus.emit({ name: "app", action: "start" });
|
appEventBus.emit({ name: "app", action: "start" });
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
this.mainViewInitiallyLoaded = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dialog.showErrorBox("ERROR!", err.toString());
|
dialog.showErrorBox("ERROR!", err.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { KubeApi, parseKubeApi } from "./kube-api";
|
|||||||
@autobind()
|
@autobind()
|
||||||
export class ApiManager {
|
export class ApiManager {
|
||||||
private apis = observable.map<string, KubeApi>();
|
private apis = observable.map<string, KubeApi>();
|
||||||
private stores = observable.map<KubeApi, KubeObjectStore>();
|
private stores = observable.map<string, KubeObjectStore>();
|
||||||
|
|
||||||
getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) {
|
getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) {
|
||||||
if (typeof pathOrCallback === "string") {
|
if (typeof pathOrCallback === "string") {
|
||||||
@ -46,12 +46,12 @@ export class ApiManager {
|
|||||||
@action
|
@action
|
||||||
registerStore(store: KubeObjectStore, apis: KubeApi[] = [store.api]) {
|
registerStore(store: KubeObjectStore, apis: KubeApi[] = [store.api]) {
|
||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
this.stores.set(api, store);
|
this.stores.set(api.apiBase, store);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getStore<S extends KubeObjectStore>(api: string | KubeApi): S {
|
getStore<S extends KubeObjectStore>(api: string | KubeApi): S {
|
||||||
return this.stores.get(this.resolveApi(api)) as S;
|
return this.stores.get(this.resolveApi(api)?.apiBase) as S;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,7 @@ export interface IReleaseRevision {
|
|||||||
updated: string;
|
updated: string;
|
||||||
status: string;
|
status: string;
|
||||||
chart: string;
|
chart: string;
|
||||||
|
app_version: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,8 @@ export class ReleaseRollbackDialog extends React.Component<Props> {
|
|||||||
themeName="light"
|
themeName="light"
|
||||||
value={revision}
|
value={revision}
|
||||||
options={revisions}
|
options={revisions}
|
||||||
formatOptionLabel={({ value }: SelectOption<IReleaseRevision>) => `${value.revision} - ${value.chart}`}
|
formatOptionLabel={({ value }: SelectOption<IReleaseRevision>) => `${value.revision} - ${value.chart}
|
||||||
|
- ${value.app_version}, updated: ${new Date(value.updated).toLocaleString()}`}
|
||||||
onChange={({ value }: SelectOption<IReleaseRevision>) => this.revision = value}
|
onChange={({ value }: SelectOption<IReleaseRevision>) => this.revision = value}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -76,4 +76,3 @@ export class NamespaceSelect extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { workloadURL, workloadStores } from "../+workloads";
|
|||||||
import { namespaceStore } from "../+namespaces/namespace.store";
|
import { namespaceStore } from "../+namespaces/namespace.store";
|
||||||
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
|
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
|
||||||
import { isAllowedResource, KubeResource } from "../../../common/rbac";
|
import { isAllowedResource, KubeResource } from "../../../common/rbac";
|
||||||
import { ResourceNames } from "../../../renderer/utils/rbac";
|
import { ResourceNames } from "../../utils/rbac";
|
||||||
import { autobind } from "../../utils";
|
import { autobind } from "../../utils";
|
||||||
|
|
||||||
const resources: KubeResource[] = [
|
const resources: KubeResource[] = [
|
||||||
|
|||||||
@ -21,10 +21,16 @@
|
|||||||
&.primary {
|
&.primary {
|
||||||
background: $buttonPrimaryBackground;
|
background: $buttonPrimaryBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.accent {
|
&.accent {
|
||||||
background: $buttonAccentBackground;
|
background: $buttonAccentBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
background-color: $buttonLightBackground;
|
||||||
|
color: #505050;
|
||||||
|
}
|
||||||
|
|
||||||
&.plain {
|
&.plain {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<any>, TooltipDecorator
|
|||||||
waiting?: boolean;
|
waiting?: boolean;
|
||||||
primary?: boolean;
|
primary?: boolean;
|
||||||
accent?: boolean;
|
accent?: boolean;
|
||||||
|
light?: boolean;
|
||||||
plain?: boolean;
|
plain?: boolean;
|
||||||
outlined?: boolean;
|
outlined?: boolean;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
@ -24,13 +25,16 @@ export class Button extends React.PureComponent<ButtonProps, {}> {
|
|||||||
private button: HTMLButtonElement;
|
private button: HTMLButtonElement;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, waiting, label, primary, accent, plain, hidden, active, big, round, outlined, tooltip, children, ...props } = this.props;
|
const {
|
||||||
const btnProps = props as Partial<ButtonProps>;
|
className, waiting, label, primary, accent, plain, hidden, active, big,
|
||||||
|
round, outlined, tooltip, light, children, ...props
|
||||||
|
} = this.props;
|
||||||
|
const btnProps: Partial<ButtonProps> = props;
|
||||||
|
|
||||||
if (hidden) return null;
|
if (hidden) return null;
|
||||||
|
|
||||||
btnProps.className = cssNames("Button", className, {
|
btnProps.className = cssNames("Button", className, {
|
||||||
waiting, primary, accent, plain, active, big, round, outlined
|
waiting, primary, accent, plain, active, big, round, outlined, light,
|
||||||
});
|
});
|
||||||
|
|
||||||
const btnContent: ReactNode = (
|
const btnContent: ReactNode = (
|
||||||
|
|||||||
@ -50,6 +50,10 @@ export class BarChart extends React.Component<Props> {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (chartData.datasets.length == 0) {
|
||||||
|
return <NoMetrics/>;
|
||||||
|
}
|
||||||
|
|
||||||
const formatTimeLabels = (timestamp: string, index: number) => {
|
const formatTimeLabels = (timestamp: string, index: number) => {
|
||||||
const label = moment(parseInt(timestamp)).format("HH:mm");
|
const label = moment(parseInt(timestamp)).format("HH:mm");
|
||||||
const offset = " ";
|
const offset = " ";
|
||||||
@ -143,10 +147,6 @@ export class BarChart extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
const options = merge(barOptions, customOptions);
|
const options = merge(barOptions, customOptions);
|
||||||
|
|
||||||
if (chartData.datasets.length == 0) {
|
|
||||||
return <NoMetrics/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Chart
|
<Chart
|
||||||
className={cssNames("BarChart flex box grow column", className)}
|
className={cssNames("BarChart flex box grow column", className)}
|
||||||
|
|||||||
@ -146,11 +146,10 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
|
|||||||
{dialog}
|
{dialog}
|
||||||
</Animate>
|
</Animate>
|
||||||
);
|
);
|
||||||
}
|
} else if (!this.isOpen) {
|
||||||
else if (!this.isOpen) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return createPortal(dialog, document.body);
|
return createPortal(dialog, document.body) as React.ReactPortal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,5 +42,9 @@
|
|||||||
box-shadow: 0 0 20px $boxShadow;
|
box-shadow: 0 0 20px $boxShadow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export interface Notification {
|
|||||||
message: NotificationMessage;
|
message: NotificationMessage;
|
||||||
status?: NotificationStatus;
|
status?: NotificationStatus;
|
||||||
timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide
|
timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide
|
||||||
|
onClose?(): void; // additonal logic on when the notification times out or is closed by the "x"
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
@ -72,23 +72,26 @@ export class Notifications extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="Notifications flex column align-flex-end" ref={e => this.elem = e}>
|
<div className="Notifications flex column align-flex-end" ref={e => this.elem = e}>
|
||||||
{notifications.map(notification => {
|
{notifications.map(notification => {
|
||||||
const { id, status } = notification;
|
const { id, status, onClose } = notification;
|
||||||
const msgText = this.getMessage(notification);
|
const msgText = this.getMessage(notification);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animate key={id}>
|
<Animate key={id}>
|
||||||
<div
|
<div
|
||||||
className={cssNames("notification flex align-center", status)}
|
className={cssNames("notification flex", status)}
|
||||||
onMouseLeave={() => addAutoHideTimer(id)}
|
onMouseLeave={() => addAutoHideTimer(id)}
|
||||||
onMouseEnter={() => removeAutoHideTimer(id)}>
|
onMouseEnter={() => removeAutoHideTimer(id)}>
|
||||||
<div className="box center">
|
<div className="box">
|
||||||
<Icon material="info_outline"/>
|
<Icon material="info_outline"/>
|
||||||
</div>
|
</div>
|
||||||
<div className="message box grow">{msgText}</div>
|
<div className="message box grow">{msgText}</div>
|
||||||
<div className="box center">
|
<div className="box">
|
||||||
<Icon
|
<Icon
|
||||||
material="close" className="close"
|
material="close" className="close"
|
||||||
onClick={prevDefault(() => remove(id))}
|
onClick={prevDefault(() => {
|
||||||
|
remove(id);
|
||||||
|
onClose?.();
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
61
src/renderer/ipc/index.tsx
Normal file
61
src/renderer/ipc/index.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { ipcRenderer, IpcRendererEvent } from "electron";
|
||||||
|
import { areArgsUpdateAvailableFromMain, UpdateAvailableChannel, onCorrect, UpdateAvailableFromMain, BackchannelArg } from "../../common/ipc";
|
||||||
|
import { Notifications, notificationsStore } from "../components/notifications";
|
||||||
|
import { Button } from "../components/button";
|
||||||
|
import { isMac } from "../../common/vars";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
|
function sendToBackchannel(backchannel: string, notificationId: string, data: BackchannelArg): void {
|
||||||
|
notificationsStore.remove(notificationId);
|
||||||
|
ipcRenderer.send(backchannel, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RenderYesButtons(props: { backchannel: string, notificationId: string }) {
|
||||||
|
if (isMac) {
|
||||||
|
/**
|
||||||
|
* auto-updater's "installOnQuit" is not applicable for macOS as per their docs.
|
||||||
|
*
|
||||||
|
* See: https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/AppUpdater.ts#L27-L32
|
||||||
|
*/
|
||||||
|
return <Button light label="Yes" onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: true })} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button light label="Yes, now" onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: true })} />
|
||||||
|
<Button active outlined label="Yes, later" onClick={() => sendToBackchannel(props.backchannel, props.notificationId, { doUpdate: true, now: false })} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, updateInfo]: UpdateAvailableFromMain): void {
|
||||||
|
const notificationId = uuid.v4();
|
||||||
|
|
||||||
|
Notifications.info(
|
||||||
|
(
|
||||||
|
<>
|
||||||
|
<b>Update Available</b>
|
||||||
|
<p>Version {updateInfo.version} of Lens IDE is now available. Would you like to update?</p>
|
||||||
|
<div className="flex gaps row align-left box grow">
|
||||||
|
<RenderYesButtons backchannel={backchannel} notificationId={notificationId} />
|
||||||
|
<Button active outlined label="No" onClick={() => sendToBackchannel(backchannel, notificationId, { doUpdate: false })} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
), {
|
||||||
|
id: notificationId,
|
||||||
|
onClose() {
|
||||||
|
sendToBackchannel(backchannel, notificationId, { doUpdate: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerIpcHandlers() {
|
||||||
|
onCorrect({
|
||||||
|
source: ipcRenderer,
|
||||||
|
channel: UpdateAvailableChannel,
|
||||||
|
listener: UpdateAvailableHandler,
|
||||||
|
verifier: areArgsUpdateAvailableFromMain,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import { ConfirmDialog } from "./components/confirm-dialog";
|
|||||||
import { extensionLoader } from "../extensions/extension-loader";
|
import { extensionLoader } from "../extensions/extension-loader";
|
||||||
import { broadcastMessage } from "../common/ipc";
|
import { broadcastMessage } from "../common/ipc";
|
||||||
import { CommandContainer } from "./components/command-palette/command-container";
|
import { CommandContainer } from "./components/command-palette/command-container";
|
||||||
|
import { registerIpcHandlers } from "./ipc";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class LensApp extends React.Component {
|
export class LensApp extends React.Component {
|
||||||
@ -23,6 +24,8 @@ export class LensApp extends React.Component {
|
|||||||
window.addEventListener("online", () => {
|
window.addEventListener("online", () => {
|
||||||
broadcastMessage("network:online");
|
broadcastMessage("network:online");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerIpcHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
"sidebarSubmenuActiveColor": "#ffffff",
|
"sidebarSubmenuActiveColor": "#ffffff",
|
||||||
"buttonPrimaryBackground": "#3d90ce",
|
"buttonPrimaryBackground": "#3d90ce",
|
||||||
"buttonDefaultBackground": "#414448",
|
"buttonDefaultBackground": "#414448",
|
||||||
|
"buttonLightBackground": "#f1f1f1",
|
||||||
"buttonAccentBackground": "#e85555",
|
"buttonAccentBackground": "#e85555",
|
||||||
"buttonDisabledBackground": "#808080",
|
"buttonDisabledBackground": "#808080",
|
||||||
"tableBgcStripe": "#2a2d33",
|
"tableBgcStripe": "#2a2d33",
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
"sidebarBackground": "#e8e8e8",
|
"sidebarBackground": "#e8e8e8",
|
||||||
"buttonPrimaryBackground": "#3d90ce",
|
"buttonPrimaryBackground": "#3d90ce",
|
||||||
"buttonDefaultBackground": "#414448",
|
"buttonDefaultBackground": "#414448",
|
||||||
|
"buttonLightBackground": "#f1f1f1",
|
||||||
"buttonAccentBackground": "#e85555",
|
"buttonAccentBackground": "#e85555",
|
||||||
"buttonDisabledBackground": "#808080",
|
"buttonDisabledBackground": "#808080",
|
||||||
"tableBgcStripe": "#f8f8f8",
|
"tableBgcStripe": "#f8f8f8",
|
||||||
|
|||||||
@ -34,6 +34,7 @@ $sidebarBackground: var(--sidebarBackground);
|
|||||||
// Elements
|
// Elements
|
||||||
$buttonPrimaryBackground: var(--buttonPrimaryBackground);
|
$buttonPrimaryBackground: var(--buttonPrimaryBackground);
|
||||||
$buttonDefaultBackground: var(--buttonDefaultBackground);
|
$buttonDefaultBackground: var(--buttonDefaultBackground);
|
||||||
|
$buttonLightBackground: var(--buttonLightBackground);
|
||||||
$buttonAccentBackground: var(--buttonAccentBackground);
|
$buttonAccentBackground: var(--buttonAccentBackground);
|
||||||
$buttonDisabledBackground: var(--buttonDisabledBackground);
|
$buttonDisabledBackground: var(--buttonDisabledBackground);
|
||||||
|
|
||||||
@ -131,4 +132,4 @@ $selectOptionHoveredColor: var(--selectOptionHoveredColor);
|
|||||||
$lineProgressBackground: var(--lineProgressBackground);
|
$lineProgressBackground: var(--lineProgressBackground);
|
||||||
$radioActiveBackground: var(--radioActiveBackground);
|
$radioActiveBackground: var(--radioActiveBackground);
|
||||||
$menuActiveBackground: var(--menuActiveBackground);
|
$menuActiveBackground: var(--menuActiveBackground);
|
||||||
$menuSelectedOptionBgc: var(--menuSelectedOptionBgc);
|
$menuSelectedOptionBgc: var(--menuSelectedOptionBgc);
|
||||||
|
|||||||
@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
||||||
|
|
||||||
## 4.1.0-alpha.2 (current version)
|
## 4.1.0-beta.1 (current version)
|
||||||
|
|
||||||
- Change: list views default to a namespace (insted of listing resources from all namespaces)
|
- Change: list views default to a namespace (insted of listing resources from all namespaces)
|
||||||
- Command palette
|
- Command palette
|
||||||
- Generic logs view with Pod selector
|
- Generic logs view with Pod selector
|
||||||
|
- In-app survey extension
|
||||||
- Possibility to add custom Helm repository through Lens
|
- Possibility to add custom Helm repository through Lens
|
||||||
- Possibility to change visibility of common resource list columns
|
- Possibility to change visibility of common resource list columns
|
||||||
- Suspend / resume buttons for CronJobs
|
- Suspend / resume buttons for CronJobs
|
||||||
@ -21,6 +22,8 @@ Here you can find description of changes we've built into each release. While we
|
|||||||
- Lens metrics: Prometheus v2.19.3
|
- Lens metrics: Prometheus v2.19.3
|
||||||
- Update bundled kubectl to v1.18.15
|
- Update bundled kubectl to v1.18.15
|
||||||
- Improve how watch requests are handled
|
- Improve how watch requests are handled
|
||||||
|
- Helm rollback window with more details
|
||||||
|
- Log more on start up
|
||||||
- Export PodDetailsList component to extension API
|
- Export PodDetailsList component to extension API
|
||||||
- Export Wizard components to extension API
|
- Export Wizard components to extension API
|
||||||
- Export NamespaceSelect component to extension API
|
- Export NamespaceSelect component to extension API
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import webpack from "webpack";
|
import webpack from "webpack";
|
||||||
import { sassCommonVars } from "./src/common/vars";
|
import { sassCommonVars, isDevelopment } from "./src/common/vars";
|
||||||
|
|
||||||
export default function (): webpack.Configuration {
|
export default function generateExtensionTypes(): webpack.Configuration {
|
||||||
const entry = "./src/extensions/extension-api.ts";
|
const entry = "./src/extensions/extension-api.ts";
|
||||||
const outDir = "./src/extensions/npm/extensions/dist";
|
const outDir = "./src/extensions/npm/extensions/dist";
|
||||||
|
|
||||||
@ -22,6 +22,10 @@ export default function (): webpack.Configuration {
|
|||||||
// e.g. require('@k8slens/extensions')
|
// e.g. require('@k8slens/extensions')
|
||||||
libraryTarget: "commonjs"
|
libraryTarget: "commonjs"
|
||||||
},
|
},
|
||||||
|
cache: isDevelopment,
|
||||||
|
optimization: {
|
||||||
|
minimize: false, // speed up types compilation
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user