1
0
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:
Roman 2020-11-03 08:08:52 +02:00
parent cefa8d8880
commit addc55e577
5 changed files with 86 additions and 33 deletions

View File

@ -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 { LensRendererExtension } from "./lens-renderer-extension"
import path from "path"
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 { app, ipcRenderer, remote } from "electron"
import { BaseStore } from "../common/base-store";
import * as registries from "./registries";
type ExtensionManifestPath = string; // path to package.json
export interface ExtensionLoaderStoreModel {
extensions: LensExtensionStoreModel[]
}
export interface InstalledExtension {
manifestPath: string;
manifest: LensExtensionManifest;
manifestPath: string;
isBundled?: boolean; // defined in package.json
}
@ -21,19 +24,23 @@ export function extensionPackagesRoot() {
return path.join((app || remote.app).getPath("userData"))
}
export class ExtensionLoader {
@observable extensions = observable.map<ExtensionManifestPath, InstalledExtension>([], { deep: false });
@observable instances = observable.map<ExtensionManifestPath, LensExtension>([], { deep: false })
export class ExtensionLoader extends BaseStore<ExtensionLoaderStoreModel> {
@observable extensions = observable.map<LensExtensionId, InstalledExtension>([], { deep: false });
@observable instances = observable.map<LensExtensionId, LensExtension>([], { deep: false })
@observable state = observable.map<LensExtensionId, LensExtensionStoreModel>();
constructor() {
super({
configName: "lens-extensions",
});
if (ipcRenderer) {
ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => {
extensions.forEach((ext) => {
if (!this.getByManifest(ext.manifestPath)) {
if (!this.extensions.has(ext.manifestPath)) {
this.extensions.set(ext.manifestPath, ext)
}
})
})
});
}
}
@ -43,14 +50,16 @@ export class ExtensionLoader {
loadOnMain() {
logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoloadExtensions((extension: LensMainExtension) => {
this.autoInitExtensions();
this.autoEnableExtensions((extension: LensMainExtension) => {
extension.registerTo(registries.menuRegistry, extension.appMenus)
})
}
loadOnClusterManagerRenderer() {
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.appPreferenceRegistry, extension.appPreferences)
extension.registerTo(registries.clusterFeatureRegistry, extension.clusterFeatures)
@ -60,14 +69,32 @@ export class ExtensionLoader {
loadOnClusterRenderer() {
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.kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
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) => {
for (const [id, ext] of installedExtensions) {
let instance = this.instances.get(ext.manifestPath)
@ -79,17 +106,14 @@ export class ExtensionLoader {
try {
const LensExtensionClass: LensExtensionConstructor = extensionModule.default;
instance = new LensExtensionClass(ext);
instance.enable()
callback(instance)
this.instances.set(ext.manifestPath, instance)
this.instances.set(ext.manifestPath, instance);
} catch (err) {
logger.error(`[EXTENSIONS-LOADER]: activating extension error`, { ext, err })
logger.error(`[EXTENSIONS-LOADER]: init extension instance error`, { ext, err })
}
}
}
}, {
fireImmediately: true,
delay: 0,
})
}
@ -110,26 +134,31 @@ export class ExtensionLoader {
}
}
getByManifest(manifestPath: ExtensionManifestPath): InstalledExtension {
return this.extensions.get(manifestPath);
}
broadcastExtensions(frameId?: number) {
broadcastIpc({
channel: "extensions:loaded",
frameId: 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({
extensions: Array.from(this.extensions).map(([id, instance]) => instance),
extensions: this.userExtensions.map(ext => ext.toJSON())
}, {
recurseEverything: true,
})
}
}
export const extensionLoader = new ExtensionLoader()
export const extensionLoader: ExtensionLoader = ExtensionLoader.getInstance();

View File

@ -1,10 +1,17 @@
import { observable, toJS } from "mobx";
import { action, observable, toJS } from "mobx";
import logger from "../main/logger";
import { BaseRegistry } from "./registries/base-registry";
import type { InstalledExtension } from "./extension-loader";
export type LensExtensionId = string; // path to manifest (package.json)
export type LensExtensionConstructor = new (init: InstalledExtension) => LensExtension;
export interface LensExtensionStoreModel {
id: LensExtensionId;
name: string;
isEnabled?: boolean;
}
export interface LensExtensionManifest {
name: string;
version: string;
@ -27,6 +34,10 @@ export class LensExtension {
this.isBundled = !!isBundled
}
get id(): LensExtensionId {
return this.manifestPath;
}
get name() {
return this.manifest.name
}
@ -39,13 +50,17 @@ export class LensExtension {
return this.manifest.description
}
@action
async enable() {
if (this.isEnabled) return;
this.isEnabled = true;
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
this.onActivate();
}
@action
async disable() {
if (!this.isEnabled) return;
this.onDeactivate();
this.isEnabled = false;
this.disposers.forEach(cleanUp => cleanUp());
@ -53,6 +68,14 @@ export class LensExtension {
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() {
// mock
}
@ -69,12 +92,10 @@ export class LensExtension {
};
}
toJSON() {
toJSON(): LensExtensionStoreModel {
return toJS({
id: this.id,
name: this.name,
version: this.version,
description: this.description,
manifestPath: this.manifestPath,
isEnabled: this.isEnabled,
}, {
recurseEverything: true,

View File

@ -53,6 +53,7 @@ app.on("ready", async () => {
userStore.load(),
clusterStore.load(),
workspaceStore.load(),
extensionLoader.load(),
]);
// find free port

View File

@ -13,6 +13,7 @@ import { i18nStore } from "./i18n";
import { themeStore } from "./theme.store";
import { App } from "./components/app";
import { LensApp } from "./lens-app";
import { extensionLoader } from "../extensions/extension-loader";
type AppComponent = React.ComponentType & {
init?(): Promise<void>;
@ -34,6 +35,7 @@ export async function bootstrap(App: AppComponent) {
userStore.load(),
workspaceStore.load(),
clusterStore.load(),
extensionLoader.load(),
i18nStore.init(),
themeStore.init(),
]);

View File

@ -67,9 +67,9 @@ export class Extensions extends React.Component {
)
}
return extensions.map(ext => {
const { manifestPath, name, description, isEnabled } = ext;
const { id, name, description, isEnabled } = ext;
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="package">
Name: <code className="name">{name}</code>