mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
auto enable/disable extensions -- part 1
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
cefa8d8880
commit
addc55e577
@ -1,18 +1,21 @@
|
|||||||
import type { LensExtension, LensExtensionConstructor, LensExtensionManifest } from "./lens-extension"
|
import type { LensExtension, LensExtensionConstructor, LensExtensionId, LensExtensionManifest, LensExtensionStoreModel } from "./lens-extension"
|
||||||
import type { LensMainExtension } from "./lens-main-extension"
|
import type { LensMainExtension } from "./lens-main-extension"
|
||||||
import type { LensRendererExtension } from "./lens-renderer-extension"
|
import type { LensRendererExtension } from "./lens-renderer-extension"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { broadcastIpc } from "../common/ipc"
|
import { broadcastIpc } from "../common/ipc"
|
||||||
import { computed, observable, reaction, toJS, } from "mobx"
|
import { action, computed, observable, reaction, toJS } from "mobx"
|
||||||
import logger from "../main/logger"
|
import logger from "../main/logger"
|
||||||
import { app, ipcRenderer, remote } from "electron"
|
import { app, ipcRenderer, remote } from "electron"
|
||||||
|
import { BaseStore } from "../common/base-store";
|
||||||
import * as registries from "./registries";
|
import * as registries from "./registries";
|
||||||
|
|
||||||
type ExtensionManifestPath = string; // path to package.json
|
export interface ExtensionLoaderStoreModel {
|
||||||
|
extensions: LensExtensionStoreModel[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface InstalledExtension {
|
export interface InstalledExtension {
|
||||||
manifestPath: string;
|
|
||||||
manifest: LensExtensionManifest;
|
manifest: LensExtensionManifest;
|
||||||
|
manifestPath: string;
|
||||||
isBundled?: boolean; // defined in package.json
|
isBundled?: boolean; // defined in package.json
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,19 +24,23 @@ export function extensionPackagesRoot() {
|
|||||||
return path.join((app || remote.app).getPath("userData"))
|
return path.join((app || remote.app).getPath("userData"))
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExtensionLoader {
|
export class ExtensionLoader extends BaseStore<ExtensionLoaderStoreModel> {
|
||||||
@observable extensions = observable.map<ExtensionManifestPath, InstalledExtension>([], { deep: false });
|
@observable extensions = observable.map<LensExtensionId, InstalledExtension>([], { deep: false });
|
||||||
@observable instances = observable.map<ExtensionManifestPath, LensExtension>([], { deep: false })
|
@observable instances = observable.map<LensExtensionId, LensExtension>([], { deep: false })
|
||||||
|
@observable state = observable.map<LensExtensionId, LensExtensionStoreModel>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super({
|
||||||
|
configName: "lens-extensions",
|
||||||
|
});
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => {
|
ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => {
|
||||||
extensions.forEach((ext) => {
|
extensions.forEach((ext) => {
|
||||||
if (!this.getByManifest(ext.manifestPath)) {
|
if (!this.extensions.has(ext.manifestPath)) {
|
||||||
this.extensions.set(ext.manifestPath, ext)
|
this.extensions.set(ext.manifestPath, ext)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,14 +50,16 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
loadOnMain() {
|
loadOnMain() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
logger.info('[EXTENSIONS-LOADER]: load on main')
|
||||||
this.autoloadExtensions((extension: LensMainExtension) => {
|
this.autoInitExtensions();
|
||||||
|
this.autoEnableExtensions((extension: LensMainExtension) => {
|
||||||
extension.registerTo(registries.menuRegistry, extension.appMenus)
|
extension.registerTo(registries.menuRegistry, extension.appMenus)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
||||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
this.autoInitExtensions();
|
||||||
|
this.autoEnableExtensions((extension: LensRendererExtension) => {
|
||||||
extension.registerTo(registries.globalPageRegistry, extension.globalPages)
|
extension.registerTo(registries.globalPageRegistry, extension.globalPages)
|
||||||
extension.registerTo(registries.appPreferenceRegistry, extension.appPreferences)
|
extension.registerTo(registries.appPreferenceRegistry, extension.appPreferences)
|
||||||
extension.registerTo(registries.clusterFeatureRegistry, extension.clusterFeatures)
|
extension.registerTo(registries.clusterFeatureRegistry, extension.clusterFeatures)
|
||||||
@ -60,14 +69,32 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
loadOnClusterRenderer() {
|
loadOnClusterRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
||||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
this.autoInitExtensions();
|
||||||
|
this.autoEnableExtensions((extension: LensRendererExtension) => {
|
||||||
extension.registerTo(registries.clusterPageRegistry, extension.clusterPages)
|
extension.registerTo(registries.clusterPageRegistry, extension.clusterPages)
|
||||||
extension.registerTo(registries.kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
|
extension.registerTo(registries.kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
|
||||||
extension.registerTo(registries.kubeObjectDetailRegistry, extension.kubeObjectDetailItems)
|
extension.registerTo(registries.kubeObjectDetailRegistry, extension.kubeObjectDetailItems)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected autoloadExtensions(callback: (instance: LensExtension) => void) {
|
protected autoEnableExtensions(callback: (ext: LensExtension) => void) {
|
||||||
|
return reaction(() => this.instances.toJS(), instances => {
|
||||||
|
instances.forEach(ext => {
|
||||||
|
const extensionState = this.state.get(ext.id);
|
||||||
|
const enabledInStore = !extensionState /*enabled by default*/ || extensionState.isEnabled;
|
||||||
|
if (!ext.isEnabled && enabledInStore) {
|
||||||
|
ext.enable();
|
||||||
|
callback(ext);
|
||||||
|
} else if (ext.isEnabled && !enabledInStore) {
|
||||||
|
ext.disable();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
fireImmediately: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected autoInitExtensions() {
|
||||||
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
||||||
for (const [id, ext] of installedExtensions) {
|
for (const [id, ext] of installedExtensions) {
|
||||||
let instance = this.instances.get(ext.manifestPath)
|
let instance = this.instances.get(ext.manifestPath)
|
||||||
@ -79,17 +106,14 @@ export class ExtensionLoader {
|
|||||||
try {
|
try {
|
||||||
const LensExtensionClass: LensExtensionConstructor = extensionModule.default;
|
const LensExtensionClass: LensExtensionConstructor = extensionModule.default;
|
||||||
instance = new LensExtensionClass(ext);
|
instance = new LensExtensionClass(ext);
|
||||||
instance.enable()
|
this.instances.set(ext.manifestPath, instance);
|
||||||
callback(instance)
|
|
||||||
this.instances.set(ext.manifestPath, instance)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[EXTENSIONS-LOADER]: activating extension error`, { ext, err })
|
logger.error(`[EXTENSIONS-LOADER]: init extension instance error`, { ext, err })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
delay: 0,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,26 +134,31 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getByManifest(manifestPath: ExtensionManifestPath): InstalledExtension {
|
|
||||||
return this.extensions.get(manifestPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcastExtensions(frameId?: number) {
|
broadcastExtensions(frameId?: number) {
|
||||||
broadcastIpc({
|
broadcastIpc({
|
||||||
channel: "extensions:loaded",
|
channel: "extensions:loaded",
|
||||||
frameId: frameId,
|
frameId: frameId,
|
||||||
frameOnly: !!frameId,
|
frameOnly: !!frameId,
|
||||||
args: [this.toJSON().extensions],
|
args: [
|
||||||
|
Array.from(this.extensions.toJS().values())
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
@action
|
||||||
|
protected fromStore({ extensions = [] }: ExtensionLoaderStoreModel) {
|
||||||
|
extensions.forEach(ext => {
|
||||||
|
this.state.set(ext.id, ext);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): ExtensionLoaderStoreModel {
|
||||||
return toJS({
|
return toJS({
|
||||||
extensions: Array.from(this.extensions).map(([id, instance]) => instance),
|
extensions: this.userExtensions.map(ext => ext.toJSON())
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true,
|
recurseEverything: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionLoader = new ExtensionLoader()
|
export const extensionLoader: ExtensionLoader = ExtensionLoader.getInstance();
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
import { observable, toJS } from "mobx";
|
import { action, observable, toJS } from "mobx";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { BaseRegistry } from "./registries/base-registry";
|
import { BaseRegistry } from "./registries/base-registry";
|
||||||
import type { InstalledExtension } from "./extension-loader";
|
import type { InstalledExtension } from "./extension-loader";
|
||||||
|
|
||||||
|
export type LensExtensionId = string; // path to manifest (package.json)
|
||||||
export type LensExtensionConstructor = new (init: InstalledExtension) => LensExtension;
|
export type LensExtensionConstructor = new (init: InstalledExtension) => LensExtension;
|
||||||
|
|
||||||
|
export interface LensExtensionStoreModel {
|
||||||
|
id: LensExtensionId;
|
||||||
|
name: string;
|
||||||
|
isEnabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LensExtensionManifest {
|
export interface LensExtensionManifest {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
@ -27,6 +34,10 @@ export class LensExtension {
|
|||||||
this.isBundled = !!isBundled
|
this.isBundled = !!isBundled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get id(): LensExtensionId {
|
||||||
|
return this.manifestPath;
|
||||||
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return this.manifest.name
|
return this.manifest.name
|
||||||
}
|
}
|
||||||
@ -39,13 +50,17 @@ export class LensExtension {
|
|||||||
return this.manifest.description
|
return this.manifest.description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async enable() {
|
async enable() {
|
||||||
|
if (this.isEnabled) return;
|
||||||
this.isEnabled = true;
|
this.isEnabled = true;
|
||||||
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||||
this.onActivate();
|
this.onActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async disable() {
|
async disable() {
|
||||||
|
if (!this.isEnabled) return;
|
||||||
this.onDeactivate();
|
this.onDeactivate();
|
||||||
this.isEnabled = false;
|
this.isEnabled = false;
|
||||||
this.disposers.forEach(cleanUp => cleanUp());
|
this.disposers.forEach(cleanUp => cleanUp());
|
||||||
@ -53,6 +68,14 @@ export class LensExtension {
|
|||||||
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggle(enable?: boolean) {
|
||||||
|
if (typeof enable === "boolean") {
|
||||||
|
enable ? this.enable() : this.disable()
|
||||||
|
} else {
|
||||||
|
this.isEnabled ? this.disable() : this.enable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected onActivate() {
|
protected onActivate() {
|
||||||
// mock
|
// mock
|
||||||
}
|
}
|
||||||
@ -69,12 +92,10 @@ export class LensExtension {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON(): LensExtensionStoreModel {
|
||||||
return toJS({
|
return toJS({
|
||||||
|
id: this.id,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
version: this.version,
|
|
||||||
description: this.description,
|
|
||||||
manifestPath: this.manifestPath,
|
|
||||||
isEnabled: this.isEnabled,
|
isEnabled: this.isEnabled,
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true,
|
recurseEverything: true,
|
||||||
|
|||||||
@ -53,6 +53,7 @@ app.on("ready", async () => {
|
|||||||
userStore.load(),
|
userStore.load(),
|
||||||
clusterStore.load(),
|
clusterStore.load(),
|
||||||
workspaceStore.load(),
|
workspaceStore.load(),
|
||||||
|
extensionLoader.load(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// find free port
|
// find free port
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { i18nStore } from "./i18n";
|
|||||||
import { themeStore } from "./theme.store";
|
import { themeStore } from "./theme.store";
|
||||||
import { App } from "./components/app";
|
import { App } from "./components/app";
|
||||||
import { LensApp } from "./lens-app";
|
import { LensApp } from "./lens-app";
|
||||||
|
import { extensionLoader } from "../extensions/extension-loader";
|
||||||
|
|
||||||
type AppComponent = React.ComponentType & {
|
type AppComponent = React.ComponentType & {
|
||||||
init?(): Promise<void>;
|
init?(): Promise<void>;
|
||||||
@ -34,6 +35,7 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
userStore.load(),
|
userStore.load(),
|
||||||
workspaceStore.load(),
|
workspaceStore.load(),
|
||||||
clusterStore.load(),
|
clusterStore.load(),
|
||||||
|
extensionLoader.load(),
|
||||||
i18nStore.init(),
|
i18nStore.init(),
|
||||||
themeStore.init(),
|
themeStore.init(),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -67,9 +67,9 @@ export class Extensions extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return extensions.map(ext => {
|
return extensions.map(ext => {
|
||||||
const { manifestPath, name, description, isEnabled } = ext;
|
const { id, name, description, isEnabled } = ext;
|
||||||
return (
|
return (
|
||||||
<div key={manifestPath} className="extension flex gaps align-center">
|
<div key={id} className="extension flex gaps align-center">
|
||||||
<div className="box grow flex column gaps">
|
<div className="box grow flex column gaps">
|
||||||
<div className="package">
|
<div className="package">
|
||||||
Name: <code className="name">{name}</code>
|
Name: <code className="name">{name}</code>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user