mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Initial telemetry extension (#1067)
* initial telemetry extension Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * Fix preferences saving and default value Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com> Co-authored-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
parent
4f98a01a8b
commit
45abc740e9
@ -5,3 +5,7 @@
|
|||||||
## Build
|
## Build
|
||||||
|
|
||||||
`npm run build`
|
`npm run build`
|
||||||
|
|
||||||
|
## Dev
|
||||||
|
|
||||||
|
`npm run dev`
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"dev": "tsc --watch"
|
"dev": "npm run build --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-open-doodles": "^1.0.5"
|
"react-open-doodles": "^1.0.5"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { DynamicPageType, LensRendererExtension, PageStore } from "@lens/ui-extensions";
|
import { DynamicPageType, LensRendererExtension, PageRegistry } from "@lens/ui-extensions";
|
||||||
import { examplePage, ExtensionIcon } from "./page"
|
import { examplePage, ExtensionIcon } from "./page"
|
||||||
|
|
||||||
export default class ExampleExtension extends LensRendererExtension {
|
export default class ExampleExtension extends LensRendererExtension {
|
||||||
@ -6,16 +6,18 @@ export default class ExampleExtension extends LensRendererExtension {
|
|||||||
console.log('EXAMPLE EXTENSION RENDERER: ACTIVATED', this.getMeta());
|
console.log('EXAMPLE EXTENSION RENDERER: ACTIVATED', this.getMeta());
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPages(pageStore: PageStore) {
|
registerPages(registry: PageRegistry) {
|
||||||
this.disposers.push(pageStore.register({
|
this.disposers.push(
|
||||||
type: DynamicPageType.CLUSTER,
|
registry.add({
|
||||||
path: "/extension-example",
|
type: DynamicPageType.CLUSTER,
|
||||||
title: "Example Extension",
|
path: "/extension-example",
|
||||||
components: {
|
title: "Example Extension",
|
||||||
Page: examplePage(this),
|
components: {
|
||||||
MenuIcon: ExtensionIcon,
|
Page: examplePage(this),
|
||||||
}
|
MenuIcon: ExtensionIcon,
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeactivate() {
|
onDeactivate() {
|
||||||
|
|||||||
16
extensions/telemetry/main.ts
Normal file
16
extensions/telemetry/main.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { LensMainExtension } from "@lens/extensions";
|
||||||
|
import { TelemetryPreferencesStore } from "./src/telemetry-preferences-store"
|
||||||
|
|
||||||
|
export default class TelemetryMainExtension extends LensMainExtension {
|
||||||
|
protected preferencesStore: TelemetryPreferencesStore
|
||||||
|
|
||||||
|
async onActivate() {
|
||||||
|
console.log("telemetry main extension activated")
|
||||||
|
this.preferencesStore = TelemetryPreferencesStore.getInstance<TelemetryPreferencesStore>()
|
||||||
|
await this.preferencesStore.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate() {
|
||||||
|
console.log("telemetry main extension deactivated")
|
||||||
|
}
|
||||||
|
}
|
||||||
3508
extensions/telemetry/package-lock.json
generated
Normal file
3508
extensions/telemetry/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
extensions/telemetry/package.json
Normal file
23
extensions/telemetry/package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "lens-telemetry",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Lens telemetry",
|
||||||
|
"main": "dist/main.js",
|
||||||
|
"renderer": "dist/renderer.js",
|
||||||
|
"lens": {
|
||||||
|
"metadata": {},
|
||||||
|
"styles": []
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"dev": "npm run build --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"ts-loader": "^8.0.4",
|
||||||
|
"typescript": "^4.0.3",
|
||||||
|
"webpack": "^4.44.2",
|
||||||
|
"mobx": "^5.15.5",
|
||||||
|
"react": "^16.13.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
30
extensions/telemetry/renderer.tsx
Normal file
30
extensions/telemetry/renderer.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { AppPreferenceRegistry, LensRendererExtension } from "@lens/ui-extensions";
|
||||||
|
import { TelemetryPreferencesStore } from "./src/telemetry-preferences-store"
|
||||||
|
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export default class TelemetryRendererExtension extends LensRendererExtension {
|
||||||
|
protected preferencesStore: TelemetryPreferencesStore
|
||||||
|
|
||||||
|
async onActivate() {
|
||||||
|
console.log("telemetry extension activated")
|
||||||
|
this.preferencesStore = TelemetryPreferencesStore.getInstance<TelemetryPreferencesStore>()
|
||||||
|
await this.preferencesStore.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAppPreferences(registry: AppPreferenceRegistry) {
|
||||||
|
this.disposers.push(
|
||||||
|
registry.add({
|
||||||
|
title: "Telemetry & Usage Tracking",
|
||||||
|
components: {
|
||||||
|
Hint: () => <TelemetryPreferenceHint />,
|
||||||
|
Input: () => <TelemetryPreferenceInput telemetry={this.preferencesStore} />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate() {
|
||||||
|
console.log("telemetry extension deactivated")
|
||||||
|
}
|
||||||
|
}
|
||||||
26
extensions/telemetry/src/telemetry-preference.tsx
Normal file
26
extensions/telemetry/src/telemetry-preference.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Checkbox } from "@lens/ui-extensions"
|
||||||
|
import React from "react"
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { TelemetryPreferencesStore } from "./telemetry-preferences-store"
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class TelemetryPreferenceInput extends React.Component<{telemetry: TelemetryPreferencesStore}, {}> {
|
||||||
|
render() {
|
||||||
|
const { telemetry } = this.props
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
label="Allow telemetry & usage tracking"
|
||||||
|
value={telemetry.enabled}
|
||||||
|
onChange={v => { telemetry.enabled = v; }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TelemetryPreferenceHint extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span>Telemetry & usage data is collected to continuously improve the Lens experience.</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
extensions/telemetry/src/telemetry-preferences-store.ts
Normal file
33
extensions/telemetry/src/telemetry-preferences-store.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { BaseStore } from "@lens/extensions";
|
||||||
|
import { toJS } from "mobx"
|
||||||
|
|
||||||
|
export type TelemetryPreferencesModel = {
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TelemetryPreferencesStore extends BaseStore<TelemetryPreferencesModel> {
|
||||||
|
private constructor() {
|
||||||
|
super({
|
||||||
|
configName: "telemetry-preferences-store",
|
||||||
|
defaults: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get enabled() {
|
||||||
|
return this.data.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
set enabled(v: boolean) {
|
||||||
|
this.data.enabled = v
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): TelemetryPreferencesModel {
|
||||||
|
return toJS({
|
||||||
|
enabled: this.data.enabled
|
||||||
|
}, {
|
||||||
|
recurseEverything: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
30
extensions/telemetry/tsconfig.json
Normal file
30
extensions/telemetry/tsconfig.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"sourceMap": false,
|
||||||
|
"declaration": false,
|
||||||
|
"strict": false,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"paths": {
|
||||||
|
"*": [
|
||||||
|
"node_modules/*",
|
||||||
|
"../../types/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"renderer.ts",
|
||||||
|
"../../types/",
|
||||||
|
"src/**/*"
|
||||||
|
],
|
||||||
|
}
|
||||||
67
extensions/telemetry/webpack.config.js
Normal file
67
extensions/telemetry/webpack.config.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
entry: './main.ts',
|
||||||
|
context: __dirname,
|
||||||
|
target: "electron-main",
|
||||||
|
mode: "production",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
externals: [
|
||||||
|
{
|
||||||
|
"@lens/extensions": "var global.LensExtensions",
|
||||||
|
"react": "var global.React",
|
||||||
|
"mobx": "var global.Mobx"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: [ '.tsx', '.ts', '.js' ],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
libraryTarget: "commonjs2",
|
||||||
|
globalObject: "this",
|
||||||
|
filename: 'main.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry: './renderer.tsx',
|
||||||
|
context: __dirname,
|
||||||
|
target: "electron-renderer",
|
||||||
|
mode: "production",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
externals: [
|
||||||
|
{
|
||||||
|
"@lens/ui-extensions": "var global.LensExtensions",
|
||||||
|
"@lens/extensions": "var global.LensMainExtensions",
|
||||||
|
"react": "var global.React",
|
||||||
|
"mobx": "var global.Mobx"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: [ '.tsx', '.ts', '.js' ],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
libraryTarget: "commonjs2",
|
||||||
|
globalObject: "this",
|
||||||
|
filename: 'renderer.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -173,6 +173,11 @@
|
|||||||
"confinement": "classic"
|
"confinement": "classic"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lens": {
|
||||||
|
"extensions": [
|
||||||
|
"telemetry"
|
||||||
|
]
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/call": "^8.0.0",
|
"@hapi/call": "^8.0.0",
|
||||||
"@hapi/subtext": "^7.0.3",
|
"@hapi/subtext": "^7.0.3",
|
||||||
|
|||||||
@ -7,3 +7,7 @@ export function getAppVersion(): string {
|
|||||||
export function getBundledKubectlVersion(): string {
|
export function getBundledKubectlVersion(): string {
|
||||||
return packageInfo.config.bundledKubectlVersion;
|
return packageInfo.config.bundledKubectlVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getBundledExtensions(): string[] {
|
||||||
|
return packageInfo.lens?.extensions || []
|
||||||
|
}
|
||||||
|
|||||||
27
src/extensions/app-preference-registry.ts
Normal file
27
src/extensions/app-preference-registry.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { observable } from "mobx"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export interface AppPreferenceComponents {
|
||||||
|
Hint: React.ComponentType<any>;
|
||||||
|
Input: React.ComponentType<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppPreferenceRegistration {
|
||||||
|
title: string;
|
||||||
|
components: AppPreferenceComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AppPreferenceRegistry {
|
||||||
|
preferences = observable.array<AppPreferenceRegistration>([], { deep: false });
|
||||||
|
|
||||||
|
add(preference: AppPreferenceRegistration) {
|
||||||
|
this.preferences.push(preference)
|
||||||
|
return () => {
|
||||||
|
this.preferences.replace(
|
||||||
|
this.preferences.filter(c => c !== preference)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appPreferenceRegistry = new AppPreferenceRegistry()
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { cssNames } from "../renderer/utils";
|
import { cssNames } from "../renderer/utils";
|
||||||
import { TabLayout } from "../renderer/components/layout/tab-layout";
|
import { TabLayout } from "../renderer/components/layout/tab-layout";
|
||||||
import { PageRegistration } from "./page-store"
|
import { PageRegistration } from "./page-registry"
|
||||||
|
|
||||||
export class DynamicPage extends React.Component<{ page: PageRegistration }> {
|
export class DynamicPage extends React.Component<{ page: PageRegistration }> {
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -3,3 +3,4 @@ export type { LensExtensionRuntimeEnv } from "./lens-runtime";
|
|||||||
|
|
||||||
// APIs
|
// APIs
|
||||||
export * from "./lens-main-extension"
|
export * from "./lens-main-extension"
|
||||||
|
export { BaseStore } from "../common/base-store"
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import path from "path"
|
|||||||
import { observable, reaction, toJS, } from "mobx"
|
import { observable, reaction, toJS, } from "mobx"
|
||||||
import logger from "../main/logger"
|
import logger from "../main/logger"
|
||||||
import { app, remote, ipcRenderer } from "electron"
|
import { app, remote, ipcRenderer } from "electron"
|
||||||
import { pageStore } from "./page-store";
|
import { pageRegistry } from "./page-registry";
|
||||||
|
import { appPreferenceRegistry } from "./app-preference-registry"
|
||||||
|
|
||||||
export interface InstalledExtension extends ExtensionModel {
|
export interface InstalledExtension extends ExtensionModel {
|
||||||
manifestPath: string;
|
manifestPath: string;
|
||||||
@ -37,14 +38,15 @@ export class ExtensionLoader {
|
|||||||
loadOnClusterRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
|
loadOnClusterRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer')
|
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer')
|
||||||
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
|
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
|
||||||
instance.registerPages(pageStore)
|
instance.registerPages(pageRegistry)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnMainRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
|
loadOnMainRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer')
|
logger.info('[EXTENSIONS-LOADER]: load on main renderer')
|
||||||
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
|
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
|
||||||
instance.registerPages(pageStore)
|
instance.registerPages(pageRegistry)
|
||||||
|
instance.registerAppPreferences(appPreferenceRegistry)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +84,7 @@ export class ExtensionLoader {
|
|||||||
try {
|
try {
|
||||||
if (ipcRenderer && extension.manifest.renderer) {
|
if (ipcRenderer && extension.manifest.renderer) {
|
||||||
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer))
|
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer))
|
||||||
} else if (extension.manifest.main) {
|
} else if (!ipcRenderer && extension.manifest.main) {
|
||||||
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main))
|
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main))
|
||||||
}
|
}
|
||||||
if (extEntrypoint !== "") {
|
if (extEntrypoint !== "") {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import fs from "fs-extra"
|
|||||||
import logger from "../main/logger"
|
import logger from "../main/logger"
|
||||||
import { extensionPackagesRoot, InstalledExtension } from "./extension-loader"
|
import { extensionPackagesRoot, InstalledExtension } from "./extension-loader"
|
||||||
import * as child_process from 'child_process';
|
import * as child_process from 'child_process';
|
||||||
|
import { getBundledExtensions } from "../common/utils/app-version"
|
||||||
|
|
||||||
type Dependencies = {
|
type Dependencies = {
|
||||||
[name: string]: string;
|
[name: string]: string;
|
||||||
@ -80,7 +81,11 @@ export class ExtensionManager {
|
|||||||
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
||||||
const paths = await fs.readdir(folderPath);
|
const paths = await fs.readdir(folderPath);
|
||||||
const extensions: InstalledExtension[] = []
|
const extensions: InstalledExtension[] = []
|
||||||
|
const bundledExtensions = getBundledExtensions()
|
||||||
for (const fileName of paths) {
|
for (const fileName of paths) {
|
||||||
|
if (!bundledExtensions.includes(fileName)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
const absPath = path.resolve(folderPath, fileName);
|
const absPath = path.resolve(folderPath, fileName);
|
||||||
const manifestPath = path.resolve(absPath, "package.json");
|
const manifestPath = path.resolve(absPath, "package.json");
|
||||||
await fs.access(manifestPath, fs.constants.F_OK)
|
await fs.access(manifestPath, fs.constants.F_OK)
|
||||||
|
|||||||
@ -4,10 +4,12 @@ export type { LensExtensionRuntimeEnv } from "./lens-renderer-runtime"
|
|||||||
// APIs
|
// APIs
|
||||||
export * from "./lens-extension"
|
export * from "./lens-extension"
|
||||||
export * from "./lens-renderer-extension"
|
export * from "./lens-renderer-extension"
|
||||||
export { DynamicPageType, PageStore } from "./page-store"
|
export { DynamicPageType, PageRegistry } from "./page-registry"
|
||||||
|
export { AppPreferenceRegistry } from "./app-preference-registry"
|
||||||
|
|
||||||
// TODO: add more common re-usable UI components + refactor interfaces (Props -> ComponentProps)
|
// TODO: add more common re-usable UI components + refactor interfaces (Props -> ComponentProps)
|
||||||
export * from "../renderer/components/icon"
|
export * from "../renderer/components/icon"
|
||||||
|
export * from "../renderer/components/checkbox"
|
||||||
export * from "../renderer/components/tooltip"
|
export * from "../renderer/components/tooltip"
|
||||||
export * from "../renderer/components/button"
|
export * from "../renderer/components/button"
|
||||||
export * from "../renderer/components/tabs"
|
export * from "../renderer/components/tabs"
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export class LensExtension implements ExtensionModel {
|
|||||||
async enable(runtime: LensExtensionRuntimeEnv) {
|
async enable(runtime: LensExtensionRuntimeEnv) {
|
||||||
this.isEnabled = true;
|
this.isEnabled = true;
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`, this.getMeta());
|
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||||
this.onActivate();
|
this.onActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ export class LensExtension implements ExtensionModel {
|
|||||||
this.runtime = null;
|
this.runtime = null;
|
||||||
this.disposers.forEach(cleanUp => cleanUp());
|
this.disposers.forEach(cleanUp => cleanUp());
|
||||||
this.disposers.length = 0;
|
this.disposers.length = 0;
|
||||||
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`, this.getMeta());
|
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: add more hooks
|
// todo: add more hooks
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import type { PageStore } from "./extension-renderer-api"
|
|
||||||
import type { PageRegistration } from "./page-store"
|
|
||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension"
|
||||||
|
import type { PageRegistry } from "./extension-renderer-api"
|
||||||
|
import { AppPreferenceRegistry } from "./app-preference-registry";
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
|
|
||||||
registerPages(pageStore: PageStore) {
|
registerPages(pageStore: PageRegistry) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runtime helpers
|
registerAppPreferences(appPreferenceStore: AppPreferenceRegistry) {
|
||||||
protected registerPage(pageStore: PageStore, params: PageRegistration) {
|
return
|
||||||
const dispose = pageStore.register(params);
|
|
||||||
this.disposers.push(dispose)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export interface PageComponents {
|
|||||||
MenuIcon: React.ComponentType<IconProps>;
|
MenuIcon: React.ComponentType<IconProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PageStore {
|
export class PageRegistry {
|
||||||
protected pages = observable.array<PageRegistration>([], { deep: false });
|
protected pages = observable.array<PageRegistration>([], { deep: false });
|
||||||
|
|
||||||
@computed get globalPages() {
|
@computed get globalPages() {
|
||||||
@ -39,7 +39,7 @@ export class PageStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: verify paths to avoid collision with existing pages
|
// todo: verify paths to avoid collision with existing pages
|
||||||
register(params: PageRegistration) {
|
add(params: PageRegistration) {
|
||||||
this.pages.push(params);
|
this.pages.push(params);
|
||||||
return () => {
|
return () => {
|
||||||
this.pages.replace(
|
this.pages.replace(
|
||||||
@ -49,4 +49,4 @@ export class PageStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pageStore = new PageStore();
|
export const pageRegistry = new PageRegistry();
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import "./components/app.scss"
|
import "./components/app.scss"
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Mobx from "mobx"
|
import * as Mobx from "mobx"
|
||||||
|
import * as LensMainExtensions from "../extensions/extension-api"
|
||||||
import * as LensExtensions from "../extensions/extension-renderer-api"
|
import * as LensExtensions from "../extensions/extension-renderer-api"
|
||||||
import { render, unmountComponentAtNode } from "react-dom";
|
import { render, unmountComponentAtNode } from "react-dom";
|
||||||
import { isMac } from "../common/vars";
|
import { isMac } from "../common/vars";
|
||||||
@ -19,7 +20,8 @@ type AppComponent = React.ComponentType & {
|
|||||||
export {
|
export {
|
||||||
React,
|
React,
|
||||||
Mobx,
|
Mobx,
|
||||||
LensExtensions
|
LensExtensions,
|
||||||
|
LensMainExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bootstrap(App: AppComponent) {
|
export async function bootstrap(App: AppComponent) {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { themeStore } from "../../theme.store";
|
|||||||
import { history } from "../../navigation";
|
import { history } from "../../navigation";
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
import { KubectlBinaries } from "./kubectl-binaries";
|
import { KubectlBinaries } from "./kubectl-binaries";
|
||||||
|
import { appPreferenceRegistry } from "../../../extensions/app-preference-registry";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class Preferences extends React.Component {
|
export class Preferences extends React.Component {
|
||||||
@ -114,6 +115,7 @@ export class Preferences extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { preferences } = userStore;
|
const { preferences } = userStore;
|
||||||
|
const extensionPreferences = appPreferenceRegistry.preferences
|
||||||
const header = (
|
const header = (
|
||||||
<>
|
<>
|
||||||
<h2>Preferences</h2>
|
<h2>Preferences</h2>
|
||||||
@ -185,15 +187,17 @@ export class Preferences extends React.Component {
|
|||||||
<Trans>Does not affect cluster communications!</Trans>
|
<Trans>Does not affect cluster communications!</Trans>
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
<h2><Trans>Telemetry & Usage Tracking</Trans></h2>
|
{extensionPreferences.map(({title, components: { Hint, Input}}) => {
|
||||||
<Checkbox
|
return (
|
||||||
label={<Trans>Allow telemetry & usage tracking</Trans>}
|
<div key={title}>
|
||||||
value={preferences.allowTelemetry}
|
<h2>{title}</h2>
|
||||||
onChange={v => preferences.allowTelemetry = v}
|
<Input />
|
||||||
/>
|
<small className="hint">
|
||||||
<small className="hint">
|
<Hint />
|
||||||
<Trans>Telemetry & usage data is collected to continuously improve the Lens experience.</Trans>
|
</small>
|
||||||
</small>
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</WizardLayout>
|
</WizardLayout>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store
|
|||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { clusterIpc } from "../../common/cluster-ipc";
|
import { clusterIpc } from "../../common/cluster-ipc";
|
||||||
import { webFrame } from "electron";
|
import { webFrame } from "electron";
|
||||||
import { pageStore } from "../../extensions/page-store";
|
import { pageRegistry } from "../../extensions/page-registry";
|
||||||
import { DynamicPage } from "../../extensions/dynamic-page";
|
import { DynamicPage } from "../../extensions/dynamic-page";
|
||||||
import { extensionLoader } from "../../extensions/extension-loader";
|
import { extensionLoader } from "../../extensions/extension-loader";
|
||||||
import { getLensRuntime } from "../../extensions/lens-runtime";
|
import { getLensRuntime } from "../../extensions/lens-runtime";
|
||||||
@ -78,7 +78,7 @@ export class App extends React.Component {
|
|||||||
<Route component={CustomResources} {...crdRoute}/>
|
<Route component={CustomResources} {...crdRoute}/>
|
||||||
<Route component={UserManagement} {...usersManagementRoute}/>
|
<Route component={UserManagement} {...usersManagementRoute}/>
|
||||||
<Route component={Apps} {...appsRoute}/>
|
<Route component={Apps} {...appsRoute}/>
|
||||||
{pageStore.clusterPages.map(page => {
|
{pageRegistry.clusterPages.map(page => {
|
||||||
return <Route {...page} key={page.path} render={() => <DynamicPage page={page}/>}/>
|
return <Route {...page} key={page.path} render={() => <DynamicPage page={page}/>}/>
|
||||||
})}
|
})}
|
||||||
<Redirect exact from="/" to={this.startURL}/>
|
<Redirect exact from="/" to={this.startURL}/>
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { ClusterSettings, clusterSettingsRoute } from "../+cluster-settings";
|
|||||||
import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route";
|
import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route";
|
||||||
import { clusterStore } from "../../../common/cluster-store";
|
import { clusterStore } from "../../../common/cluster-store";
|
||||||
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
||||||
import { pageStore } from "../../../extensions/page-store";
|
import { pageRegistry } from "../../../extensions/page-registry";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ClusterManager extends React.Component {
|
export class ClusterManager extends React.Component {
|
||||||
@ -63,7 +63,7 @@ export class ClusterManager extends React.Component {
|
|||||||
<Route component={AddCluster} {...addClusterRoute} />
|
<Route component={AddCluster} {...addClusterRoute} />
|
||||||
<Route component={ClusterView} {...clusterViewRoute} />
|
<Route component={ClusterView} {...clusterViewRoute} />
|
||||||
<Route component={ClusterSettings} {...clusterSettingsRoute} />
|
<Route component={ClusterSettings} {...clusterSettingsRoute} />
|
||||||
{pageStore.globalPages.map(({ path, components: { Page } }) => {
|
{pageRegistry.globalPages.map(({ path, components: { Page } }) => {
|
||||||
return <Route key={path} path={path} component={Page}/>
|
return <Route key={path} path={path} component={Page}/>
|
||||||
})}
|
})}
|
||||||
<Redirect exact to={this.startUrl} />
|
<Redirect exact to={this.startUrl} />
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { ConfirmDialog } from "../confirm-dialog";
|
|||||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||||
import { clusterViewURL } from "./cluster-view.route";
|
import { clusterViewURL } from "./cluster-view.route";
|
||||||
import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd";
|
import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd";
|
||||||
import { pageStore } from "../../../extensions/page-store";
|
import { pageRegistry } from "../../../extensions/page-registry";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
@ -156,7 +156,7 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="dynamic-pages">
|
<div className="dynamic-pages">
|
||||||
{pageStore.globalPages.map(({ path, components: { MenuIcon } }) => {
|
{pageRegistry.globalPages.map(({ path, components: { MenuIcon } }) => {
|
||||||
return <MenuIcon key={path} onClick={() => navigate(path)}/>
|
return <MenuIcon key={path} onClick={() => navigate(path)}/>
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resourc
|
|||||||
import { CustomResources } from "../+custom-resources/custom-resources";
|
import { CustomResources } from "../+custom-resources/custom-resources";
|
||||||
import { navigation } from "../../navigation";
|
import { navigation } from "../../navigation";
|
||||||
import { isAllowedResource } from "../../../common/rbac"
|
import { isAllowedResource } from "../../../common/rbac"
|
||||||
import { pageStore } from "../../../extensions/page-store";
|
import { pageRegistry } from "../../../extensions/page-registry";
|
||||||
|
|
||||||
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
|
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
|
||||||
type SidebarContextValue = {
|
type SidebarContextValue = {
|
||||||
@ -184,7 +184,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
>
|
>
|
||||||
{this.renderCustomResources()}
|
{this.renderCustomResources()}
|
||||||
</SidebarNavItem>
|
</SidebarNavItem>
|
||||||
{pageStore.clusterPages.map(({ path, title, components: { MenuIcon } }) => {
|
{pageRegistry.clusterPages.map(({ path, title, components: { MenuIcon } }) => {
|
||||||
return (
|
return (
|
||||||
<SidebarNavItem
|
<SidebarNavItem
|
||||||
key={path}
|
key={path}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user