mirror of
https://github.com/lensapp/lens.git
synced 2024-09-20 05:47:24 +03:00
Merge branch 'extensions-api' of github.com:lensapp/lens into extensions-api
This commit is contained in:
commit
3a6b499fe3
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ src/extensions/*/*.js
|
||||
src/extensions/*/*.d.ts
|
||||
src/extensions/example-extension/src/**
|
||||
types/extension-api.d.ts
|
||||
types/extension-renderer-api.d.ts
|
||||
|
1
extensions/example-extension/.gitignore
vendored
1
extensions/example-extension/.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
*.js
|
||||
node_modules/
|
||||
dist/
|
||||
|
@ -5,3 +5,7 @@
|
||||
## Build
|
||||
|
||||
`npm run build`
|
||||
|
||||
## Dev
|
||||
|
||||
`npm run dev`
|
||||
|
@ -1,48 +0,0 @@
|
||||
import { Button, Icon, IconProps, LensExtension, React, DynamicPageType } from "@lens/extensions";
|
||||
import { CoffeeDoodle } from "react-open-doodles";
|
||||
import path from "path";
|
||||
|
||||
export default class ExampleExtension extends LensExtension {
|
||||
onActivate() {
|
||||
console.log('EXAMPLE EXTENSION: ACTIVATED', this.getMeta());
|
||||
this.registerPage({
|
||||
type: DynamicPageType.CLUSTER,
|
||||
path: "/extension-example",
|
||||
title: "Example Extension",
|
||||
components: {
|
||||
Page: () => <ExtensionPage extension={this}/>,
|
||||
MenuIcon: ExtensionIcon,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onDeactivate() {
|
||||
console.log('EXAMPLE EXTENSION: DEACTIVATED', this.getMeta());
|
||||
}
|
||||
}
|
||||
|
||||
export function ExtensionIcon(props: IconProps) {
|
||||
return <Icon {...props} material="pages" tooltip={path.basename(__filename)}/>
|
||||
}
|
||||
|
||||
export class ExtensionPage extends React.Component<{ extension: ExampleExtension }> {
|
||||
deactivate = () => {
|
||||
const { extension } = this.props;
|
||||
extension.runtime.navigate("/")
|
||||
extension.disable();
|
||||
}
|
||||
|
||||
render() {
|
||||
const doodleStyle = {
|
||||
width: "200px"
|
||||
}
|
||||
return (
|
||||
<div className="ExampleExtension flex column gaps align-flex-start">
|
||||
<div style={doodleStyle}><CoffeeDoodle accent="#3d90ce"/></div>
|
||||
<p>Hello from Example extension!</p>
|
||||
<p>File: <i>{__filename}</i></p>
|
||||
<Button accent label="Deactivate" onClick={this.deactivate}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
11
extensions/example-extension/main.ts
Normal file
11
extensions/example-extension/main.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { LensMainExtension } from "@lens/extensions";
|
||||
|
||||
export default class ExampleExtensionMain extends LensMainExtension {
|
||||
onActivate() {
|
||||
console.log('EXAMPLE EXTENSION MAIN: ACTIVATED', this.getMeta());
|
||||
}
|
||||
|
||||
onDeactivate() {
|
||||
console.log('EXAMPLE EXTENSION MAIN: DEACTIVATED', this.getMeta());
|
||||
}
|
||||
}
|
3445
extensions/example-extension/package-lock.json
generated
3445
extensions/example-extension/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,19 +2,22 @@
|
||||
"name": "extension-example",
|
||||
"version": "1.0.0",
|
||||
"description": "Example extension",
|
||||
"main": "dist/index.js",
|
||||
"main": "dist/main.js",
|
||||
"renderer": "dist/renderer.js",
|
||||
"lens": {
|
||||
"metadata": {},
|
||||
"styles": []
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"dev": "npm run build --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-open-doodles": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.0.3"
|
||||
"ts-loader": "^8.0.4",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2"
|
||||
}
|
||||
}
|
||||
|
33
extensions/example-extension/page.tsx
Normal file
33
extensions/example-extension/page.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Button, Icon, IconProps, LensRendererExtension } from "@lens/ui-extensions";
|
||||
import { CoffeeDoodle } from "react-open-doodles";
|
||||
import path from "path";
|
||||
import React from "react"
|
||||
|
||||
export function ExtensionIcon(props: IconProps) {
|
||||
return <Icon {...props} material="pages" tooltip={path.basename(__filename)}/>
|
||||
}
|
||||
|
||||
export class ExtensionPage extends React.Component<{ extension: LensRendererExtension }> {
|
||||
deactivate = () => {
|
||||
const { extension } = this.props;
|
||||
extension.disable();
|
||||
}
|
||||
|
||||
render() {
|
||||
const doodleStyle = {
|
||||
width: "200px"
|
||||
}
|
||||
return (
|
||||
<div className="flex column gaps align-flex-start">
|
||||
<div style={doodleStyle}><CoffeeDoodle accent="#3d90ce" /></div>
|
||||
<p>Hello from Example extension!</p>
|
||||
<p>File: <i>{__filename}</i></p>
|
||||
<Button accent label="Deactivate" onClick={this.deactivate}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function examplePage(ext: LensRendererExtension) {
|
||||
return () => <ExtensionPage extension={ext} />
|
||||
}
|
26
extensions/example-extension/renderer.ts
Normal file
26
extensions/example-extension/renderer.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { DynamicPageType, LensRendererExtension, PageRegistry } from "@lens/ui-extensions";
|
||||
import { examplePage, ExtensionIcon } from "./page"
|
||||
|
||||
export default class ExampleExtension extends LensRendererExtension {
|
||||
onActivate() {
|
||||
console.log('EXAMPLE EXTENSION RENDERER: ACTIVATED', this.getMeta());
|
||||
}
|
||||
|
||||
registerPages(registry: PageRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
type: DynamicPageType.CLUSTER,
|
||||
path: "/extension-example",
|
||||
title: "Example Extension",
|
||||
components: {
|
||||
Page: examplePage(this),
|
||||
MenuIcon: ExtensionIcon,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
onDeactivate() {
|
||||
console.log('EXAMPLE EXTENSION RENDERER: DEACTIVATED', this.getMeta());
|
||||
}
|
||||
}
|
@ -12,11 +12,13 @@
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"../../types",
|
||||
"./index.tsx"
|
||||
"./*.ts",
|
||||
"./*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
64
extensions/example-extension/webpack.config.js
Normal file
64
extensions/example-extension/webpack.config.js
Normal file
@ -0,0 +1,64 @@
|
||||
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",
|
||||
"mobx": "var global.Mobx",
|
||||
}
|
||||
],
|
||||
resolve: {
|
||||
extensions: [ '.tsx', '.ts', '.js' ],
|
||||
},
|
||||
output: {
|
||||
libraryTarget: "commonjs2",
|
||||
filename: 'main.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
},
|
||||
{
|
||||
entry: './renderer.ts',
|
||||
context: __dirname,
|
||||
target: "electron-renderer",
|
||||
mode: "production",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
"@lens/ui-extensions": "var global.LensExtensions",
|
||||
"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'),
|
||||
},
|
||||
},
|
||||
];
|
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'),
|
||||
},
|
||||
},
|
||||
];
|
13
package.json
13
package.json
@ -12,15 +12,16 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "concurrently -k \"yarn dev-run -C\" yarn:dev:*",
|
||||
"dev-build": "concurrently yarn:compile:*",
|
||||
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
|
||||
"dev:main": "yarn compile:main --watch",
|
||||
"dev:renderer": "yarn compile:renderer --watch",
|
||||
"dev:extensions": "yarn compile:extensions --watch",
|
||||
"dev:extension-rollup": "yarn compile:extension-rollup --watch",
|
||||
"compile": "env NODE_ENV=production concurrently yarn:compile:*",
|
||||
"compile:main": "webpack --config webpack.main.ts",
|
||||
"compile:renderer": "webpack --config webpack.renderer.ts",
|
||||
"compile:i18n": "lingui compile",
|
||||
"compile:extensions": "rollup --config src/extensions/rollup.config.js",
|
||||
"compile:extension-rollup": "rollup --config src/extensions/rollup.config.js",
|
||||
"build:linux": "yarn compile && electron-builder --linux --dir -c.productName=Lens",
|
||||
"build:mac": "yarn compile && electron-builder --mac --dir -c.productName=Lens",
|
||||
"build:win": "yarn compile && electron-builder --win --dir -c.productName=Lens",
|
||||
@ -171,6 +172,11 @@
|
||||
"confinement": "classic"
|
||||
}
|
||||
},
|
||||
"lens": {
|
||||
"extensions": [
|
||||
"telemetry"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/call": "^8.0.0",
|
||||
"@hapi/subtext": "^7.0.3",
|
||||
@ -196,7 +202,6 @@
|
||||
"mobx": "^5.15.5",
|
||||
"mobx-observable-history": "^1.0.3",
|
||||
"mock-fs": "^4.12.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"node-pty": "^0.9.0",
|
||||
"npm": "^6.14.8",
|
||||
@ -319,8 +324,8 @@
|
||||
"progress-bar-webpack-plugin": "^2.1.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-beautiful-dnd": "^13.0.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-select": "^3.1.0",
|
||||
|
@ -7,3 +7,7 @@ export function getAppVersion(): string {
|
||||
export function getBundledKubectlVersion(): string {
|
||||
return packageInfo.config.bundledKubectlVersion;
|
||||
}
|
||||
|
||||
export function getBundledExtensions(): string[] {
|
||||
return packageInfo.lens?.extensions || []
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
import path from "path";
|
||||
import packageInfo from "../../package.json"
|
||||
import { defineGlobal } from "./utils/defineGlobal";
|
||||
import { addAlias } from "module-alias";
|
||||
|
||||
export const isMac = process.platform === "darwin"
|
||||
export const isWindows = process.platform === "win32"
|
||||
@ -24,6 +23,7 @@ export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
|
||||
|
||||
// Extensions
|
||||
export const extensionsLibName = `${appName}-extensions.api`
|
||||
export const extensionsRendererLibName = `${appName}-extensions-renderer.api`
|
||||
export const extensionsDir = path.join(contextDir, "src/extensions");
|
||||
|
||||
// Special runtime paths
|
||||
@ -36,13 +36,6 @@ defineGlobal("__static", {
|
||||
}
|
||||
})
|
||||
|
||||
// Special dynamic module aliases
|
||||
if (isProduction && process.resourcesPath) {
|
||||
addAlias("@lens/extensions", path.join(process.resourcesPath, "static", `build/${extensionsLibName}.js`))
|
||||
} else {
|
||||
addAlias("@lens/extensions", path.join(contextDir, "static", `build/${extensionsLibName}.js`))
|
||||
}
|
||||
|
||||
// Apis
|
||||
export const apiPrefix = "/api" // local router apis
|
||||
export const apiKubePrefix = "/api-kube" // k8s cluster apis
|
||||
|
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()
|
15
src/extensions/dynamic-page.tsx
Normal file
15
src/extensions/dynamic-page.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import { cssNames } from "../renderer/utils";
|
||||
import { TabLayout } from "../renderer/components/layout/tab-layout";
|
||||
import { PageRegistration } from "./page-registry"
|
||||
|
||||
export class DynamicPage extends React.Component<{ page: PageRegistration }> {
|
||||
render() {
|
||||
const { className, components: { Page }, subPages = [] } = this.props.page;
|
||||
return (
|
||||
<TabLayout className={cssNames("ExtensionPage", className)} tabs={subPages}>
|
||||
<Page/>
|
||||
</TabLayout>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,15 +1,6 @@
|
||||
// Lens-extensions api developer's kit
|
||||
// TODO: add more common re-usable UI components + refactor interfaces (Props -> ComponentProps)
|
||||
export type { LensExtensionRuntimeEnv } from "./lens-runtime";
|
||||
|
||||
// TODO: figure out how to import as normal npm-package
|
||||
export { default as React } from "react"
|
||||
|
||||
export * from "./lens-extension"
|
||||
export { LensRuntimeRendererEnv } from "./lens-runtime";
|
||||
export { DynamicPageType } from "./register-page";
|
||||
|
||||
export * from "../renderer/components/icon"
|
||||
export * from "../renderer/components/tooltip"
|
||||
export * from "../renderer/components/button"
|
||||
export * from "../renderer/components/tabs"
|
||||
export * from "../renderer/components/badge"
|
||||
// APIs
|
||||
export * from "./lens-main-extension"
|
||||
export { BaseStore } from "../common/base-store"
|
||||
|
@ -1,10 +1,13 @@
|
||||
import type { ExtensionId, LensExtension, ExtensionManifest, ExtensionModel } from "./lens-extension"
|
||||
import type { LensRendererExtension } from "./lens-renderer-extension"
|
||||
import { broadcastIpc } from "../common/ipc"
|
||||
import type { LensRuntimeRendererEnv } from "./lens-runtime"
|
||||
import type { LensExtensionRuntimeEnv } from "./lens-runtime"
|
||||
import path from "path"
|
||||
import { observable, reaction, toJS, } from "mobx"
|
||||
import logger from "../main/logger"
|
||||
import { app, remote, ipcRenderer } from "electron"
|
||||
import { pageRegistry } from "./page-registry";
|
||||
import { appPreferenceRegistry } from "./app-preference-registry"
|
||||
|
||||
export interface InstalledExtension extends ExtensionModel {
|
||||
manifestPath: string;
|
||||
@ -16,16 +19,6 @@ export function extensionPackagesRoot() {
|
||||
return path.join((app || remote.app).getPath("userData"))
|
||||
}
|
||||
|
||||
export function withExtensionPackagesRoot(callback: Function) {
|
||||
const cwd = process.cwd()
|
||||
try {
|
||||
process.chdir(extensionPackagesRoot())
|
||||
return callback()
|
||||
} finally {
|
||||
process.chdir(cwd)
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionLoader {
|
||||
@observable extensions = observable.map<ExtensionId, InstalledExtension>([], { deep: false });
|
||||
@observable instances = observable.map<ExtensionId, LensExtension>([], { deep: false })
|
||||
@ -42,38 +35,65 @@ export class ExtensionLoader {
|
||||
}
|
||||
}
|
||||
|
||||
autoEnableOnLoad(getLensRuntimeEnv: () => LensRuntimeRendererEnv, { delay = 0 } = {}) {
|
||||
logger.info('[EXTENSIONS-LOADER]: auto-activation loaded extensions: ON');
|
||||
return reaction(() => this.extensions.toJS(), installedExtensions => {
|
||||
installedExtensions.forEach((ext) => {
|
||||
loadOnClusterRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer')
|
||||
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
|
||||
instance.registerPages(pageRegistry)
|
||||
})
|
||||
}
|
||||
|
||||
loadOnMainRenderer(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer')
|
||||
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensRendererExtension) => {
|
||||
instance.registerPages(pageRegistry)
|
||||
instance.registerAppPreferences(appPreferenceRegistry)
|
||||
})
|
||||
}
|
||||
|
||||
loadOnMain(getLensRuntimeEnv: () => LensExtensionRuntimeEnv) {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
||||
this.autoloadExtensions(getLensRuntimeEnv, (instance: LensExtension) => {
|
||||
// todo
|
||||
})
|
||||
}
|
||||
|
||||
protected autoloadExtensions(getLensRuntimeEnv: () => LensExtensionRuntimeEnv, callback: (instance: LensExtension) => void) {
|
||||
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
||||
for(const [id, ext] of installedExtensions) {
|
||||
let instance = this.instances.get(ext.manifestPath)
|
||||
if (!instance) {
|
||||
const extensionModule = this.requireExtension(ext)
|
||||
if (!extensionModule) {
|
||||
logger.error("[EXTENSION-LOADER] failed to load extension " + ext.manifestPath)
|
||||
return
|
||||
continue
|
||||
}
|
||||
const LensExtensionClass = extensionModule.default;
|
||||
instance = new LensExtensionClass({ ...ext.manifest, manifestPath: ext.manifestPath, id: ext.manifestPath }, ext.manifest);
|
||||
instance.enable(getLensRuntimeEnv());
|
||||
instance.enable(getLensRuntimeEnv())
|
||||
callback(instance)
|
||||
this.instances.set(ext.id, instance)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, {
|
||||
fireImmediately: true,
|
||||
delay: delay,
|
||||
delay: 0,
|
||||
})
|
||||
}
|
||||
|
||||
protected requireExtension(extension: InstalledExtension) {
|
||||
return withExtensionPackagesRoot(() => {
|
||||
try {
|
||||
const extMain = path.join(path.dirname(extension.manifestPath), extension.manifest.main)
|
||||
return __non_webpack_require__(extMain)
|
||||
} catch (err) {
|
||||
console.error(`[EXTENSION-LOADER]: can't load extension main at ${extension.manifestPath}: ${err}`, { extension });
|
||||
let extEntrypoint = ""
|
||||
try {
|
||||
if (ipcRenderer && extension.manifest.renderer) {
|
||||
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer))
|
||||
} else if (!ipcRenderer && extension.manifest.main) {
|
||||
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main))
|
||||
}
|
||||
})
|
||||
if (extEntrypoint !== "") {
|
||||
return __non_webpack_require__(extEntrypoint)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[EXTENSION-LOADER]: can't load extension main at ${extEntrypoint}: ${err}`, { extension });
|
||||
console.trace(err)
|
||||
}
|
||||
}
|
||||
|
||||
getById(id: ExtensionId): InstalledExtension {
|
||||
@ -84,7 +104,7 @@ export class ExtensionLoader {
|
||||
const extension = this.getById(id);
|
||||
if (extension) {
|
||||
const instance = this.instances.get(extension.id)
|
||||
if (instance) { await instance.uninstall() }
|
||||
if (instance) { await instance.disable() }
|
||||
this.extensions.delete(id);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,24 @@ import type { ExtensionManifest } from "./lens-extension"
|
||||
import path from "path"
|
||||
import fs from "fs-extra"
|
||||
import logger from "../main/logger"
|
||||
import { withExtensionPackagesRoot, extensionPackagesRoot, InstalledExtension } from "./extension-loader"
|
||||
import npm from "npm"
|
||||
import { extensionPackagesRoot, InstalledExtension } from "./extension-loader"
|
||||
import * as child_process from 'child_process';
|
||||
import { getBundledExtensions } from "../common/utils/app-version"
|
||||
|
||||
type Dependencies = {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
type PackageJson = {
|
||||
dependencies: Dependencies;
|
||||
}
|
||||
|
||||
export class ExtensionManager {
|
||||
|
||||
protected packagesJson: PackageJson = {
|
||||
dependencies: {}
|
||||
}
|
||||
|
||||
get extensionPackagesRoot() {
|
||||
return extensionPackagesRoot()
|
||||
}
|
||||
@ -14,11 +28,13 @@ export class ExtensionManager {
|
||||
return path.resolve(__static, "../extensions");
|
||||
}
|
||||
|
||||
get npmPath() {
|
||||
return __non_webpack_require__.resolve('npm/bin/npm-cli')
|
||||
}
|
||||
|
||||
async load() {
|
||||
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
|
||||
|
||||
await fs.ensureDir(path.join(this.extensionPackagesRoot, "node_modules"))
|
||||
await fs.writeFile(path.join(this.extensionPackagesRoot, "package.json"), `{"dependencies": []}`, {mode: 0o600})
|
||||
|
||||
return await this.loadExtensions();
|
||||
}
|
||||
@ -27,9 +43,7 @@ export class ExtensionManager {
|
||||
let manifestJson: ExtensionManifest;
|
||||
try {
|
||||
manifestJson = __non_webpack_require__(manifestPath)
|
||||
withExtensionPackagesRoot(() => {
|
||||
this.installPackageFromPath(path.dirname(manifestPath))
|
||||
})
|
||||
this.packagesJson.dependencies[manifestJson.name] = path.dirname(manifestPath)
|
||||
|
||||
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name)
|
||||
return {
|
||||
@ -44,31 +58,17 @@ export class ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
protected installPackageFromPath(path: string): Promise<void> {
|
||||
const origLogger = console.log
|
||||
protected installPackages(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
npm.load({
|
||||
production: true,
|
||||
global: false,
|
||||
prefix: this.extensionPackagesRoot,
|
||||
dev: false,
|
||||
spin: false,
|
||||
"ignore-scripts": true,
|
||||
loglevel: "silent"
|
||||
}, (err) => {
|
||||
console.log = function() {
|
||||
// just to ignore ts empty function error
|
||||
}
|
||||
npm.commands.install([
|
||||
path
|
||||
], (err) => {
|
||||
console.log = origLogger
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
const child = child_process.fork(this.npmPath, ["install", "--silent"], {
|
||||
cwd: extensionPackagesRoot(),
|
||||
silent: true
|
||||
})
|
||||
child.on("close", () => {
|
||||
resolve()
|
||||
})
|
||||
child.on("error", (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -80,15 +80,23 @@ export class ExtensionManager {
|
||||
|
||||
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
||||
const paths = await fs.readdir(folderPath);
|
||||
const manifestsLoading = paths.map(fileName => {
|
||||
const extensions: InstalledExtension[] = []
|
||||
const bundledExtensions = getBundledExtensions()
|
||||
for (const fileName of paths) {
|
||||
if (!bundledExtensions.includes(fileName)) {
|
||||
continue
|
||||
}
|
||||
const absPath = path.resolve(folderPath, fileName);
|
||||
const manifestPath = path.resolve(absPath, "package.json");
|
||||
return fs.access(manifestPath, fs.constants.F_OK)
|
||||
.then(async () => await this.getExtensionByManifest(manifestPath))
|
||||
.catch(() => null)
|
||||
});
|
||||
let extensions = await Promise.all(manifestsLoading);
|
||||
extensions = extensions.filter(v => !!v); // filter out files and invalid folders (without manifest.json)
|
||||
await fs.access(manifestPath, fs.constants.F_OK)
|
||||
const ext = await this.getExtensionByManifest(manifestPath).catch(() => null)
|
||||
if (ext) {
|
||||
extensions.push(ext)
|
||||
}
|
||||
}
|
||||
await fs.writeFile(path.join(this.extensionPackagesRoot, "package.json"), JSON.stringify(this.packagesJson), {mode: 0o600})
|
||||
await this.installPackages()
|
||||
|
||||
logger.debug(`[EXTENSION-MANAGER]: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||
return extensions;
|
||||
}
|
||||
|
16
src/extensions/extension-renderer-api.ts
Normal file
16
src/extensions/extension-renderer-api.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// Lens-extensions api developer's kit
|
||||
export type { LensExtensionRuntimeEnv } from "./lens-renderer-runtime"
|
||||
|
||||
// APIs
|
||||
export * from "./lens-extension"
|
||||
export * from "./lens-renderer-extension"
|
||||
export { DynamicPageType, PageRegistry } from "./page-registry"
|
||||
export { AppPreferenceRegistry } from "./app-preference-registry"
|
||||
|
||||
// TODO: add more common re-usable UI components + refactor interfaces (Props -> ComponentProps)
|
||||
export * from "../renderer/components/icon"
|
||||
export * from "../renderer/components/checkbox"
|
||||
export * from "../renderer/components/tooltip"
|
||||
export * from "../renderer/components/button"
|
||||
export * from "../renderer/components/tabs"
|
||||
export * from "../renderer/components/badge"
|
@ -1,5 +1,4 @@
|
||||
import type { LensRuntimeRendererEnv } from "./lens-runtime";
|
||||
import type { PageRegistration } from "./register-page";
|
||||
import type { LensExtensionRuntimeEnv } from "./lens-runtime";
|
||||
import { readJsonSync } from "fs-extra";
|
||||
import { action, observable, toJS } from "mobx";
|
||||
import logger from "../main/logger";
|
||||
@ -19,7 +18,8 @@ export interface ExtensionModel {
|
||||
}
|
||||
|
||||
export interface ExtensionManifest extends ExtensionModel {
|
||||
main: string;
|
||||
main?: string;
|
||||
renderer?: string;
|
||||
description?: string; // todo: add more fields similar to package.json + some extra
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ export class LensExtension implements ExtensionModel {
|
||||
@observable manifest: ExtensionManifest;
|
||||
@observable manifestPath: string;
|
||||
@observable isEnabled = false;
|
||||
@observable.ref runtime: LensRuntimeRendererEnv;
|
||||
@observable.ref runtime: LensExtensionRuntimeEnv;
|
||||
|
||||
constructor(model: ExtensionModel, manifest: ExtensionManifest) {
|
||||
this.importModel(model, manifest);
|
||||
@ -52,10 +52,14 @@ export class LensExtension implements ExtensionModel {
|
||||
}
|
||||
}
|
||||
|
||||
async enable(runtime: LensRuntimeRendererEnv) {
|
||||
async migrate(appVersion: string) {
|
||||
// mock
|
||||
}
|
||||
|
||||
async enable(runtime: LensExtensionRuntimeEnv) {
|
||||
this.isEnabled = true;
|
||||
this.runtime = runtime;
|
||||
console.log(`[EXTENSION]: enabled ${this.name}@${this.version}`, this.getMeta());
|
||||
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||
this.onActivate();
|
||||
}
|
||||
|
||||
@ -65,7 +69,7 @@ export class LensExtension implements ExtensionModel {
|
||||
this.runtime = null;
|
||||
this.disposers.forEach(cleanUp => cleanUp());
|
||||
this.disposers.length = 0;
|
||||
console.log(`[EXTENSION]: disabled ${this.name}@${this.version}`, this.getMeta());
|
||||
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||
}
|
||||
|
||||
// todo: add more hooks
|
||||
@ -77,27 +81,12 @@ export class LensExtension implements ExtensionModel {
|
||||
// mock
|
||||
}
|
||||
|
||||
// todo
|
||||
async install(downloadUrl?: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
// todo
|
||||
async uninstall() {
|
||||
return;
|
||||
}
|
||||
|
||||
async hasNewVersion(): Promise<Partial<ExtensionModel>> {
|
||||
return;
|
||||
}
|
||||
|
||||
getMeta() {
|
||||
return toJS({
|
||||
id: this.id,
|
||||
manifest: this.manifest,
|
||||
manifestPath: this.manifestPath,
|
||||
enabled: this.isEnabled,
|
||||
runtime: this.runtime,
|
||||
enabled: this.isEnabled
|
||||
}, {
|
||||
recurseEverything: true
|
||||
})
|
||||
@ -116,12 +105,4 @@ export class LensExtension implements ExtensionModel {
|
||||
recurseEverything: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Runtime helpers
|
||||
protected registerPage(params: PageRegistration, autoDisable = true) {
|
||||
const dispose = this.runtime.dynamicPages.register(params);
|
||||
if (autoDisable) {
|
||||
this.disposers.push(dispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
src/extensions/lens-main-extension.ts
Normal file
11
src/extensions/lens-main-extension.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { LensExtension } from "./lens-extension"
|
||||
|
||||
export class LensMainExtension extends LensExtension {
|
||||
async registerAppMenus() {
|
||||
//
|
||||
}
|
||||
|
||||
async registerPrometheusProviders(registry: any) {
|
||||
//
|
||||
}
|
||||
}
|
14
src/extensions/lens-renderer-extension.ts
Normal file
14
src/extensions/lens-renderer-extension.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { LensExtension } from "./lens-extension"
|
||||
import type { PageRegistry } from "./extension-renderer-api"
|
||||
import { AppPreferenceRegistry } from "./app-preference-registry";
|
||||
|
||||
export class LensRendererExtension extends LensExtension {
|
||||
|
||||
registerPages(pageStore: PageRegistry) {
|
||||
return
|
||||
}
|
||||
|
||||
registerAppPreferences(appPreferenceStore: AppPreferenceRegistry) {
|
||||
return
|
||||
}
|
||||
}
|
16
src/extensions/lens-renderer-runtime.ts
Normal file
16
src/extensions/lens-renderer-runtime.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// Lens extension runtime params available to renderer extensions after activation
|
||||
|
||||
import logger from "../main/logger";
|
||||
import { navigate } from "../renderer/navigation";
|
||||
|
||||
export interface LensExtensionRuntimeEnv {
|
||||
logger: typeof logger;
|
||||
navigate: typeof navigate;
|
||||
}
|
||||
|
||||
export function getLensRuntime(): LensExtensionRuntimeEnv {
|
||||
return {
|
||||
logger,
|
||||
navigate
|
||||
}
|
||||
}
|
@ -1,19 +1,13 @@
|
||||
// Lens renderer runtime apis exposed to extensions once activated
|
||||
// Lens extension runtime params available to extensions after activation
|
||||
|
||||
import logger from "../main/logger";
|
||||
import { dynamicPages } from "./register-page";
|
||||
import { navigate } from "../renderer/navigation";
|
||||
|
||||
export interface LensRuntimeRendererEnv {
|
||||
navigate: typeof navigate;
|
||||
export interface LensExtensionRuntimeEnv {
|
||||
logger: typeof logger;
|
||||
dynamicPages: typeof dynamicPages
|
||||
}
|
||||
|
||||
export function getLensRuntime(): LensRuntimeRendererEnv {
|
||||
export function getLensRuntime(): LensExtensionRuntimeEnv {
|
||||
return {
|
||||
logger,
|
||||
navigate,
|
||||
dynamicPages,
|
||||
logger
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import { computed, observable } from "mobx";
|
||||
import React from "react";
|
||||
import { RouteProps } from "react-router";
|
||||
import { IconProps } from "../renderer/components/icon";
|
||||
import { cssNames, IClassName } from "../renderer/utils";
|
||||
import { TabLayout, TabRoute } from "../renderer/components/layout/tab-layout";
|
||||
import { IClassName } from "../renderer/utils";
|
||||
import { TabRoute } from "../renderer/components/layout/tab-layout";
|
||||
|
||||
export enum DynamicPageType {
|
||||
GLOBAL = "lens-scope",
|
||||
@ -27,7 +27,7 @@ export interface PageComponents {
|
||||
MenuIcon: React.ComponentType<IconProps>;
|
||||
}
|
||||
|
||||
export class PagesStore {
|
||||
export class PageRegistry {
|
||||
protected pages = observable.array<PageRegistration>([], { deep: false });
|
||||
|
||||
@computed get globalPages() {
|
||||
@ -39,7 +39,7 @@ export class PagesStore {
|
||||
}
|
||||
|
||||
// todo: verify paths to avoid collision with existing pages
|
||||
register(params: PageRegistration) {
|
||||
add(params: PageRegistration) {
|
||||
this.pages.push(params);
|
||||
return () => {
|
||||
this.pages.replace(
|
||||
@ -49,15 +49,4 @@ export class PagesStore {
|
||||
}
|
||||
}
|
||||
|
||||
export class DynamicPage extends React.Component<{ page: PageRegistration }> {
|
||||
render() {
|
||||
const { className, components: { Page }, subPages = [] } = this.props.page;
|
||||
return (
|
||||
<TabLayout className={cssNames("ExtensionPage", className)} tabs={subPages}>
|
||||
<Page/>
|
||||
</TabLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamicPages = new PagesStore();
|
||||
export const pageRegistry = new PageRegistry();
|
@ -22,6 +22,19 @@ const config: RollupOptions = {
|
||||
],
|
||||
};
|
||||
|
||||
const rendererConfig: RollupOptions = {
|
||||
input: "src/extensions/extension-renderer-api.ts",
|
||||
output: [
|
||||
{ file: "types/extension-renderer-api.d.ts", format: "es", }
|
||||
],
|
||||
plugins: [
|
||||
dts(),
|
||||
dtsModuleWrap({ name: "@lens/ui-extensions" }),
|
||||
ignoreImport({ extensions: ['.scss'] }),
|
||||
json(),
|
||||
],
|
||||
};
|
||||
|
||||
function dtsModuleWrap({ name }: { name: string }): Plugin {
|
||||
return {
|
||||
name,
|
||||
@ -56,4 +69,4 @@ function dtsModuleWrap({ name }: { name: string }): Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
export default config;
|
||||
export default [config, rendererConfig];
|
||||
|
@ -17,9 +17,17 @@ import { clusterStore } from "../common/cluster-store"
|
||||
import { userStore } from "../common/user-store";
|
||||
import { workspaceStore } from "../common/workspace-store";
|
||||
import { tracker } from "../common/tracker";
|
||||
import * as LensExtensions from "../extensions/extension-api";
|
||||
import { extensionManager } from "../extensions/extension-manager";
|
||||
import { extensionLoader } from "../extensions/extension-loader";
|
||||
import { getLensRuntime } from "../extensions/lens-runtime";
|
||||
import logger from "./logger"
|
||||
import * as Mobx from "mobx"
|
||||
|
||||
export {
|
||||
LensExtensions,
|
||||
Mobx
|
||||
}
|
||||
|
||||
const workingDir = path.join(app.getPath("appData"), appName);
|
||||
app.setName(appName);
|
||||
@ -78,7 +86,9 @@ async function main() {
|
||||
// create window manager and open app
|
||||
windowManager = new WindowManager(proxyPort);
|
||||
|
||||
extensionLoader.loadOnMain(getLensRuntime)
|
||||
extensionLoader.extensions.replace(await extensionManager.load())
|
||||
extensionLoader.broadcastExtensions()
|
||||
}
|
||||
|
||||
app.on("ready", main);
|
||||
|
@ -4,6 +4,7 @@ import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"
|
||||
import windowStateKeeper from "electron-window-state"
|
||||
import { observable } from "mobx";
|
||||
import { initMenu } from "./menu";
|
||||
import { extensionLoader } from "../extensions/extension-loader";
|
||||
|
||||
export class WindowManager {
|
||||
protected mainView: BrowserWindow;
|
||||
@ -40,6 +41,9 @@ export class WindowManager {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
this.mainView.webContents.on("dom-ready", () => {
|
||||
extensionLoader.broadcastExtensions()
|
||||
})
|
||||
|
||||
// track visible cluster from ui
|
||||
ipcMain.on("cluster-view:current-id", (event, clusterId: ClusterId) => {
|
||||
@ -72,8 +76,8 @@ export class WindowManager {
|
||||
try {
|
||||
await this.showSplash();
|
||||
await this.mainView.loadURL(`http://localhost:${this.proxyPort}`)
|
||||
this.mainView.show();
|
||||
this.splashWindow.close();
|
||||
this.mainView.show()
|
||||
this.splashWindow.close()
|
||||
} catch (err) {
|
||||
dialog.showErrorBox("ERROR!", err.toString())
|
||||
}
|
||||
|
@ -1,21 +1,29 @@
|
||||
import "./components/app.scss"
|
||||
import React from "react";
|
||||
import * as Mobx from "mobx"
|
||||
import * as LensMainExtensions from "../extensions/extension-api"
|
||||
import * as LensExtensions from "../extensions/extension-renderer-api"
|
||||
import { render, unmountComponentAtNode } from "react-dom";
|
||||
import { isMac } from "../common/vars";
|
||||
import { userStore } from "../common/user-store";
|
||||
import { workspaceStore } from "../common/workspace-store";
|
||||
import { extensionLoader } from "../extensions/extension-loader";
|
||||
import { clusterStore } from "../common/cluster-store";
|
||||
import { i18nStore } from "./i18n";
|
||||
import { themeStore } from "./theme.store";
|
||||
import { App } from "./components/app";
|
||||
import { LensApp } from "./lens-app";
|
||||
import { getLensRuntime } from "../extensions/lens-runtime";
|
||||
|
||||
type AppComponent = React.ComponentType & {
|
||||
init?(): Promise<void>;
|
||||
}
|
||||
|
||||
export {
|
||||
React,
|
||||
Mobx,
|
||||
LensExtensions,
|
||||
LensMainExtensions
|
||||
}
|
||||
|
||||
export async function bootstrap(App: AppComponent) {
|
||||
const rootElem = document.getElementById("app")
|
||||
rootElem.classList.toggle("is-mac", isMac);
|
||||
@ -31,7 +39,6 @@ export async function bootstrap(App: AppComponent) {
|
||||
|
||||
// Register additional store listeners
|
||||
clusterStore.registerIpcListener();
|
||||
extensionLoader.autoEnableOnLoad(getLensRuntime);
|
||||
|
||||
// init app's dependencies if any
|
||||
if (App.init) {
|
||||
|
@ -17,6 +17,7 @@ import { themeStore } from "../../theme.store";
|
||||
import { history } from "../../navigation";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { KubectlBinaries } from "./kubectl-binaries";
|
||||
import { appPreferenceRegistry } from "../../../extensions/app-preference-registry";
|
||||
|
||||
@observer
|
||||
export class Preferences extends React.Component {
|
||||
@ -114,6 +115,7 @@ export class Preferences extends React.Component {
|
||||
|
||||
render() {
|
||||
const { preferences } = userStore;
|
||||
const extensionPreferences = appPreferenceRegistry.preferences
|
||||
const header = (
|
||||
<>
|
||||
<h2>Preferences</h2>
|
||||
@ -185,15 +187,17 @@ export class Preferences extends React.Component {
|
||||
<Trans>Does not affect cluster communications!</Trans>
|
||||
</small>
|
||||
|
||||
<h2><Trans>Telemetry & Usage Tracking</Trans></h2>
|
||||
<Checkbox
|
||||
label={<Trans>Allow telemetry & usage tracking</Trans>}
|
||||
value={preferences.allowTelemetry}
|
||||
onChange={v => preferences.allowTelemetry = v}
|
||||
/>
|
||||
<small className="hint">
|
||||
<Trans>Telemetry & usage data is collected to continuously improve the Lens experience.</Trans>
|
||||
</small>
|
||||
{extensionPreferences.map(({title, components: { Hint, Input}}) => {
|
||||
return (
|
||||
<div key={title}>
|
||||
<h2>{title}</h2>
|
||||
<Input />
|
||||
<small className="hint">
|
||||
<Hint />
|
||||
</small>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</WizardLayout>
|
||||
</div>
|
||||
);
|
||||
|
@ -36,7 +36,10 @@ import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store
|
||||
import logger from "../../main/logger";
|
||||
import { clusterIpc } from "../../common/cluster-ipc";
|
||||
import { webFrame } from "electron";
|
||||
import { DynamicPage, dynamicPages } from "../../extensions/register-page";
|
||||
import { pageRegistry } from "../../extensions/page-registry";
|
||||
import { DynamicPage } from "../../extensions/dynamic-page";
|
||||
import { extensionLoader } from "../../extensions/extension-loader";
|
||||
import { getLensRuntime } from "../../extensions/lens-runtime";
|
||||
|
||||
@observer
|
||||
export class App extends React.Component {
|
||||
@ -45,8 +48,10 @@ export class App extends React.Component {
|
||||
const clusterId = getHostedClusterId();
|
||||
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`)
|
||||
await Terminal.preloadFonts()
|
||||
|
||||
await clusterIpc.setFrameId.invokeFromRenderer(clusterId, frameId);
|
||||
await getHostedCluster().whenReady; // cluster.activate() is done at this point
|
||||
extensionLoader.loadOnClusterRenderer(getLensRuntime)
|
||||
}
|
||||
|
||||
get startURL() {
|
||||
@ -74,7 +79,7 @@ export class App extends React.Component {
|
||||
<Route component={CustomResources} {...crdRoute}/>
|
||||
<Route component={UserManagement} {...usersManagementRoute}/>
|
||||
<Route component={Apps} {...appsRoute}/>
|
||||
{dynamicPages.clusterPages.map(page => {
|
||||
{pageRegistry.clusterPages.map(page => {
|
||||
return <Route {...page} key={page.path} render={() => <DynamicPage page={page}/>}/>
|
||||
})}
|
||||
<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 { clusterStore } from "../../../common/cluster-store";
|
||||
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
||||
import { dynamicPages } from "../../../extensions/register-page";
|
||||
import { pageRegistry } from "../../../extensions/page-registry";
|
||||
|
||||
@observer
|
||||
export class ClusterManager extends React.Component {
|
||||
@ -63,7 +63,7 @@ export class ClusterManager extends React.Component {
|
||||
<Route component={AddCluster} {...addClusterRoute} />
|
||||
<Route component={ClusterView} {...clusterViewRoute} />
|
||||
<Route component={ClusterSettings} {...clusterSettingsRoute} />
|
||||
{dynamicPages.globalPages.map(({ path, components: { Page } }) => {
|
||||
{pageRegistry.globalPages.map(({ path, components: { Page } }) => {
|
||||
return <Route key={path} path={path} component={Page}/>
|
||||
})}
|
||||
<Redirect exact to={this.startUrl} />
|
||||
|
@ -22,7 +22,7 @@ import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||
import { clusterViewURL } from "./cluster-view.route";
|
||||
import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd";
|
||||
import { dynamicPages } from "../../../extensions/register-page";
|
||||
import { pageRegistry } from "../../../extensions/page-registry";
|
||||
|
||||
interface Props {
|
||||
className?: IClassName;
|
||||
@ -156,7 +156,7 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
)}
|
||||
</div>
|
||||
<div className="dynamic-pages">
|
||||
{dynamicPages.globalPages.map(({ path, components: { MenuIcon } }) => {
|
||||
{pageRegistry.globalPages.map(({ path, components: { MenuIcon } }) => {
|
||||
return <MenuIcon key={path} onClick={() => navigate(path)}/>
|
||||
})}
|
||||
</div>
|
||||
|
@ -28,7 +28,7 @@ import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resourc
|
||||
import { CustomResources } from "../+custom-resources/custom-resources";
|
||||
import { navigation } from "../../navigation";
|
||||
import { isAllowedResource } from "../../../common/rbac"
|
||||
import { dynamicPages } from "../../../extensions/register-page";
|
||||
import { pageRegistry } from "../../../extensions/page-registry";
|
||||
|
||||
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
|
||||
type SidebarContextValue = {
|
||||
@ -184,7 +184,7 @@ export class Sidebar extends React.Component<Props> {
|
||||
>
|
||||
{this.renderCustomResources()}
|
||||
</SidebarNavItem>
|
||||
{dynamicPages.clusterPages.map(({ path, title, components: { MenuIcon } }) => {
|
||||
{pageRegistry.clusterPages.map(({ path, title, components: { MenuIcon } }) => {
|
||||
return (
|
||||
<SidebarNavItem
|
||||
key={path}
|
||||
|
@ -11,9 +11,15 @@ import { ErrorBoundary } from "./components/error-boundary";
|
||||
import { WhatsNew, whatsNewRoute } from "./components/+whats-new";
|
||||
import { Notifications } from "./components/notifications";
|
||||
import { ConfirmDialog } from "./components/confirm-dialog";
|
||||
import { extensionLoader } from "../extensions/extension-loader";
|
||||
import { getLensRuntime } from "../extensions/lens-runtime";
|
||||
|
||||
@observer
|
||||
export class LensApp extends React.Component {
|
||||
static async init() {
|
||||
extensionLoader.loadOnMainRenderer(getLensRuntime)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<I18nProvider i18n={_i18n}>
|
||||
|
@ -17,16 +17,12 @@ export default function (): webpack.Configuration {
|
||||
main: path.resolve(mainDir, "index.ts"),
|
||||
},
|
||||
output: {
|
||||
libraryTarget: "global",
|
||||
path: buildDir,
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.json', '.js', '.ts']
|
||||
},
|
||||
node: {
|
||||
// webpack modifies node internals by default, keep as is for main-process
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
externals: [
|
||||
nodeExternals()
|
||||
],
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { appName, buildDir, extensionsDir, extensionsLibName, htmlTemplate, isDevelopment, isProduction, publicPath, rendererDir, sassCommonVars } from "./src/common/vars";
|
||||
import { appName, buildDir, extensionsDir, extensionsLibName, extensionsRendererLibName, htmlTemplate, isDevelopment, isProduction, publicPath, rendererDir, sassCommonVars } from "./src/common/vars";
|
||||
import path from "path";
|
||||
import webpack from "webpack";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
@ -8,22 +8,9 @@ import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
|
||||
import ProgressBarPlugin from "progress-bar-webpack-plugin";
|
||||
|
||||
export default [
|
||||
webpackLensRenderer,
|
||||
webpackExtensionsApi,
|
||||
webpackLensRenderer
|
||||
]
|
||||
|
||||
// todo: use common chunks/externals for "react", "react-dom", etc.
|
||||
export function webpackExtensionsApi(): webpack.Configuration {
|
||||
const config = webpackLensRenderer({ showVars: false });
|
||||
config.name = "extensions-api"
|
||||
config.entry = {
|
||||
[extensionsLibName]: path.resolve(extensionsDir, "extension-api.ts")
|
||||
};
|
||||
config.output.libraryTarget = "commonjs2"
|
||||
config.devtool = "nosources-source-map";
|
||||
return config;
|
||||
}
|
||||
|
||||
export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration {
|
||||
if (showVars) {
|
||||
console.info('WEBPACK:renderer', require("./src/common/vars"));
|
||||
@ -39,6 +26,9 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
[appName]: path.resolve(rendererDir, "bootstrap.tsx"),
|
||||
},
|
||||
output: {
|
||||
libraryTarget: "global",
|
||||
library: "",
|
||||
globalObject: "this",
|
||||
publicPath: publicPath,
|
||||
path: buildDir,
|
||||
filename: '[name].js',
|
||||
@ -184,4 +174,4 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
}),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8971,11 +8971,6 @@ mock-fs@^4.12.0:
|
||||
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.12.0.tgz#a5d50b12d2d75e5bec9dac3b67ffe3c41d31ade4"
|
||||
integrity sha512-/P/HtrlvBxY4o/PzXY9cCNBrdylDNxg7gnrv2sMNxj+UJ2m8jSpl0/A6fuJeNAWr99ZvGWH8XCbE0vmnM5KupQ==
|
||||
|
||||
module-alias@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
|
||||
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
|
||||
|
||||
moment@^2.10.2, moment@^2.26.0:
|
||||
version "2.26.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
|
||||
|
Loading…
Reference in New Issue
Block a user