mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into mobx-6.2
# Conflicts: # src/common/hotbar-store.ts
This commit is contained in:
commit
426cb48966
@ -2,9 +2,9 @@
|
|||||||
"name": "open-lens",
|
"name": "open-lens",
|
||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"version": "5.0.0-alpha.1",
|
"version": "5.0.0-alpha.2",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021, OpenLens Authors",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "OpenLens Authors"
|
"name": "OpenLens Authors"
|
||||||
|
|||||||
@ -59,7 +59,7 @@ describe("empty config", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
await ClusterStore.getInstanceOrCreate().load();
|
await ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -177,7 +177,7 @@ describe("config with existing clusters", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.getInstanceOrCreate().load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -274,7 +274,7 @@ users:
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.getInstanceOrCreate().load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -316,7 +316,7 @@ describe("pre 2.0 config with an existing cluster", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.getInstanceOrCreate().load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -386,7 +386,7 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.getInstanceOrCreate().load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -430,7 +430,7 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.getInstanceOrCreate().load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -469,7 +469,7 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.getInstanceOrCreate().load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -505,7 +505,7 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.getInstanceOrCreate().load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { HotbarStore } from "../hotbar-store";
|
|||||||
describe("HotbarStore", () => {
|
describe("HotbarStore", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
ClusterStore.getInstanceOrCreate();
|
ClusterStore.createInstance();
|
||||||
|
|
||||||
HotbarStore.resetInstance();
|
HotbarStore.resetInstance();
|
||||||
mockFs({ tmp: { "lens-hotbar-store.json": "{}" } });
|
mockFs({ tmp: { "lens-hotbar-store.json": "{}" } });
|
||||||
@ -17,8 +17,18 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
describe("load", () => {
|
describe("load", () => {
|
||||||
it("loads one hotbar by default", () => {
|
it("loads one hotbar by default", () => {
|
||||||
HotbarStore.getInstanceOrCreate().load();
|
HotbarStore.createInstance().load();
|
||||||
expect(HotbarStore.getInstance().hotbars.length).toEqual(1);
|
expect(HotbarStore.getInstance().hotbars.length).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("add", () => {
|
||||||
|
it("adds a hotbar", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.add({ name: "hottest" });
|
||||||
|
expect(hotbarStore.hotbars.length).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -28,7 +28,7 @@ describe("user store tests", () => {
|
|||||||
UserStore.resetInstance();
|
UserStore.resetInstance();
|
||||||
mockFs({ tmp: { "config.json": "{}", "kube_config": "{}" } });
|
mockFs({ tmp: { "config.json": "{}", "kube_config": "{}" } });
|
||||||
|
|
||||||
(UserStore.getInstanceOrCreate() as any).refreshNewContexts = jest.fn(() => Promise.resolve());
|
(UserStore.createInstance() as any).refreshNewContexts = jest.fn(() => Promise.resolve());
|
||||||
|
|
||||||
return UserStore.getInstance().load();
|
return UserStore.getInstance().load();
|
||||||
});
|
});
|
||||||
@ -102,7 +102,7 @@ describe("user store tests", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return UserStore.getInstanceOrCreate().load();
|
return UserStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { CatalogCategory, CatalogEntity, CatalogEntityActionContext, CatalogEnti
|
|||||||
import { clusterDisconnectHandler } from "../cluster-ipc";
|
import { clusterDisconnectHandler } from "../cluster-ipc";
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store";
|
||||||
import { requestMain } from "../ipc";
|
import { requestMain } from "../ipc";
|
||||||
|
import { productName } from "../vars";
|
||||||
|
|
||||||
export type KubernetesClusterSpec = {
|
export type KubernetesClusterSpec = {
|
||||||
kubeconfigPath: string;
|
kubeconfigPath: string;
|
||||||
@ -59,7 +60,7 @@ export class KubernetesCluster implements CatalogEntity {
|
|||||||
onlyVisibleForSource: "local",
|
onlyVisibleForSource: "local",
|
||||||
onClick: async () => ClusterStore.getInstance().removeById(this.metadata.uid),
|
onClick: async () => ClusterStore.getInstance().removeById(this.metadata.uid),
|
||||||
confirm: {
|
confirm: {
|
||||||
message: `Remove Kubernetes Cluster "${this.metadata.name} from Lens?`
|
message: `Remove Kubernetes Cluster "${this.metadata.name} from ${productName}?`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { action, comparer, observable, toJS, makeObservable } from "mobx";
|
import { action, comparer, observable, toJS, makeObservable } from "mobx";
|
||||||
import { BaseStore } from "./base-store";
|
import { BaseStore } from "./base-store";
|
||||||
import migrations from "../migrations/hotbar-store";
|
import migrations from "../migrations/hotbar-store";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
export interface HotbarItem {
|
export interface HotbarItem {
|
||||||
entity: {
|
entity: {
|
||||||
@ -12,16 +13,25 @@ export interface HotbarItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Hotbar {
|
export interface Hotbar {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
items: HotbarItem[];
|
items: HotbarItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HotbarCreateOptions {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
items?: HotbarItem[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface HotbarStoreModel {
|
export interface HotbarStoreModel {
|
||||||
hotbars: Hotbar[];
|
hotbars: Hotbar[];
|
||||||
|
activeHotbarId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||||
@observable hotbars: Hotbar[] = [];
|
@observable hotbars: Hotbar[] = [];
|
||||||
|
@observable private _activeHotbarId: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
@ -35,32 +45,105 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get activeHotbarId() {
|
||||||
|
return this._activeHotbarId;
|
||||||
|
}
|
||||||
|
|
||||||
|
set activeHotbarId(id: string) {
|
||||||
|
if (this.getById(id)) {
|
||||||
|
this._activeHotbarId = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeHotbarIndex() {
|
||||||
|
return this.hotbars.findIndex((hotbar) => hotbar.id === this.activeHotbarId);
|
||||||
|
}
|
||||||
|
|
||||||
@action protected async fromStore(data: Partial<HotbarStoreModel> = {}) {
|
@action protected async fromStore(data: Partial<HotbarStoreModel> = {}) {
|
||||||
if (data.hotbars?.length === 0) {
|
if (data.hotbars?.length === 0) {
|
||||||
this.hotbars = [{
|
this.hotbars = [{
|
||||||
name: "default",
|
id: uuid.v4(),
|
||||||
|
name: "Default",
|
||||||
items: []
|
items: []
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
this.hotbars = data.hotbars;
|
this.hotbars = data.hotbars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.activeHotbarId) {
|
||||||
|
if (this.getById(data.activeHotbarId)) {
|
||||||
|
this.activeHotbarId = data.activeHotbarId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.activeHotbarId) {
|
||||||
|
this.activeHotbarId = this.hotbars[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getActive() {
|
||||||
|
return this.getById(this.activeHotbarId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName(name: string) {
|
getByName(name: string) {
|
||||||
return this.hotbars.find((hotbar) => hotbar.name === name);
|
return this.hotbars.find((hotbar) => hotbar.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(hotbar: Hotbar) {
|
getById(id: string) {
|
||||||
this.hotbars.push(hotbar);
|
return this.hotbars.find((hotbar) => hotbar.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add(data: HotbarCreateOptions) {
|
||||||
|
const {
|
||||||
|
id = uuid.v4(),
|
||||||
|
items = [],
|
||||||
|
name,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
const hotbar = { id, name, items };
|
||||||
|
|
||||||
|
this.hotbars.push(hotbar as Hotbar);
|
||||||
|
|
||||||
|
return hotbar as Hotbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
remove(hotbar: Hotbar) {
|
remove(hotbar: Hotbar) {
|
||||||
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
|
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
|
||||||
|
|
||||||
|
if (this.activeHotbarId === hotbar.id) {
|
||||||
|
this.activeHotbarId = this.hotbars[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToPrevious() {
|
||||||
|
const hotbarStore = HotbarStore.getInstance();
|
||||||
|
let index = hotbarStore.activeHotbarIndex - 1;
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = hotbarStore.hotbars.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hotbarStore.activeHotbarId = hotbarStore.hotbars[index].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToNext() {
|
||||||
|
const hotbarStore = HotbarStore.getInstance();
|
||||||
|
let index = hotbarStore.activeHotbarIndex + 1;
|
||||||
|
|
||||||
|
if (index >= hotbarStore.hotbars.length) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hotbarStore.activeHotbarId = hotbarStore.hotbars[index].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): HotbarStoreModel {
|
toJSON(): HotbarStoreModel {
|
||||||
return toJS({
|
const model: HotbarStoreModel = {
|
||||||
hotbars: this.hotbars
|
hotbars: this.hotbars,
|
||||||
});
|
activeHotbarId: this.activeHotbarId
|
||||||
|
};
|
||||||
|
|
||||||
|
return toJS(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,3 @@
|
|||||||
/**
|
|
||||||
* Narrowing class instances to the one.
|
|
||||||
* Use "private" or "protected" modifier for constructor (when overriding) to disallow "new" usage.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const usersStore: UsersStore = UsersStore.getInstance();
|
|
||||||
*/
|
|
||||||
type StaticThis<T, R extends any[]> = { new(...args: R): T };
|
type StaticThis<T, R extends any[]> = { new(...args: R): T };
|
||||||
|
|
||||||
export class Singleton {
|
export class Singleton {
|
||||||
@ -13,12 +6,28 @@ export class Singleton {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (Singleton.creating.length === 0) {
|
if (Singleton.creating.length === 0) {
|
||||||
throw new TypeError("A singleton class must be created by getInstanceOrCreate()");
|
throw new TypeError("A singleton class must be created by createInstance()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstanceOrCreate<T, R extends any[]>(this: StaticThis<T, R>, ...args: R): T {
|
/**
|
||||||
|
* Creates the single instance of the child class if one was not already created.
|
||||||
|
*
|
||||||
|
* Multiple calls will return the same instance.
|
||||||
|
* Essentially throwing away the arguments to the subsequent calls.
|
||||||
|
*
|
||||||
|
* Note: this is a racy function, if two (or more) calls are racing to call this function
|
||||||
|
* only the first's arguments will be used.
|
||||||
|
* @param this Implicit argument that is the child class type
|
||||||
|
* @param args The constructor arguments for the child class
|
||||||
|
* @returns An instance of the child class
|
||||||
|
*/
|
||||||
|
static createInstance<T, R extends any[]>(this: StaticThis<T, R>, ...args: R): T {
|
||||||
if (!Singleton.instances.has(this)) {
|
if (!Singleton.instances.has(this)) {
|
||||||
|
if (Singleton.creating.length > 0) {
|
||||||
|
throw new TypeError("Cannot create a second singleton while creating a first");
|
||||||
|
}
|
||||||
|
|
||||||
Singleton.creating = this.name;
|
Singleton.creating = this.name;
|
||||||
Singleton.instances.set(this, new this(...args));
|
Singleton.instances.set(this, new this(...args));
|
||||||
Singleton.creating = "";
|
Singleton.creating = "";
|
||||||
@ -27,6 +36,13 @@ export class Singleton {
|
|||||||
return Singleton.instances.get(this) as T;
|
return Singleton.instances.get(this) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance of the child class that was previously created.
|
||||||
|
* @param this Implicit argument that is the child class type
|
||||||
|
* @param strict If false will return `undefined` instead of throwing when an instance doesn't exist.
|
||||||
|
* Default: `true`
|
||||||
|
* @returns An instance of the child class
|
||||||
|
*/
|
||||||
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict = true): T | undefined {
|
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict = true): T | undefined {
|
||||||
if (!Singleton.instances.has(this) && strict) {
|
if (!Singleton.instances.has(this) && strict) {
|
||||||
throw new TypeError(`instance of ${this.name} is not created`);
|
throw new TypeError(`instance of ${this.name} is not created`);
|
||||||
@ -35,6 +51,13 @@ export class Singleton {
|
|||||||
return Singleton.instances.get(this) as (T | undefined);
|
return Singleton.instances.get(this) as (T | undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the instance of the child class.
|
||||||
|
*
|
||||||
|
* Note: this doesn't prevent callers of `getInstance` from storing the result in a global.
|
||||||
|
*
|
||||||
|
* There is *no* way in JS or TS to prevent globals like that.
|
||||||
|
*/
|
||||||
static resetInstance() {
|
static resetInstance() {
|
||||||
Singleton.instances.delete(this);
|
Singleton.instances.delete(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,9 @@ export const isSnap = !!process.env.SNAP;
|
|||||||
export const isProduction = process.env.NODE_ENV === "production";
|
export const isProduction = process.env.NODE_ENV === "production";
|
||||||
export const isTestEnv = !!process.env.JEST_WORKER_ID;
|
export const isTestEnv = !!process.env.JEST_WORKER_ID;
|
||||||
export const isDevelopment = !isTestEnv && !isProduction;
|
export const isDevelopment = !isTestEnv && !isProduction;
|
||||||
|
export const isPublishConfigured = Object.keys(packageInfo.build).includes("publish");
|
||||||
|
|
||||||
|
export const productName = packageInfo.productName;
|
||||||
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`;
|
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`;
|
||||||
export const publicPath = "/build/";
|
export const publicPath = "/build/";
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ExtensionDiscovery.resetInstance();
|
ExtensionDiscovery.resetInstance();
|
||||||
ExtensionsStore.resetInstance();
|
ExtensionsStore.resetInstance();
|
||||||
ExtensionsStore.getInstanceOrCreate();
|
ExtensionsStore.createInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("emits add for added extension", async done => {
|
it("emits add for added extension", async done => {
|
||||||
@ -43,7 +43,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
mockedWatch.mockImplementationOnce(() =>
|
mockedWatch.mockImplementationOnce(() =>
|
||||||
(mockWatchInstance) as any
|
(mockWatchInstance) as any
|
||||||
);
|
);
|
||||||
const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate();
|
const extensionDiscovery = ExtensionDiscovery.createInstance();
|
||||||
|
|
||||||
// Need to force isLoaded to be true so that the file watching is started
|
// Need to force isLoaded to be true so that the file watching is started
|
||||||
extensionDiscovery.isLoaded = true;
|
extensionDiscovery.isLoaded = true;
|
||||||
@ -83,7 +83,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
mockedWatch.mockImplementationOnce(() =>
|
mockedWatch.mockImplementationOnce(() =>
|
||||||
(mockWatchInstance) as any
|
(mockWatchInstance) as any
|
||||||
);
|
);
|
||||||
const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate();
|
const extensionDiscovery = ExtensionDiscovery.createInstance();
|
||||||
|
|
||||||
// Need to force isLoaded to be true so that the file watching is started
|
// Need to force isLoaded to be true so that the file watching is started
|
||||||
extensionDiscovery.isLoaded = true;
|
extensionDiscovery.isLoaded = true;
|
||||||
|
|||||||
@ -110,7 +110,7 @@ describe("ExtensionLoader", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.only("renderer updates extension after ipc broadcast", async (done) => {
|
it.only("renderer updates extension after ipc broadcast", async (done) => {
|
||||||
const extensionLoader = ExtensionLoader.getInstanceOrCreate();
|
const extensionLoader = ExtensionLoader.createInstance();
|
||||||
|
|
||||||
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`);
|
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`);
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ describe("ExtensionLoader", () => {
|
|||||||
// Disable sending events in this test
|
// Disable sending events in this test
|
||||||
(ipcRenderer.on as any).mockImplementation();
|
(ipcRenderer.on as any).mockImplementation();
|
||||||
|
|
||||||
const extensionLoader = ExtensionLoader.getInstanceOrCreate();
|
const extensionLoader = ExtensionLoader.createInstance();
|
||||||
|
|
||||||
await extensionLoader.init();
|
await extensionLoader.init();
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@k8slens/extensions",
|
"name": "@k8slens/extensions",
|
||||||
"productName": "Lens extensions",
|
"productName": "OpenLens extensions",
|
||||||
"description": "Lens - The Kubernetes IDE: extensions",
|
"description": "OpenLens - Open Source Kubernetes IDE: extensions",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"copyright": "© 2021, Mirantis, Inc.",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "dist/src/extensions/extension-api.js",
|
"main": "dist/src/extensions/extension-api.js",
|
||||||
"types": "dist/src/extensions/extension-api.d.ts",
|
"types": "dist/src/extensions/extension-api.d.ts",
|
||||||
@ -13,8 +13,7 @@
|
|||||||
"dist/**/*.js"
|
"dist/**/*.js"
|
||||||
],
|
],
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mirantis, Inc.",
|
"name": "OpenLens Authors"
|
||||||
"email": "info@k8slens.dev"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
|||||||
@ -50,7 +50,7 @@ describe("kube auth proxy tests", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
UserStore.resetInstance();
|
UserStore.resetInstance();
|
||||||
UserStore.getInstanceOrCreate();
|
UserStore.createInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calling exit multiple times shouldn't throw", async () => {
|
it("calling exit multiple times shouldn't throw", async () => {
|
||||||
|
|||||||
@ -76,7 +76,7 @@ describe("kubeconfig manager tests", () => {
|
|||||||
});
|
});
|
||||||
const contextHandler = new ContextHandler(cluster);
|
const contextHandler = new ContextHandler(cluster);
|
||||||
const port = await getFreePort();
|
const port = await getFreePort();
|
||||||
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port);
|
const kubeConfManager = new KubeconfigManager(cluster, contextHandler, port);
|
||||||
|
|
||||||
expect(logger.error).not.toBeCalled();
|
expect(logger.error).not.toBeCalled();
|
||||||
expect(await kubeConfManager.getPath()).toBe(`tmp${path.sep}kubeconfig-foo`);
|
expect(await kubeConfManager.getPath()).toBe(`tmp${path.sep}kubeconfig-foo`);
|
||||||
@ -98,13 +98,15 @@ describe("kubeconfig manager tests", () => {
|
|||||||
});
|
});
|
||||||
const contextHandler = new ContextHandler(cluster);
|
const contextHandler = new ContextHandler(cluster);
|
||||||
const port = await getFreePort();
|
const port = await getFreePort();
|
||||||
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port);
|
const kubeConfManager = new KubeconfigManager(cluster, contextHandler, port);
|
||||||
const configPath = await kubeConfManager.getPath();
|
const configPath = await kubeConfManager.getPath();
|
||||||
|
|
||||||
expect(await fse.pathExists(configPath)).toBe(true);
|
expect(await fse.pathExists(configPath)).toBe(true);
|
||||||
await kubeConfManager.unlink();
|
await kubeConfManager.unlink();
|
||||||
expect(await fse.pathExists(configPath)).toBe(false);
|
expect(await fse.pathExists(configPath)).toBe(false);
|
||||||
await kubeConfManager.unlink(); // doesn't throw
|
await kubeConfManager.unlink(); // doesn't throw
|
||||||
expect(await kubeConfManager.getPath()).toBeUndefined();
|
expect(async () => {
|
||||||
|
await kubeConfManager.getPath();
|
||||||
|
}).rejects.toThrow("already unlinked");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { autoUpdater, UpdateInfo } from "electron-updater";
|
import { autoUpdater, UpdateInfo } from "electron-updater";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { isDevelopment, isTestEnv } from "../common/vars";
|
import { isDevelopment, isPublishConfigured, isTestEnv } from "../common/vars";
|
||||||
import { delay } from "../common/utils";
|
import { delay } from "../common/utils";
|
||||||
import { areArgsUpdateAvailableToBackchannel, AutoUpdateLogPrefix, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
import { areArgsUpdateAvailableToBackchannel, AutoUpdateLogPrefix, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
@ -8,6 +8,10 @@ import { app, ipcMain } from "electron";
|
|||||||
|
|
||||||
let installVersion: null | string = null;
|
let installVersion: null | string = null;
|
||||||
|
|
||||||
|
export function isAutoUpdateEnabled() {
|
||||||
|
return autoUpdater.isUpdaterActive() && isPublishConfigured;
|
||||||
|
}
|
||||||
|
|
||||||
function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: UpdateAvailableToBackchannel) {
|
function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: UpdateAvailableToBackchannel) {
|
||||||
if (arg.doUpdate) {
|
if (arg.doUpdate) {
|
||||||
if (arg.now) {
|
if (arg.now) {
|
||||||
|
|||||||
@ -293,7 +293,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
try {
|
try {
|
||||||
this.initializing = true;
|
this.initializing = true;
|
||||||
this.contextHandler = new ContextHandler(this);
|
this.contextHandler = new ContextHandler(this);
|
||||||
this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port);
|
this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler, port);
|
||||||
this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`;
|
this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`;
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
logger.info(`[CLUSTER]: "${this.contextName}" init success`, {
|
logger.info(`[CLUSTER]: "${this.contextName}" init success`, {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import "../common/prometheus-providers";
|
|||||||
import * as Mobx from "mobx";
|
import * as Mobx from "mobx";
|
||||||
import * as LensExtensions from "../extensions/core-api";
|
import * as LensExtensions from "../extensions/core-api";
|
||||||
import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron";
|
import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron";
|
||||||
import { appName, isMac } from "../common/vars";
|
import { appName, isMac, productName } from "../common/vars";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { LensProxy } from "./lens-proxy";
|
import { LensProxy } from "./lens-proxy";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
@ -38,12 +38,12 @@ const workingDir = path.join(app.getPath("appData"), appName);
|
|||||||
|
|
||||||
app.setName(appName);
|
app.setName(appName);
|
||||||
|
|
||||||
logger.info("📟 Setting Lens as protocol client for lens://");
|
logger.info(`📟 Setting ${productName} as protocol client for lens://`);
|
||||||
|
|
||||||
if (app.setAsDefaultProtocolClient("lens")) {
|
if (app.setAsDefaultProtocolClient("lens")) {
|
||||||
logger.info("📟 succeeded ✅");
|
logger.info("📟 Protocol client register succeeded ✅");
|
||||||
} else {
|
} else {
|
||||||
logger.info("📟 failed ❗");
|
logger.info("📟 Protocol client register failed ❗");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env.CICD) {
|
if (!process.env.CICD) {
|
||||||
@ -63,7 +63,7 @@ if (app.commandLine.getSwitchValue("proxy-server") !== "") {
|
|||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
app.exit();
|
app.exit();
|
||||||
} else {
|
} else {
|
||||||
const lprm = LensProtocolRouterMain.getInstanceOrCreate();
|
const lprm = LensProtocolRouterMain.createInstance();
|
||||||
|
|
||||||
for (const arg of process.argv) {
|
for (const arg of process.argv) {
|
||||||
if (arg.toLowerCase().startsWith("lens://")) {
|
if (arg.toLowerCase().startsWith("lens://")) {
|
||||||
@ -74,7 +74,7 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.on("second-instance", (event, argv) => {
|
app.on("second-instance", (event, argv) => {
|
||||||
const lprm = LensProtocolRouterMain.getInstanceOrCreate();
|
const lprm = LensProtocolRouterMain.createInstance();
|
||||||
|
|
||||||
for (const arg of argv) {
|
for (const arg of argv) {
|
||||||
if (arg.toLowerCase().startsWith("lens://")) {
|
if (arg.toLowerCase().startsWith("lens://")) {
|
||||||
@ -87,7 +87,7 @@ app.on("second-instance", (event, argv) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on("ready", async () => {
|
app.on("ready", async () => {
|
||||||
logger.info(`🚀 Starting Lens from "${workingDir}"`);
|
logger.info(`🚀 Starting ${productName} from "${workingDir}"`);
|
||||||
logger.info("🐚 Syncing shell environment");
|
logger.info("🐚 Syncing shell environment");
|
||||||
await shellSync();
|
await shellSync();
|
||||||
|
|
||||||
@ -99,11 +99,11 @@ app.on("ready", async () => {
|
|||||||
|
|
||||||
registerFileProtocol("static", __static);
|
registerFileProtocol("static", __static);
|
||||||
|
|
||||||
const userStore = UserStore.getInstanceOrCreate();
|
const userStore = UserStore.createInstance();
|
||||||
const clusterStore = ClusterStore.getInstanceOrCreate();
|
const clusterStore = ClusterStore.createInstance();
|
||||||
const hotbarStore = HotbarStore.getInstanceOrCreate();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const extensionsStore = ExtensionsStore.getInstanceOrCreate();
|
const extensionsStore = ExtensionsStore.createInstance();
|
||||||
const filesystemStore = FilesystemProvisionerStore.getInstanceOrCreate();
|
const filesystemStore = FilesystemProvisionerStore.createInstance();
|
||||||
|
|
||||||
logger.info("💾 Loading stores");
|
logger.info("💾 Loading stores");
|
||||||
// preload
|
// preload
|
||||||
@ -115,36 +115,35 @@ app.on("ready", async () => {
|
|||||||
filesystemStore.load(),
|
filesystemStore.load(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// find free port
|
|
||||||
let proxyPort;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info("🔑 Getting free port for LensProxy server");
|
logger.info("🔑 Getting free port for LensProxy server");
|
||||||
proxyPort = await getFreePort();
|
const proxyPort = await getFreePort();
|
||||||
|
|
||||||
|
// create cluster manager
|
||||||
|
ClusterManager.createInstance(proxyPort);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy");
|
dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy");
|
||||||
app.exit();
|
app.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create cluster manager
|
const clusterManager = ClusterManager.getInstance();
|
||||||
ClusterManager.getInstanceOrCreate(proxyPort);
|
|
||||||
|
|
||||||
// run proxy
|
// run proxy
|
||||||
try {
|
try {
|
||||||
logger.info("🔌 Starting LensProxy");
|
logger.info("🔌 Starting LensProxy");
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||||
LensProxy.getInstanceOrCreate(proxyPort).listen();
|
LensProxy.createInstance(clusterManager.port).listen();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error?.message}`);
|
logger.error(`Could not start proxy (127.0.0:${clusterManager.port}): ${error?.message}`);
|
||||||
dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error?.message || "unknown error"}`);
|
dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${clusterManager.port}): ${error?.message || "unknown error"}`);
|
||||||
app.exit();
|
app.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// test proxy connection
|
// test proxy connection
|
||||||
try {
|
try {
|
||||||
logger.info("🔎 Testing LensProxy connection ...");
|
logger.info("🔎 Testing LensProxy connection ...");
|
||||||
const versionFromProxy = await getAppVersionFromProxyServer(proxyPort);
|
const versionFromProxy = await getAppVersionFromProxyServer(clusterManager.port);
|
||||||
|
|
||||||
if (getAppVersion() !== versionFromProxy) {
|
if (getAppVersion() !== versionFromProxy) {
|
||||||
logger.error(`Proxy server responded with invalid response`);
|
logger.error(`Proxy server responded with invalid response`);
|
||||||
@ -154,9 +153,9 @@ app.on("ready", async () => {
|
|||||||
logger.error("Checking proxy server connection failed", error);
|
logger.error("Checking proxy server connection failed", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate();
|
const extensionDiscovery = ExtensionDiscovery.createInstance();
|
||||||
|
|
||||||
ExtensionLoader.getInstanceOrCreate().init();
|
ExtensionLoader.createInstance().init();
|
||||||
extensionDiscovery.init();
|
extensionDiscovery.init();
|
||||||
|
|
||||||
// Start the app without showing the main window when auto starting on login
|
// Start the app without showing the main window when auto starting on login
|
||||||
@ -164,7 +163,7 @@ app.on("ready", async () => {
|
|||||||
const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden);
|
const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden);
|
||||||
|
|
||||||
logger.info("🖥️ Starting WindowManager");
|
logger.info("🖥️ Starting WindowManager");
|
||||||
const windowManager = WindowManager.getInstanceOrCreate(proxyPort);
|
const windowManager = WindowManager.createInstance(clusterManager.port);
|
||||||
|
|
||||||
installDeveloperTools();
|
installDeveloperTools();
|
||||||
|
|
||||||
|
|||||||
@ -9,16 +9,39 @@ import logger from "./logger";
|
|||||||
|
|
||||||
export class KubeconfigManager {
|
export class KubeconfigManager {
|
||||||
protected configDir = app.getPath("temp");
|
protected configDir = app.getPath("temp");
|
||||||
protected tempFile: string;
|
protected tempFile: string = null;
|
||||||
|
|
||||||
private constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) { }
|
constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) { }
|
||||||
|
|
||||||
static async create(cluster: Cluster, contextHandler: ContextHandler, port: number) {
|
async getPath(): Promise<string> {
|
||||||
const kcm = new KubeconfigManager(cluster, contextHandler, port);
|
if (this.tempFile === undefined) {
|
||||||
|
throw new Error("kubeconfig is already unlinked");
|
||||||
|
}
|
||||||
|
|
||||||
await kcm.init();
|
if (!this.tempFile) {
|
||||||
|
await this.init();
|
||||||
|
}
|
||||||
|
|
||||||
return kcm;
|
// create proxy kubeconfig if it is removed without unlink called
|
||||||
|
if (!(await fs.pathExists(this.tempFile))) {
|
||||||
|
try {
|
||||||
|
this.tempFile = await this.createProxyKubeconfig();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Failed to created temp config for auth-proxy`, { err });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async unlink() {
|
||||||
|
if (!this.tempFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Deleting temporary kubeconfig: ${this.tempFile}`);
|
||||||
|
await fs.unlink(this.tempFile);
|
||||||
|
this.tempFile = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async init() {
|
protected async init() {
|
||||||
@ -30,20 +53,6 @@ export class KubeconfigManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPath() {
|
|
||||||
// create proxy kubeconfig if it is removed
|
|
||||||
if (this.tempFile !== undefined && !(await fs.pathExists(this.tempFile))) {
|
|
||||||
try {
|
|
||||||
this.tempFile = await this.createProxyKubeconfig();
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Failed to created temp config for auth-proxy`, { err });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tempFile;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected resolveProxyUrl() {
|
protected resolveProxyUrl() {
|
||||||
return `http://127.0.0.1:${this.port}/${this.cluster.id}`;
|
return `http://127.0.0.1:${this.port}/${this.cluster.id}`;
|
||||||
}
|
}
|
||||||
@ -87,14 +96,4 @@ export class KubeconfigManager {
|
|||||||
|
|
||||||
return tempFile;
|
return tempFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
async unlink() {
|
|
||||||
if (!this.tempFile) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Deleting temporary kubeconfig: ${this.tempFile}`);
|
|
||||||
await fs.unlink(this.tempFile);
|
|
||||||
this.tempFile = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { app, BrowserWindow, dialog, ipcMain, IpcMainEvent, Menu, MenuItem, MenuItemConstructorOptions, webContents, shell } from "electron";
|
import { app, BrowserWindow, dialog, ipcMain, IpcMainEvent, Menu, MenuItem, MenuItemConstructorOptions, webContents, shell } from "electron";
|
||||||
import { autorun } from "mobx";
|
import { autorun } from "mobx";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { appName, isMac, isWindows, isTestEnv, docsUrl, supportUrl } from "../common/vars";
|
import { appName, isMac, isWindows, isTestEnv, docsUrl, supportUrl, productName } from "../common/vars";
|
||||||
import { addClusterURL } from "../renderer/components/+add-cluster/add-cluster.route";
|
import { addClusterURL } from "../renderer/components/+add-cluster/add-cluster.route";
|
||||||
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
|
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
|
||||||
import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route";
|
import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route";
|
||||||
@ -34,7 +34,7 @@ export function showAbout(browserWindow: BrowserWindow) {
|
|||||||
title: `${isWindows ? " ".repeat(2) : ""}${appName}`,
|
title: `${isWindows ? " ".repeat(2) : ""}${appName}`,
|
||||||
type: "info",
|
type: "info",
|
||||||
buttons: ["Close"],
|
buttons: ["Close"],
|
||||||
message: `Lens`,
|
message: productName,
|
||||||
detail: appInfo.join("\r\n")
|
detail: appInfo.join("\r\n")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ export function buildMenu(windowManager: WindowManager) {
|
|||||||
label: app.getName(),
|
label: app.getName(),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: "About Lens",
|
label: `About ${productName}`,
|
||||||
click(menuItem: MenuItem, browserWindow: BrowserWindow) {
|
click(menuItem: MenuItem, browserWindow: BrowserWindow) {
|
||||||
showAbout(browserWindow);
|
showAbout(browserWindow);
|
||||||
}
|
}
|
||||||
@ -220,7 +220,7 @@ export function buildMenu(windowManager: WindowManager) {
|
|||||||
},
|
},
|
||||||
...ignoreOnMac([
|
...ignoreOnMac([
|
||||||
{
|
{
|
||||||
label: "About Lens",
|
label: `About ${productName}`,
|
||||||
click(menuItem: MenuItem, browserWindow: BrowserWindow) {
|
click(menuItem: MenuItem, browserWindow: BrowserWindow) {
|
||||||
showAbout(browserWindow);
|
showAbout(browserWindow);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,10 +17,10 @@ function throwIfDefined(val: any): void {
|
|||||||
|
|
||||||
describe("protocol router tests", () => {
|
describe("protocol router tests", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ExtensionsStore.getInstanceOrCreate();
|
ExtensionsStore.createInstance();
|
||||||
ExtensionLoader.getInstanceOrCreate();
|
ExtensionLoader.createInstance();
|
||||||
|
|
||||||
const lpr = LensProtocolRouterMain.getInstanceOrCreate();
|
const lpr = LensProtocolRouterMain.createInstance();
|
||||||
|
|
||||||
lpr.extensionsLoaded = true;
|
lpr.extensionsLoaded = true;
|
||||||
lpr.rendererLoaded = true;
|
lpr.rendererLoaded = true;
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import packageInfo from "../../package.json";
|
|||||||
import { Menu, Tray } from "electron";
|
import { Menu, Tray } from "electron";
|
||||||
import { autorun } from "mobx";
|
import { autorun } from "mobx";
|
||||||
import { showAbout } from "./menu";
|
import { showAbout } from "./menu";
|
||||||
import { checkForUpdates } from "./app-updater";
|
import { checkForUpdates, isAutoUpdateEnabled } from "./app-updater";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
|
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { isDevelopment, isWindows } from "../common/vars";
|
import { isDevelopment, isWindows, productName } from "../common/vars";
|
||||||
import { exitApp } from "./exit-app";
|
import { exitApp } from "./exit-app";
|
||||||
|
|
||||||
const TRAY_LOG_PREFIX = "[TRAY]";
|
const TRAY_LOG_PREFIX = "[TRAY]";
|
||||||
@ -58,9 +58,9 @@ export function initTray(windowManager: WindowManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createTrayMenu(windowManager: WindowManager): Menu {
|
function createTrayMenu(windowManager: WindowManager): Menu {
|
||||||
return Menu.buildFromTemplate([
|
const template: Electron.MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label: "Open Lens",
|
label: `Open ${productName}`,
|
||||||
click() {
|
click() {
|
||||||
windowManager
|
windowManager
|
||||||
.ensureMainWindow()
|
.ensureMainWindow()
|
||||||
@ -74,16 +74,22 @@ function createTrayMenu(windowManager: WindowManager): Menu {
|
|||||||
.navigate(preferencesURL())
|
.navigate(preferencesURL())
|
||||||
.catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to nativate to Preferences`, { error }));
|
.catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to nativate to Preferences`, { error }));
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
{
|
];
|
||||||
|
|
||||||
|
if (isAutoUpdateEnabled()) {
|
||||||
|
template.push({
|
||||||
label: "Check for updates",
|
label: "Check for updates",
|
||||||
click() {
|
click() {
|
||||||
checkForUpdates()
|
checkForUpdates()
|
||||||
.then(() => windowManager.ensureMainWindow());
|
.then(() => windowManager.ensureMainWindow());
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Menu.buildFromTemplate(template.concat([
|
||||||
{
|
{
|
||||||
label: "About Lens",
|
label: `About ${productName}`,
|
||||||
click() {
|
click() {
|
||||||
windowManager.ensureMainWindow()
|
windowManager.ensureMainWindow()
|
||||||
.then(showAbout)
|
.then(showAbout)
|
||||||
@ -97,5 +103,5 @@ function createTrayMenu(windowManager: WindowManager): Menu {
|
|||||||
exitApp();
|
exitApp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import { Hotbar } from "../../common/hotbar-store";
|
import { Hotbar } from "../../common/hotbar-store";
|
||||||
import { ClusterStore } from "../../common/cluster-store";
|
import { ClusterStore } from "../../common/cluster-store";
|
||||||
import { migration } from "../migration-wrapper";
|
import { migration } from "../migration-wrapper";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
export default migration({
|
export default migration({
|
||||||
version: "5.0.0-alpha.0",
|
version: "5.0.0-alpha.0",
|
||||||
@ -9,11 +10,14 @@ export default migration({
|
|||||||
const hotbars: Hotbar[] = [];
|
const hotbars: Hotbar[] = [];
|
||||||
|
|
||||||
ClusterStore.getInstance().enabledClustersList.forEach((cluster: any) => {
|
ClusterStore.getInstance().enabledClustersList.forEach((cluster: any) => {
|
||||||
const name = cluster.workspace || "default";
|
const name = cluster.workspace;
|
||||||
|
|
||||||
|
if (!name) return;
|
||||||
|
|
||||||
let hotbar = hotbars.find((h) => h.name === name);
|
let hotbar = hotbars.find((h) => h.name === name);
|
||||||
|
|
||||||
if (!hotbar) {
|
if (!hotbar) {
|
||||||
hotbar = { name, items: [] };
|
hotbar = { id: uuid(), name, items: [] };
|
||||||
hotbars.push(hotbar);
|
hotbars.push(hotbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
src/migrations/hotbar-store/5.0.0-alpha.2.ts
Normal file
16
src/migrations/hotbar-store/5.0.0-alpha.2.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Cleans up a store that had the state related data stored
|
||||||
|
import { Hotbar } from "../../common/hotbar-store";
|
||||||
|
import { migration } from "../migration-wrapper";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
|
export default migration({
|
||||||
|
version: "5.0.0-alpha.2",
|
||||||
|
run(store) {
|
||||||
|
const hotbars = (store.get("hotbars") || []) as Hotbar[];
|
||||||
|
|
||||||
|
store.set("hotbars", hotbars.map((hotbar) => ({
|
||||||
|
id: uuid.v4(),
|
||||||
|
...hotbar
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,7 +1,9 @@
|
|||||||
// Hotbar store migrations
|
// Hotbar store migrations
|
||||||
|
|
||||||
import version500alpha0 from "./5.0.0-alpha.0";
|
import version500alpha0 from "./5.0.0-alpha.0";
|
||||||
|
import version500alpha2 from "./5.0.0-alpha.2";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...version500alpha0,
|
...version500alpha0,
|
||||||
|
...version500alpha2
|
||||||
};
|
};
|
||||||
|
|||||||
@ -51,16 +51,16 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
await attachChromeDebugger();
|
await attachChromeDebugger();
|
||||||
rootElem.classList.toggle("is-mac", isMac);
|
rootElem.classList.toggle("is-mac", isMac);
|
||||||
|
|
||||||
ExtensionLoader.getInstanceOrCreate().init();
|
ExtensionLoader.createInstance().init();
|
||||||
ExtensionDiscovery.getInstanceOrCreate().init();
|
ExtensionDiscovery.createInstance().init();
|
||||||
|
|
||||||
const userStore = UserStore.getInstanceOrCreate();
|
const userStore = UserStore.createInstance();
|
||||||
const clusterStore = ClusterStore.getInstanceOrCreate();
|
const clusterStore = ClusterStore.createInstance();
|
||||||
const extensionsStore = ExtensionsStore.getInstanceOrCreate();
|
const extensionsStore = ExtensionsStore.createInstance();
|
||||||
const filesystemStore = FilesystemProvisionerStore.getInstanceOrCreate();
|
const filesystemStore = FilesystemProvisionerStore.createInstance();
|
||||||
const themeStore = ThemeStore.getInstanceOrCreate();
|
const themeStore = ThemeStore.createInstance();
|
||||||
const hotbarStore = HotbarStore.getInstanceOrCreate();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
const helmRepoManager = HelmRepoManager.getInstanceOrCreate();
|
const helmRepoManager = HelmRepoManager.createInstance();
|
||||||
|
|
||||||
// preload common stores
|
// preload common stores
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|||||||
@ -38,7 +38,10 @@ export class HelmChartStore extends ItemStore<HelmChart> {
|
|||||||
|
|
||||||
protected sortVersions = (versions: IChartVersion[]) => {
|
protected sortVersions = (versions: IChartVersion[]) => {
|
||||||
return versions.sort((first, second) => {
|
return versions.sort((first, second) => {
|
||||||
return semver.compare(second.version, first.version);
|
const firstVersion = semver.coerce(first.version || 0);
|
||||||
|
const secondVersion = semver.coerce(second.version || 0);
|
||||||
|
|
||||||
|
return semver.compare(secondVersion, firstVersion);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ export class Catalog extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addToHotbar(item: CatalogEntityItem) {
|
addToHotbar(item: CatalogEntityItem) {
|
||||||
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
|
const hotbar = HotbarStore.getInstance().getActive();
|
||||||
|
|
||||||
if (!hotbar) {
|
if (!hotbar) {
|
||||||
return;
|
return;
|
||||||
@ -71,16 +71,6 @@ export class Catalog extends React.Component {
|
|||||||
hotbar.items.push({ entity: { uid: item.id }});
|
hotbar.items.push({ entity: { uid: item.id }});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromHotbar(item: CatalogEntityItem) {
|
|
||||||
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
|
|
||||||
|
|
||||||
if (!hotbar) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hotbar.items = hotbar.items.filter((i) => i.entity.uid !== item.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDetails(item: CatalogEntityItem) {
|
onDetails(item: CatalogEntityItem) {
|
||||||
item.onRun(catalogEntityRunContext);
|
item.onRun(catalogEntityRunContext);
|
||||||
}
|
}
|
||||||
@ -142,9 +132,6 @@ export class Catalog extends React.Component {
|
|||||||
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }>
|
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }>
|
||||||
<Icon material="add" small interactive={true} title="Add to hotbar"/> Add to Hotbar
|
<Icon material="add" small interactive={true} title="Add to hotbar"/> Add to Hotbar
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem key="remove-from-hotbar" onClick={() => this.removeFromHotbar(item) }>
|
|
||||||
<Icon material="clear" small interactive={true} title="Remove from hotbar"/> Remove from Hotbar
|
|
||||||
</MenuItem>
|
|
||||||
{ menuItems.map((menuItem, index) => {
|
{ menuItems.map((menuItem, index) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem key={index} onClick={() => this.onMenuItemClick(menuItem)}>
|
<MenuItem key={index} onClick={() => this.onMenuItemClick(menuItem)}>
|
||||||
|
|||||||
@ -42,16 +42,16 @@ describe("Extensions", () => {
|
|||||||
UserStore.resetInstance();
|
UserStore.resetInstance();
|
||||||
ThemeStore.resetInstance();
|
ThemeStore.resetInstance();
|
||||||
|
|
||||||
await UserStore.getInstanceOrCreate().load();
|
await UserStore.createInstance().load();
|
||||||
await ThemeStore.getInstanceOrCreate().init();
|
await ThemeStore.createInstance().init();
|
||||||
|
|
||||||
ExtensionLoader.resetInstance();
|
ExtensionLoader.resetInstance();
|
||||||
ExtensionDiscovery.resetInstance();
|
ExtensionDiscovery.resetInstance();
|
||||||
Extensions.installStates.clear();
|
Extensions.installStates.clear();
|
||||||
|
|
||||||
ExtensionDiscovery.getInstanceOrCreate().uninstallExtension = jest.fn(() => Promise.resolve());
|
ExtensionDiscovery.createInstance().uninstallExtension = jest.fn(() => Promise.resolve());
|
||||||
|
|
||||||
ExtensionLoader.getInstanceOrCreate().addExtension({
|
ExtensionLoader.createInstance().addExtension({
|
||||||
id: "extensionId",
|
id: "extensionId",
|
||||||
manifest: {
|
manifest: {
|
||||||
name: "test",
|
name: "test",
|
||||||
|
|||||||
@ -44,8 +44,8 @@ const getFewPodsTabData = (): LogTabData => {
|
|||||||
|
|
||||||
describe("<LogResourceSelector />", () => {
|
describe("<LogResourceSelector />", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserStore.getInstanceOrCreate();
|
UserStore.createInstance();
|
||||||
ThemeStore.getInstanceOrCreate();
|
ThemeStore.createInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
50
src/renderer/components/hotbar/hotbar-add-command.tsx
Normal file
50
src/renderer/components/hotbar/hotbar-add-command.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
import { CommandOverlay } from "../command-palette";
|
||||||
|
import { Input, InputValidator } from "../input";
|
||||||
|
|
||||||
|
const uniqueHotbarName: InputValidator = {
|
||||||
|
condition: ({ required }) => required,
|
||||||
|
message: () => "Hotbar with this name already exists",
|
||||||
|
validate: value => !HotbarStore.getInstance().getByName(value),
|
||||||
|
};
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class HotbarAddCommand extends React.Component {
|
||||||
|
|
||||||
|
onSubmit(name: string) {
|
||||||
|
if (!name.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hotbarStore = HotbarStore.getInstance();
|
||||||
|
|
||||||
|
const hotbar = hotbarStore.add({
|
||||||
|
name
|
||||||
|
});
|
||||||
|
|
||||||
|
hotbarStore.activeHotbarId = hotbar.id;
|
||||||
|
|
||||||
|
CommandOverlay.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
placeholder="Hotbar name"
|
||||||
|
autoFocus={true}
|
||||||
|
theme="round-black"
|
||||||
|
data-test-id="command-palette-hotbar-add-name"
|
||||||
|
validators={[uniqueHotbarName]}
|
||||||
|
onSubmit={(v) => this.onSubmit(v)}
|
||||||
|
dirty={true}
|
||||||
|
showValidationLine={true} />
|
||||||
|
<small className="hint">
|
||||||
|
Please provide a new hotbar name (Press "Enter" to confirm or "Escape" to cancel)
|
||||||
|
</small>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -65,7 +65,7 @@ export class HotbarIcon extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeFromHotbar(item: CatalogEntity) {
|
removeFromHotbar(item: CatalogEntity) {
|
||||||
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
|
const hotbar = HotbarStore.getInstance().getActive();
|
||||||
|
|
||||||
if (!hotbar) {
|
if (!hotbar) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -22,4 +22,14 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.HotbarSelector {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.Badge {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import "./hotbar-menu.scss";
|
import "./hotbar-menu.scss";
|
||||||
|
import "./hotbar.commands";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
@ -7,6 +8,10 @@ import { cssNames, IClassName } from "../../utils";
|
|||||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
import { HotbarStore } from "../../../common/hotbar-store";
|
||||||
import { catalogEntityRunContext } from "../../api/catalog-entity";
|
import { catalogEntityRunContext } from "../../api/catalog-entity";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { Badge } from "../badge";
|
||||||
|
import { CommandOverlay } from "../command-palette";
|
||||||
|
import { HotbarSwitchCommand } from "./hotbar-switch-command";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
@ -14,9 +19,8 @@ interface Props {
|
|||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class HotbarMenu extends React.Component<Props> {
|
export class HotbarMenu extends React.Component<Props> {
|
||||||
|
|
||||||
get hotbarItems() {
|
get hotbarItems() {
|
||||||
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
|
const hotbar = HotbarStore.getInstance().getActive();
|
||||||
|
|
||||||
if (!hotbar) {
|
if (!hotbar) {
|
||||||
return [];
|
return [];
|
||||||
@ -25,8 +29,21 @@ export class HotbarMenu extends React.Component<Props> {
|
|||||||
return hotbar.items.map((item) => catalogEntityRegistry.items.find((entity) => entity.metadata.uid === item.entity.uid)).filter(Boolean);
|
return hotbar.items.map((item) => catalogEntityRegistry.items.find((entity) => entity.metadata.uid === item.entity.uid)).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previous() {
|
||||||
|
HotbarStore.getInstance().switchToPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
HotbarStore.getInstance().switchToNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
openSelector() {
|
||||||
|
CommandOverlay.open(<HotbarSwitchCommand />);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className } = this.props;
|
const { className } = this.props;
|
||||||
|
const hotbarIndex = HotbarStore.getInstance().activeHotbarIndex + 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("HotbarMenu flex column", className)}>
|
<div className={cssNames("HotbarMenu flex column", className)}>
|
||||||
@ -43,6 +60,13 @@ export class HotbarMenu extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="HotbarSelector flex gaps auto">
|
||||||
|
<Icon material="chevron_left" className="previous box" onClick={() => this.previous()} />
|
||||||
|
<div className="box">
|
||||||
|
<Badge small label={hotbarIndex} onClick={() => this.openSelector()} />
|
||||||
|
</div>
|
||||||
|
<Icon material="chevron_right" className="next box" onClick={() => this.next()} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/renderer/components/hotbar/hotbar-remove-command.tsx
Normal file
57
src/renderer/components/hotbar/hotbar-remove-command.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { Select } from "../select";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
import { CommandOverlay } from "../command-palette";
|
||||||
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class HotbarRemoveCommand extends React.Component {
|
||||||
|
@computed get options() {
|
||||||
|
return HotbarStore.getInstance().hotbars.map((hotbar) => {
|
||||||
|
return { value: hotbar.id, label: hotbar.name };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(id: string): void {
|
||||||
|
const hotbarStore = HotbarStore.getInstance();
|
||||||
|
const hotbar = hotbarStore.getById(id);
|
||||||
|
|
||||||
|
if (!hotbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandOverlay.close();
|
||||||
|
ConfirmDialog.open({
|
||||||
|
okButtonProps: {
|
||||||
|
label: `Remove Hotbar`,
|
||||||
|
primary: false,
|
||||||
|
accent: true,
|
||||||
|
},
|
||||||
|
ok: () => {
|
||||||
|
hotbarStore.remove(hotbar);
|
||||||
|
},
|
||||||
|
message: (
|
||||||
|
<div className="confirm flex column gaps">
|
||||||
|
<p>
|
||||||
|
Are you sure you want remove hotbar <b>{hotbar.name}</b>?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
onChange={(v) => this.onChange(v.value)}
|
||||||
|
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||||
|
menuIsOpen={true}
|
||||||
|
options={this.options}
|
||||||
|
autoFocus={true}
|
||||||
|
escapeClearsValue={false}
|
||||||
|
placeholder="Remove hotbar" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/renderer/components/hotbar/hotbar-switch-command.tsx
Normal file
58
src/renderer/components/hotbar/hotbar-switch-command.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { Select } from "../select";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
import { CommandOverlay } from "../command-palette";
|
||||||
|
import { HotbarAddCommand } from "./hotbar-add-command";
|
||||||
|
import { HotbarRemoveCommand } from "./hotbar-remove-command";
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class HotbarSwitchCommand extends React.Component {
|
||||||
|
private static addActionId = "__add__";
|
||||||
|
private static removeActionId = "__remove__";
|
||||||
|
|
||||||
|
@computed get options() {
|
||||||
|
const hotbarStore = HotbarStore.getInstance();
|
||||||
|
const options = hotbarStore.hotbars.map((hotbar) => {
|
||||||
|
return { value: hotbar.id, label: hotbar.name };
|
||||||
|
});
|
||||||
|
|
||||||
|
options.push({ value: HotbarSwitchCommand.addActionId, label: "Add hotbar ..." });
|
||||||
|
|
||||||
|
if (hotbarStore.hotbars.length > 1) {
|
||||||
|
options.push({ value: HotbarSwitchCommand.removeActionId, label: "Remove hotbar ..." });
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(idOrAction: string): void {
|
||||||
|
switch(idOrAction) {
|
||||||
|
case HotbarSwitchCommand.addActionId:
|
||||||
|
CommandOverlay.open(<HotbarAddCommand />);
|
||||||
|
|
||||||
|
return;
|
||||||
|
case HotbarSwitchCommand.removeActionId:
|
||||||
|
CommandOverlay.open(<HotbarRemoveCommand />);
|
||||||
|
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
HotbarStore.getInstance().activeHotbarId = idOrAction;
|
||||||
|
CommandOverlay.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
onChange={(v) => this.onChange(v.value)}
|
||||||
|
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||||
|
menuIsOpen={true}
|
||||||
|
options={this.options}
|
||||||
|
autoFocus={true}
|
||||||
|
escapeClearsValue={false}
|
||||||
|
placeholder="Switch to hotbar" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/renderer/components/hotbar/hotbar.commands.tsx
Normal file
27
src/renderer/components/hotbar/hotbar.commands.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { commandRegistry } from "../../../extensions/registries";
|
||||||
|
import { CommandOverlay } from "../command-palette";
|
||||||
|
import { HotbarAddCommand } from "./hotbar-add-command";
|
||||||
|
import { HotbarRemoveCommand } from "./hotbar-remove-command";
|
||||||
|
import { HotbarSwitchCommand } from "./hotbar-switch-command";
|
||||||
|
|
||||||
|
commandRegistry.add({
|
||||||
|
id: "hotbar.switchHotbar",
|
||||||
|
title: "Hotbar: Switch ...",
|
||||||
|
scope: "global",
|
||||||
|
action: () => CommandOverlay.open(<HotbarSwitchCommand />)
|
||||||
|
});
|
||||||
|
|
||||||
|
commandRegistry.add({
|
||||||
|
id: "hotbar.addHotbar",
|
||||||
|
title: "Hotbar: Add Hotbar ...",
|
||||||
|
scope: "global",
|
||||||
|
action: () => CommandOverlay.open(<HotbarAddCommand />)
|
||||||
|
});
|
||||||
|
|
||||||
|
commandRegistry.add({
|
||||||
|
id: "hotbar.removeHotbar",
|
||||||
|
title: "Hotbar: Remove Hotbar ...",
|
||||||
|
scope: "global",
|
||||||
|
action: () => CommandOverlay.open(<HotbarRemoveCommand />)
|
||||||
|
});
|
||||||
@ -16,7 +16,7 @@ const cluster: Cluster = new Cluster({
|
|||||||
|
|
||||||
describe("<MainLayoutHeader />", () => {
|
describe("<MainLayoutHeader />", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.getInstanceOrCreate();
|
ClusterStore.createInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ClusterStore } from "../../common/cluster-store";
|
|||||||
import { InvalidKubeConfigArgs, InvalidKubeconfigChannel } from "../../common/ipc/invalid-kubeconfig";
|
import { InvalidKubeConfigArgs, InvalidKubeconfigChannel } from "../../common/ipc/invalid-kubeconfig";
|
||||||
import { Notifications, notificationsStore } from "../components/notifications";
|
import { Notifications, notificationsStore } from "../components/notifications";
|
||||||
import { Button } from "../components/button";
|
import { Button } from "../components/button";
|
||||||
|
import { productName } from "../../common/vars";
|
||||||
|
|
||||||
export const invalidKubeconfigHandler = {
|
export const invalidKubeconfigHandler = {
|
||||||
source: ipcRenderer,
|
source: ipcRenderer,
|
||||||
@ -24,7 +25,7 @@ function InvalidKubeconfigListener(event: IpcRendererEvent, ...[clusterId]: Inva
|
|||||||
<div className="flex column gaps">
|
<div className="flex column gaps">
|
||||||
<b>Cluster with Invalid Kubeconfig Detected!</b>
|
<b>Cluster with Invalid Kubeconfig Detected!</b>
|
||||||
<p>Cluster <b>{cluster.name}</b> has invalid kubeconfig {contextName} and cannot be displayed.
|
<p>Cluster <b>{cluster.name}</b> has invalid kubeconfig {contextName} and cannot be displayed.
|
||||||
Please fix the <a href="#" onClick={(e) => { e.preventDefault(); shell.showItemInFolder(cluster.kubeConfigPath); }}>kubeconfig</a> manually and restart Lens
|
Please fix the <a href="#" onClick={(e) => { e.preventDefault(); shell.showItemInFolder(cluster.kubeConfigPath); }}>kubeconfig</a> manually and restart {productName}
|
||||||
or remove the cluster.</p>
|
or remove the cluster.</p>
|
||||||
<p>Do you want to remove the cluster now?</p>
|
<p>Do you want to remove the cluster now?</p>
|
||||||
<div className="flex gaps row align-left box grow">
|
<div className="flex gaps row align-left box grow">
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export class LensApp extends React.Component {
|
|||||||
static async init() {
|
static async init() {
|
||||||
catalogEntityRegistry.init();
|
catalogEntityRegistry.init();
|
||||||
ExtensionLoader.getInstance().loadOnClusterManagerRenderer();
|
ExtensionLoader.getInstance().loadOnClusterManagerRenderer();
|
||||||
LensProtocolRouterRenderer.getInstanceOrCreate().init();
|
LensProtocolRouterRenderer.createInstance().init();
|
||||||
bindProtocolAddRouteHandlers();
|
bindProtocolAddRouteHandlers();
|
||||||
|
|
||||||
window.addEventListener("offline", () => broadcastMessage("network:offline"));
|
window.addEventListener("offline", () => broadcastMessage("network:offline"));
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Lens - The Kubernetes IDE</title>
|
<title>OpenLens - Open Source Kubernetes IDE</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { ClusterStore } from "../../../common/cluster-store";
|
|||||||
|
|
||||||
describe("renderer/utils/StorageHelper", () => {
|
describe("renderer/utils/StorageHelper", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.getInstanceOrCreate();
|
ClusterStore.createInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user