From 0d0c67f13f1ce4d21e8391987172b8bbd70d93c2 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 4 Jun 2021 05:44:25 -0400 Subject: [PATCH] Update docs to be correct with the new api changes (#2916) * Update docs to be correct with the new api changes Signed-off-by: Sebastian Malton * Fix renderer-extension.md Signed-off-by: Sebastian Malton --- .../capabilities/common-capabilities.md | 66 ++-- docs/extensions/capabilities/styling.md | 4 +- docs/extensions/get-started/anatomy.md | 4 +- docs/extensions/guides/README.md | 11 +- docs/extensions/guides/ipc.md | 20 +- .../guides/kube-object-list-layout.md | 104 ++++-- docs/extensions/guides/main-extension.md | 38 +-- docs/extensions/guides/protocol-handlers.md | 6 +- docs/extensions/guides/renderer-extension.md | 323 +++++++++++------- docs/extensions/guides/stores.md | 22 +- .../testing-and-publishing/testing.md | 8 +- src/common/vars.ts | 12 +- src/extensions/main-api/index.ts | 2 + src/extensions/main-api/navigation.ts | 26 ++ 14 files changed, 395 insertions(+), 251 deletions(-) create mode 100644 src/extensions/main-api/navigation.ts diff --git a/docs/extensions/capabilities/common-capabilities.md b/docs/extensions/capabilities/common-capabilities.md index 676603ce21..ffb066f72f 100644 --- a/docs/extensions/capabilities/common-capabilities.md +++ b/docs/extensions/capabilities/common-capabilities.md @@ -14,9 +14,9 @@ In order to see logs from this extension, you need to start Lens from the comman This extension can register a custom callback that is executed when an extension is activated (started). ``` javascript -import { LensMainExtension } from "@k8slens/extensions" +import { Main } from "@k8slens/extensions" -export default class ExampleMainExtension extends LensMainExtension { +export default class ExampleMainExtension extends Main.LensExtension { async onActivate() { console.log("hello world") } @@ -28,9 +28,9 @@ export default class ExampleMainExtension extends LensMainExtension { This extension can register a custom callback that is executed when an extension is deactivated (stopped). ``` javascript -import { LensMainExtension } from "@k8slens/extensions" +import { Main } from "@k8slens/extensions" -export default class ExampleMainExtension extends LensMainExtension { +export default class ExampleMainExtension extends Main.LensExtension { async onDeactivate() { console.log("bye bye") } @@ -44,15 +44,15 @@ This extension can register custom app menus that will be displayed on OS native Example: ```typescript -import { LensMainExtension, windowManager } from "@k8slens/extensions" +import { Main } from "@k8slens/extensions" -export default class ExampleMainExtension extends LensMainExtension { +export default class ExampleMainExtension extends Main.LensExtension { appMenus = [ { parentId: "help", label: "Example item", click() { - windowManager.navigate("https://k8slens.dev"); + Main.Navigation.navigate("https://k8slens.dev"); } } ] @@ -69,9 +69,9 @@ In order to see logs from this extension you need to check them via **View** > * This extension can register a custom callback that is executed when an extension is activated (started). ``` javascript -import { LensRendererExtension } from "@k8slens/extensions" +import { Renderer } from "@k8slens/extensions" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { async onActivate() { console.log("hello world") } @@ -83,9 +83,9 @@ export default class ExampleExtension extends LensRendererExtension { This extension can register a custom callback that is executed when an extension is deactivated (stopped). ``` javascript -import { LensRendererExtension } from "@k8slens/extensions" +import { Renderer } from "@k8slens/extensions" -export default class ExampleMainExtension extends LensRendererExtension { +export default class ExampleMainExtension extends Renderer.LensExtension { async onDeactivate() { console.log("bye bye") } @@ -99,10 +99,16 @@ The global page is a full-screen page that hides all other content from a window ```typescript import React from "react" -import { Component, LensRendererExtension } from "@k8slens/extensions" +import { Renderer } from "@k8slens/extensions" import { ExamplePage } from "./src/example-page" -export default class ExampleRendererExtension extends LensRendererExtension { +const { + Component: { + Icon, + } +} = Renderer; + +export default class ExampleRendererExtension extends Renderer.LensExtension { globalPages = [ { id: "example", @@ -117,7 +123,7 @@ export default class ExampleRendererExtension extends LensRendererExtension { title: "Example page", // used in icon's tooltip target: { pageId: "example" } components: { - Icon: () => , + Icon: () => , } } ] @@ -131,12 +137,12 @@ It is responsible for storing a state for custom preferences. ```typescript import React from "react" -import { LensRendererExtension } from "@k8slens/extensions" +import { Renderer } from "@k8slens/extensions" import { myCustomPreferencesStore } from "./src/my-custom-preferences-store" import { MyCustomPreferenceHint, MyCustomPreferenceInput } from "./src/my-custom-preference" -export default class ExampleRendererExtension extends LensRendererExtension { +export default class ExampleRendererExtension extends Renderer.LensExtension { appPreferences = [ { title: "My Custom Preference", @@ -156,10 +162,10 @@ These pages are visible in a cluster menu when a cluster is opened. ```typescript import React from "react" -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { ExampleIcon, ExamplePage } from "./src/page" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { clusterPages = [ { id: "extension-example", // optional @@ -190,10 +196,10 @@ These features are visible in the "Cluster Settings" page. ```typescript import React from "react" -import { LensRendererExtension } from "@k8slens/extensions" +import { Renderer } from "@k8slens/extensions" import { MyCustomFeature } from "./src/my-custom-feature" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { clusterFeatures = [ { title: "My Custom Feature", @@ -219,15 +225,21 @@ This extension can register custom icons and text to a status bar area. ```typescript import React from "react"; -import { Component, LensRendererExtension, Navigation } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; -export default class ExampleExtension extends LensRendererExtension { +const { + Component: { + Icon, + } +} = Renderer; + +export default class ExampleExtension extends Renderer.LensExtension { statusBarItems = [ { components: { Item: (
this.navigate("/example-page")} > - +
) } @@ -243,10 +255,10 @@ This extension can register custom menu items (actions) for specified Kubernetes ```typescript import React from "react" -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { CustomMenuItem, CustomMenuItemProps } from "./src/custom-menu-item" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { kubeObjectMenuItems = [ { kind: "Node", @@ -266,10 +278,10 @@ This extension can register custom details (content) for specified Kubernetes ki ```typescript import React from "react" -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { CustomKindDetails, CustomKindDetailsProps } from "./src/custom-kind-details" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { kubeObjectDetailItems = [ { kind: "CustomKind", diff --git a/docs/extensions/capabilities/styling.md b/docs/extensions/capabilities/styling.md index 62dbddde1a..152a2e3937 100644 --- a/docs/extensions/capabilities/styling.md +++ b/docs/extensions/capabilities/styling.md @@ -114,14 +114,14 @@ There is a way of detect active theme and its changes in JS. [MobX observer func ```js import React from "react" import { observer } from "mobx-react" -import { App, Component, Theme } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; @observer export class SupportPage extends React.Component { render() { return (
-

Active theme is {Theme.getActiveTheme().name}

+

Active theme is {Renderer.Theme.getActiveTheme().name}

); } diff --git a/docs/extensions/get-started/anatomy.md b/docs/extensions/get-started/anatomy.md index f445e421bf..87b3042189 100644 --- a/docs/extensions/get-started/anatomy.md +++ b/docs/extensions/get-started/anatomy.md @@ -96,11 +96,11 @@ It also registers the `MenuItem` component that displays the `ExampleIcon` React These React components are defined in the additional `./src/page.tsx` file. ``` typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { ExampleIcon, ExamplePage } from "./page" import React from "react" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { clusterPages = [ { id: "extension-example", diff --git a/docs/extensions/guides/README.md b/docs/extensions/guides/README.md index 012794a39e..c1c2521f5a 100644 --- a/docs/extensions/guides/README.md +++ b/docs/extensions/guides/README.md @@ -30,9 +30,8 @@ Each guide or code sample includes the following: | Sample | APIs | | ----- | ----- | -[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension
LensRendererExtension
Component.Icon
Component.IconProps | -[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension
Store.ClusterStore
Store.workspaceStore | -[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension
LensRendererExtension
Component.Icon
Component.IconProps | -[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension
LensRendererExtension
Component.Icon
Component.IconProps | -[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension
LensRendererExtension
Component.Icon
Component.IconProps | -[custom-resource-page](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) | LensRendererExtension
K8sApi.KubeApi
K8sApi.KubeObjectStore
Component.KubeObjectListLayout
Component.KubeObjectDetailsProps
Component.IconProps | +[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | +[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | +[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | +[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension
LensRendererExtension
Renderer.Component.Icon
Renderer.Component.IconProps | +[custom-resource-page](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) | LensRendererExtension
Renderer.K8sApi.KubeApi
Renderer.K8sApi.KubeObjectStore
Renderer.Component.KubeObjectListLayout
Renderer.Component.KubeObjectDetailsProps
Renderer.Component.IconProps | diff --git a/docs/extensions/guides/ipc.md b/docs/extensions/guides/ipc.md index e95cb25883..df4684ac5d 100644 --- a/docs/extensions/guides/ipc.md +++ b/docs/extensions/guides/ipc.md @@ -43,10 +43,10 @@ To register either a handler or a listener, you should do something like the fol `main.ts`: ```typescript -import { LensMainExtension } from "@k8slens/extensions"; +import { Main } from "@k8slens/extensions"; import { IpcMain } from "./helpers/main"; -export class ExampleExtensionMain extends LensMainExtension { +export class ExampleExtensionMain extends Main.LensExtension { onActivate() { IpcMain.createInstance(this); } @@ -60,10 +60,10 @@ Lens will automatically clean up that store and all the handlers on deactivation `helpers/main.ts`: ```typescript -import { Ipc, Types } from "@k8slens/extensions"; +import { Main } from "@k8slens/extensions"; -export class IpcMain extends Ipc.Main { - constructor(extension: LensMainExtension) { +export class IpcMain extends Main.Ipc { + constructor(extension: Main.LensExtension) { super(extension); this.listen("initialize", onInitialize); @@ -82,10 +82,10 @@ You should be able to just call `IpcMain.getInstance()` anywhere it is needed in `renderer.ts`: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { IpcRenderer } from "./helpers/renderer"; -export class ExampleExtensionRenderer extends LensRendererExtension { +export class ExampleExtensionRenderer extends Renderer.LensExtension { onActivate() { const ipc = IpcRenderer.createInstance(this); @@ -100,9 +100,9 @@ It is also needed to create an instance to broadcast messages too. `helpers/renderer.ts`: ```typescript -import { Ipc } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; -export class IpcRenderer extends Ipc.Renderer {} +export class IpcRenderer extends Renderer.Ipc {} ``` It is necessary to create child classes of these `abstract class`'s in your extension before you can use them. @@ -116,7 +116,7 @@ There is no behind the scenes transfer of these functions. To register a "handler" call `IpcMain.getInstance().handle(...)`. The cleanup of these handlers is handled by Lens itself. -The `listen()` methods on `Ipc.Main` and `Ipc.Renderer` return a `Disposer`, or more specifically, a `() => void`. +The `listen()` methods on `Main.Ipc` and `Renderer.Ipc` return a `Disposer`, or more specifically, a `() => void`. This can be optionally called to remove the listener early. Calling either `IpcRenderer.getInstance().broadcast(...)` or `IpcMain.getInstance().broadcast(...)` sends an event to all `renderer` frames and to `main`. diff --git a/docs/extensions/guides/kube-object-list-layout.md b/docs/extensions/guides/kube-object-list-layout.md index 99f6796c91..f5ee72e902 100644 --- a/docs/extensions/guides/kube-object-list-layout.md +++ b/docs/extensions/guides/kube-object-list-layout.md @@ -18,7 +18,7 @@ First thing we need to do with our extension is to register new menu item in the We will do this in our extension class `CrdSampleExtension` that is derived `LensRendererExtension` class: ```typescript -export default class CrdSampleExtension extends LensRendererExtension { +export default class CrdSampleExtension extends Renderer.LensExtension { } ``` @@ -27,11 +27,21 @@ This object will register a menu item with "Certificates" text. It will also use `CertificateIcon` component to render an icon and navigate to cluster page that is having `certificates` page id. ```typescript -export function CertificateIcon(props: Component.IconProps) { - return +import { Renderer } from "@k8slens/extensions"; + +type IconProps = Renderer.Component.IconProps; + +const { + Component: { + Icon, + }, +} = Renderer; + +export function CertificateIcon(props: IconProps) { + return } -export default class CrdSampleExtension extends LensRendererExtension { +export default class CrdSampleExtension extends Renderer.LensExtension { clusterPageMenus = [ { @@ -48,7 +58,7 @@ export default class CrdSampleExtension extends LensRendererExtension { Then we need to register `PageRegistration` object with `certificates` id and define `CertificatePage` component to render certificates. ```typescript -export default class CrdSampleExtension extends LensRendererExtension { +export default class CrdSampleExtension extends Renderer.LensExtension { ... clusterPages = [{ @@ -65,18 +75,29 @@ export default class CrdSampleExtension extends LensRendererExtension { In the previous step we defined `CertificatePage` component to render certificates. In this step we will actually implement that. -`CertificatePage` is a React component that will render `Component.KubeObjectListLayout` component to list `Certificate` CRD objects. +`CertificatePage` is a React component that will render `Renderer.Component.KubeObjectListLayout` component to list `Certificate` CRD objects. ### Get CRD objects In order to list CRD objects, we need first fetch those from Kubernetes API. Lens Extensions API provides easy mechanism to do this. -We just need to define `Certificate` class derived from `K8sApi.KubeObject`, `CertificatesApi`derived from `K8sApi.KubeApi` and `CertificatesStore` derived from `K8sApi.KubeObjectStore`. +We just need to define `Certificate` class derived from `Renderer.K8sApi.KubeObject`, `CertificatesApi`derived from `Renderer.K8sApi.KubeApi` and `CertificatesStore` derived from `Renderer.K8sApi.KubeObjectStore`. `Certificate` class defines properties found in the CRD object: ```typescript -export class Certificate extends K8sApi.KubeObject { +import { Renderer } from "@k8slens/extensions"; + +const { + K8sApi: { + KubeObject, + KubeObjectStore, + KubeApi, + apiManager, + }, +} = Renderer; + +export class Certificate extends KubeObject { static kind = "Certificate" static namespaced = true static apiBase = "/apis/cert-manager.io/v1alpha2/certificates" @@ -121,8 +142,8 @@ export class Certificate extends K8sApi.KubeObject { With `CertificatesApi` class we are able to manage `Certificate` objects in Kubernetes API: ```typescript -export class CertificatesApi extends K8sApi.KubeApi { -} +export class CertificatesApi extends KubeApi {} + export const certificatesApi = new CertificatesApi({ objectConstructor: Certificate }); @@ -131,7 +152,7 @@ export const certificatesApi = new CertificatesApi({ `CertificateStore` defines storage for `Certificate` objects ```typescript -export class CertificatesStore extends K8sApi.KubeObjectStore { +export class CertificatesStore extends KubeObjectStore { api = certificatesApi } @@ -141,7 +162,7 @@ export const certificatesStore = new CertificatesStore(); And, finally, we register this store to Lens's API manager. ```typescript -K8sApi.apiManager.registerStore(certificatesStore); +apiManager.registerStore(certificatesStore); ``` @@ -153,23 +174,32 @@ Then we need to fetch those and render them in the UI. First we define `CertificatePage` class that extends `React.Component`. ```typescript -import { Component, LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import React from "react"; import { certificatesStore } from "../certificate-store"; import { Certificate } from "../certificate" -export class CertificatePage extends React.Component<{ extension: LensRendererExtension }> { +export class CertificatePage extends React.Component<{ extension: Renderer.LensExtension }> { } ``` Next we will implement `render` method that will display certificates in a list. -To do that, we just need to add `Component.KubeObjectListLayout` component inside `Component.TabLayout` component in render method. -To define which objects the list is showing, we need to pass `certificateStore` object to `Component.KubeObjectListLayout` in `store` property. -`Component.KubeObjectListLayout` will fetch automatically items from the given store when component is mounted. +To do that, we just need to add `Renderer.Component.KubeObjectListLayout` component inside `Renderer.Component.TabLayout` component in render method. +To define which objects the list is showing, we need to pass `certificateStore` object to `Renderer.Component.KubeObjectListLayout` in `store` property. +`Renderer.Component.KubeObjectListLayout` will fetch automatically items from the given store when component is mounted. Also, we can define needed sorting callbacks and search filters for the list: ```typescript +import { Renderer } from "@k8slens/extensions"; + +const { + Component: { + TabLayout, + KubeObjectListLayout, + }, +} = Renderer; + enum sortBy { name = "name", namespace = "namespace", @@ -181,8 +211,8 @@ export class CertificatePage extends React.Component<{ extension: LensRendererEx render() { return ( - - + certificate.getName(), @@ -204,7 +234,7 @@ export class CertificatePage extends React.Component<{ extension: LensRendererEx certificate.spec.issuerRef.name ]} /> - + ) } } @@ -219,7 +249,7 @@ First, we need to register our custom component to render details for the specif We will do this again in `CrdSampleExtension` class: ```typescript -export default class CrdSampleExtension extends LensRendererExtension { +export default class CrdSampleExtension extends Renderer.LensExtension { //... kubeObjectDetailItems = [{ @@ -235,14 +265,22 @@ export default class CrdSampleExtension extends LensRendererExtension { Here we defined that `CertificateDetails` component will render the resource details. So, next we need to implement that component. Lens will inject `Certificate` object into our component so we just need to render some information out of it. -We can use `Component.DrawerItem` component from Lens Extensions API to give the same look and feel as Lens is using elsewhere: +We can use `Renderer.Component.DrawerItem` component from Lens Extensions API to give the same look and feel as Lens is using elsewhere: ```typescript -import { Component, K8sApi } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import React from "react"; import { Certificate } from "../certificate"; -export interface CertificateDetailsProps extends Component.KubeObjectDetailsProps{ +const { + Component: { + KubeObjectDetailsProps, + DrawerItem, + Badge, + } +} = Renderer; + +export interface CertificateDetailsProps extends KubeObjectDetailsProps{ } export class CertificateDetails extends React.Component { @@ -252,29 +290,29 @@ export class CertificateDetails extends React.Component if (!certificate) return null; return (
- + {certificate.getAge(true, false)} ago ({certificate.metadata.creationTimestamp }) - - + + {certificate.spec.dnsNames.join(",")} - - + + {certificate.spec.secretName} - - + + {certificate.status.conditions.map((condition, index) => { const { type, reason, message, status } = condition; const kind = type || reason; if (!kind) return null; return ( - ); })} - +
) } diff --git a/docs/extensions/guides/main-extension.md b/docs/extensions/guides/main-extension.md index f1212c0d37..32d24acdef 100644 --- a/docs/extensions/guides/main-extension.md +++ b/docs/extensions/guides/main-extension.md @@ -11,9 +11,9 @@ The Main Extension API allows you to access, configure, and customize Lens data, To create a main extension simply extend the `LensMainExtension` class: ```typescript -import { LensMainExtension } from "@k8slens/extensions"; +import { Main } from "@k8slens/extensions"; -export default class ExampleExtensionMain extends LensMainExtension { +export default class ExampleExtensionMain extends Main.LensExtension { onActivate() { console.log('custom main process extension code started'); } @@ -39,34 +39,6 @@ The example above logs messages when the extension is enabled and disabled. To see standard output from the main process there must be a console connected to it. Achieve this by starting Lens from the command prompt. -The following example is a little more interesting. -It accesses some Lens state data, and it periodically logs the name of the cluster that is currently active in Lens. - -```typescript -import { LensMainExtension, Store } from "@k8slens/extensions"; - -export default class ActiveClusterExtensionMain extends LensMainExtension { - - timer: NodeJS.Timeout - - onActivate() { - console.log("Cluster logger activated"); - this.timer = setInterval(() => { - if (!Store.ClusterStore.getInstance().active) { - console.log("No active cluster"); - return; - } - console.log("active cluster is", Store.ClusterStore.getInstance().active.contextName) - }, 5000) - } - - onDeactivate() { - clearInterval(this.timer) - console.log("Cluster logger deactivated"); - } -} -``` - For more details on accessing Lens state data, please see the [Stores](../stores) guide. ### `appMenus` @@ -76,9 +48,9 @@ Note that this is the only UI feature that the Main Extension API allows you to The following example demonstrates adding an item to the **Help** menu. ``` typescript -import { LensMainExtension } from "@k8slens/extensions"; +import { Main } from "@k8slens/extensions"; -export default class SamplePageMainExtension extends LensMainExtension { +export default class SamplePageMainExtension extends Main.LensExtension { appMenus = [ { parentId: "help", @@ -102,4 +74,4 @@ Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`. * `click()` is called when the menu item is selected. In this example, we simply log a message. However, you would typically have this navigate to a specific page or perform another operation. -Note that pages are associated with the [`LensRendererExtension`](renderer-extension.md) class and can be defined in the process of extending it. +Note that pages are associated with the [`Renderer.LensExtension`](renderer-extension.md) class and can be defined in the process of extending it. diff --git a/docs/extensions/guides/protocol-handlers.md b/docs/extensions/guides/protocol-handlers.md index 8e13c8436a..f3335cba56 100644 --- a/docs/extensions/guides/protocol-handlers.md +++ b/docs/extensions/guides/protocol-handlers.md @@ -18,13 +18,13 @@ In other words, which handler is selected in either process is independent from Example of registering a handler: ```typescript -import { LensMainExtension, Interface } from "@k8slens/extensions"; +import { Main, Common } from "@k8slens/extensions"; -function rootHandler(params: Iterface.ProtocolRouteParams) { +function rootHandler(params: Common.Types.ProtocolRouteParams) { console.log("routed to ExampleExtension", params); } -export default class ExampleExtensionMain extends LensMainExtension { +export default class ExampleExtensionMain extends Main.LensExtension { protocolHandlers = [ pathSchema: "/", handler: rootHandler, diff --git a/docs/extensions/guides/renderer-extension.md b/docs/extensions/guides/renderer-extension.md index b03607bd42..c96391fde0 100644 --- a/docs/extensions/guides/renderer-extension.md +++ b/docs/extensions/guides/renderer-extension.md @@ -24,9 +24,9 @@ All UI elements are based on React components. To create a renderer extension, extend the `LensRendererExtension` class: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; -export default class ExampleExtensionMain extends LensRendererExtension { +export default class ExampleExtensionMain extends Renderer.LensExtension { onActivate() { console.log('custom renderer process extension code started'); } @@ -61,11 +61,11 @@ Use your extension to access Kubernetes resources in the active cluster with [`C Add a cluster page definition to a `LensRendererExtension` subclass with the following example: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { ExampleIcon, ExamplePage } from "./page" import React from "react" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { clusterPages = [ { id: "hello", @@ -88,7 +88,7 @@ It offers flexibility in defining the appearance and behavior of your page. `ExamplePage` in the example above can be defined in `page.tsx`: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import React from "react" export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> { @@ -116,11 +116,11 @@ Use `clusterPageMenus`, covered in the next section, to add cluster pages to the By expanding on the above example, you can add a cluster page menu item to the `ExampleExtension` definition: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { ExampleIcon, ExamplePage } from "./page" import React from "react" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { clusterPages = [ { id: "hello", @@ -157,14 +157,20 @@ When users click **Hello World**, the cluster dashboard will show the contents o This example requires the definition of another React-based component, `ExampleIcon`, which has been added to `page.tsx`, as follows: ```typescript -import { LensRendererExtension, Component } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import React from "react" -export function ExampleIcon(props: Component.IconProps) { - return +type IconProps = Renderer.Component.IconProps; + +const { + Component: { Icon }, +} = Renderer; + +export function ExampleIcon(props: IconProps) { + return } -export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> { +export class ExamplePage extends React.Component<{ extension: Renderer.LensExtension }> { render() { return (
@@ -176,8 +182,8 @@ export class ExamplePage extends React.Component<{ extension: LensRendererExtens ``` Lens includes various built-in components available for extension developers to use. -One of these is the `Component.Icon`, introduced in `ExampleIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io). -The properties that `Component.Icon` uses are defined as follows: +One of these is the `Renderer.Component.Icon`, introduced in `ExampleIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io). +The properties that `Renderer.Component.Icon` uses are defined as follows: * `material` takes the name of the icon you want to use. * `tooltip` sets the text you want to appear when a user hovers over the icon. @@ -187,11 +193,11 @@ The following example groups two sub menu items under one parent menu item: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { ExampleIcon, ExamplePage } from "./page" import React from "react" -export default class ExampleExtension extends LensRendererExtension { +export default class ExampleExtension extends Renderer.LensExtension { clusterPages = [ { id: "hello", @@ -261,11 +267,11 @@ Unlike cluster pages, users can trigger global pages even when there is no activ The following example defines a `LensRendererExtension` subclass with a single global page definition: ```typescript -import { LensRendererExtension } from '@k8slens/extensions'; +import { Renderer } from '@k8slens/extensions'; import { HelpPage } from './page'; import React from 'react'; -export default class HelpExtension extends LensRendererExtension { +export default class HelpExtension extends Renderer.LensExtension { globalPages = [ { id: "help", @@ -288,7 +294,7 @@ It offers flexibility in defining the appearance and behavior of your page. `HelpPage` in the example above can be defined in `page.tsx`: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import React from "react" export class HelpPage extends React.Component<{ extension: LensRendererExtension }> { @@ -320,11 +326,11 @@ Global pages can be made available in the following ways: By expanding on the above example, you can add a global page menu item to the `HelpExtension` definition: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { HelpIcon, HelpPage } from "./page" import React from "react" -export default class HelpExtension extends LensRendererExtension { +export default class HelpExtension extends Renderer.LensExtension { globalPages = [ { id: "help", @@ -362,14 +368,20 @@ This example requires the definition of another React-based component, `HelpIcon Update `page.tsx` from the example above with the `HelpIcon` definition, as follows: ```typescript -import { LensRendererExtension, Component } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import React from "react" -export function HelpIcon(props: Component.IconProps) { - return +type IconProps = Renderer.Component.IconProps; + +const { + Component: { Icon }, +} = Renderer; + +export function HelpIcon(props: IconProps) { + return } -export class HelpPage extends React.Component<{ extension: LensRendererExtension }> { +export class HelpPage extends React.Component<{ extension: Renderer.LensExtension }> { render() { return (
@@ -381,8 +393,8 @@ export class HelpPage extends React.Component<{ extension: LensRendererExtension ``` Lens includes various built-in components available for extension developers to use. -One of these is the `Component.Icon`, introduced in `HelpIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io). -The property that `Component.Icon` uses is defined as follows: +One of these is the `Renderer.Component.Icon`, introduced in `HelpIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io). +The property that `Renderer.Component.Icon` uses is defined as follows: * `material` takes the name of the icon you want to use. @@ -401,11 +413,11 @@ They can be installed and uninstalled by the Lens user from the cluster **Settin The following example shows how to add a cluster feature as part of a `LensRendererExtension`: ```typescript -import { LensRendererExtension } from "@k8slens/extensions" +import { Renderer } from "@k8slens/extensions" import { ExampleFeature } from "./src/example-feature" import React from "react" -export default class ExampleFeatureExtension extends LensRendererExtension { +export default class ExampleFeatureExtension extends Renderer.LensExtension { clusterFeatures = [ { title: "Example Feature", @@ -462,45 +474,69 @@ Consider using the following properties with `updateStatus()`: The following shows a very simple implementation of a `ClusterFeature`: ```typescript -import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions"; +import { Renderer, Common } from "@k8slens/extensions"; import * as path from "path"; -export class ExampleFeature extends ClusterFeature.Feature { +const { + K8sApi: { + ResourceStack, + forCluster, + StorageClass, + Namespace, + } +} = Renderer; - async install(cluster: Store.Cluster): Promise { +type ResourceStack = Renderer.K8sApi.ResourceStack; +type Pod = Renderer.K8sApi.Pod; +type KubernetesCluster = Common.Catalog.KubernetesCluster; - super.applyResources(cluster, path.join(__dirname, "../resources/")); +export interface MetricsStatus { + installed: boolean; + canUpgrade: boolean; +} + +export class ExampleFeature { + protected stack: ResourceStack; + + constructor(protected cluster: KubernetesCluster) { + this.stack = new ResourceStack(cluster, this.name); } - async upgrade(cluster: Store.Cluster): Promise { - return this.install(cluster); + install(): Promise { + return this.stack.kubectlApplyFolder(path.join(__dirname, "../resources/")); } - async updateStatus(cluster: Store.Cluster): Promise { + upgrade(): Promise { + return this.install(config); + } + + async getStatus(): Promise { + const status: MetricsStatus = { installed: false, canUpgrade: false}; + try { - const pod = K8sApi.forCluster(cluster, K8sApi.Pod); + const pod = forCluster(cluster, Pod); const examplePod = await pod.get({name: "example-pod", namespace: "default"}); + if (examplePod?.kind) { - this.status.installed = true; - this.status.currentVersion = examplePod.spec.containers[0].image.split(":")[1]; - this.status.canUpgrade = true; // a real implementation would perform a check here that is relevant to the specific feature + status.installed = true; + status.currentVersion = examplePod.spec.containers[0].image.split(":")[1]; + status.canUpgrade = true; // a real implementation would perform a check here that is relevant to the specific feature } else { - this.status.installed = false; - this.status.canUpgrade = false; + status.installed = false; + status.canUpgrade = false; } } catch(e) { if (e?.error?.code === 404) { - this.status.installed = false; - this.status.canUpgrade = false; + status.installed = false; + status.canUpgrade = false; } } - return this.status; + return status; } - async uninstall(cluster: Store.Cluster): Promise { - const podApi = K8sApi.forCluster(cluster, K8sApi.Pod); - await podApi.delete({name: "example-pod", namespace: "default"}); + async uninstall(): Promise { + return this.stack.kubectlDeleteFolder(this.resourceFolder); } } ``` @@ -539,12 +575,12 @@ You can use Lens extensions to add custom preferences to the Preferences page, p The following example demonstrates adding a custom preference: ```typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference"; import { observable } from "mobx"; import React from "react"; -export default class ExampleRendererExtension extends LensRendererExtension { +export default class ExampleRendererExtension extends Renderer.LensExtension { @observable preference = { enabled: false }; @@ -578,10 +614,16 @@ This is how `ExampleRendererExtension` handles the state of the preference input In this example `ExamplePreferenceInput`, `ExamplePreferenceHint`, and `ExamplePreferenceProps` are defined in `./src/example-preference.tsx` as follows: ```typescript -import { Component } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { observer } from "mobx-react"; import React from "react"; +const { + Component: { + Checkbox, + }, +} = Renderer; + export class ExamplePreferenceProps { preference: { enabled: boolean; @@ -594,7 +636,7 @@ export class ExamplePreferenceInput extends React.Component { preference.enabled = v; }} @@ -612,7 +654,7 @@ export class ExamplePreferenceHint extends React.Component { } ``` -`ExamplePreferenceInput` implements a simple checkbox using Lens's `Component.Checkbox` using the following properties: +`ExamplePreferenceInput` implements a simple checkbox using Lens's `Renderer.Component.Checkbox` using the following properties: * `label` sets the text that displays next to the checkbox. * `value` is initially set to `preference.enabled`. @@ -645,11 +687,11 @@ The following example adds a `statusBarItems` definition and a `globalPages` def It configures the status bar item to navigate to the global page upon activation (normally a mouse click): ```typescript -import { LensRendererExtension } from '@k8slens/extensions'; +import { Renderer } from '@k8slens/extensions'; import { HelpIcon, HelpPage } from "./page" import React from 'react'; -export default class HelpExtension extends LensRendererExtension { +export default class HelpExtension extends Renderer.LensExtension { globalPages = [ { id: "help", @@ -703,16 +745,19 @@ The following example shows how to add a `kubeObjectMenuItems` for namespace res ```typescript import React from "react" -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { NamespaceMenuItem } from "./src/namespace-menu-item" -export default class ExampleExtension extends LensRendererExtension { +type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps; +type Namespace = Renderer.K8sApi.Namespace; + +export default class ExampleExtension extends Renderer.LensExtension { kubeObjectMenuItems = [ { kind: "Namespace", apiVersions: ["v1"], components: { - MenuItem: (props: Component.KubeObjectMenuProps) => + MenuItem: (props: KubeObjectMenuProps) => } } ]; @@ -734,16 +779,28 @@ In this example a `NamespaceMenuItem` object is returned. ```typescript import React from "react"; -import { Component, K8sApi, Navigation} from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; -export function NamespaceMenuItem(props: Component.KubeObjectMenuProps) { +const { + Component: { + terminalStore, + MenuItem, + Icon, + }, + Navigation, +} = Renderer; + +type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps; +type Namespace = Renderer.K8sApi.Namespace; + +export function NamespaceMenuItem(props: KubeObjectMenuProps) { const { object: namespace, toolbar } = props; if (!namespace) return null; const namespaceName = namespace.getName(); const sendToTerminal = (command: string) => { - Component.terminalStore.sendCommand(command, { + terminalStore.sendCommand(command, { enter: true, newTab: true, }); @@ -755,21 +812,21 @@ export function NamespaceMenuItem(props: Component.KubeObjectMenuProps - - Get Pods - + + + Get Pods + ); } ``` -`NamespaceMenuItem` returns a `Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property. +`NamespaceMenuItem` returns a `Renderer.Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property. In the example, `getPods()` opens a terminal tab and runs `kubectl` to get a list of pods running in the current namespace. The name of the namespace is retrieved from `props` passed into `NamespaceMenuItem()`. -`namespace` is the `props.object`, which is of type `K8sApi.Namespace`. -`K8sApi.Namespace` is the API for accessing namespaces. +`namespace` is the `props.object`, which is of type `Renderer.K8sApi.Namespace`. +`Renderer.K8sApi.Namespace` is the API for accessing namespaces. The current namespace in this example is simply given by `namespace.getName()`. Thus, `kubeObjectMenuItems` afford convenient access to the specific resource selected by the user. @@ -783,18 +840,22 @@ These custom details appear on the details page for a specific resource, such as The following example shows how to use `kubeObjectDetailItems` to add a tabulated list of pods to the Namespace resource details page: ```typescript -import React from "react" -import { LensRendererExtension } from "@k8slens/extensions"; -import { NamespaceDetailsItem } from "./src/namespace-details-item" +import React from "react"; +import { Renderer } from "@k8slens/extensions"; +import { NamespaceDetailsItem } from "./src/namespace-details-item"; -export default class ExampleExtension extends LensRendererExtension { +type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps; +type KubeObjectDetailsProps = Renderer.Component.KubeObjectDetailsProps; +type Namespace = Renderer.K8sApi.Namespace; + +export default class ExampleExtension extends Renderer.LensExtension { kubeObjectDetailItems = [ { kind: "Namespace", apiVersions: ["v1"], priority: 10, components: { - Details: (props: Component.KubeObjectDetailsProps) => + Details: (props: KubeObjectDetailsProps) => } } ]; @@ -814,25 +875,39 @@ In this example a `NamespaceDetailsItem` object is returned. `NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`: ``` typescript -import { Component, K8sApi } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { PodsDetailsList } from "./pods-details-list"; import React from "react"; import { observable } from "mobx"; import { observer } from "mobx-react"; -@observer -export class NamespaceDetailsItem extends React.Component> { +const { + K8sApi: { + podsApi, + }, + Component: { + DrawerTitle, + }, +} = Renderer; - @observable private pods: K8sApi.Pod[]; +type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps; +type Namespace = Renderer.K8sApi.Namespace; +type Pod = Renderer.K8sApi.Pod; + +@observer +export class NamespaceDetailsItem extends React.Component> { + @observable private pods: Pod[]; async componentDidMount() { - this.pods = await K8sApi.podsApi.list({namespace: this.props.object.getName()}); + const namespace = this.props.object.getName(); + + this.pods = await podsApi.list({ namespace }); } render() { return (
- +
) @@ -840,14 +915,14 @@ export class NamespaceDetailsItem extends React.Component>`, it can access the current namespace object (type `K8sApi.Namespace`) through `this.props.object`. +Since `NamespaceDetailsItem` extends `React.Component>`, it can access the current namespace object (type `Namespace`) through `this.props.object`. You can query this object for many details about the current namespace. -In the example above, `componentDidMount()` gets the namespace's name using the `K8sApi.Namespace` `getName()` method. +In the example above, `componentDidMount()` gets the namespace's name using the `Namespace` `getName()` method. Use the namespace's name to limit the list of pods only to those in the relevant namespace. -To get this list of pods, this example uses the Kubernetes pods API `K8sApi.podsApi.list()` method. -The `K8sApi.podsApi` is automatically configured for the active cluster. +To get this list of pods, this example uses the Kubernetes pods API `podsApi.list()` method. +The `podsApi` is automatically configured for the active cluster. -Note that `K8sApi.podsApi.list()` is an asynchronous method. +Note that `podsApi.list()` is an asynchronous method. Getting the pods list should occur prior to rendering the `NamespaceDetailsItem`. It is a common technique in React development to await async calls in `componentDidMount()`. However, `componentDidMount()` is called right after the first call to `render()`. @@ -856,51 +931,59 @@ Like in the [`appPreferences` guide](#apppreferences), [`mobx`](https://mobx.js. This is done simply by marking the `pods` field as an `observable` and the `NamespaceDetailsItem` class itself as an `observer`. Finally, the `NamespaceDetailsItem` renders using the `render()` method. -Details are placed in drawers, and using `Component.DrawerTitle` provides a separator from details above this one. -Multiple details in a drawer can be placed in `` elements for further separation, if desired. +Details are placed in drawers, and using `Renderer.Component.DrawerTitle` provides a separator from details above this one. +Multiple details in a drawer can be placed in `` elements for further separation, if desired. The rest of this example's details are defined in `PodsDetailsList`, found in `./pods-details-list.tsx`: ``` typescript import React from "react"; -import { Component, K8sApi } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; + +const { + Component: { + TableHead, + TableRow, + TableCell, + Table, + }, +} = Renderer; + +type Pod = Renderer.K8sApi.Pod; interface Props { - pods: K8sApi.Pod[]; + pods?: Pod[]; } export class PodsDetailsList extends React.Component { - - getTableRow(index: number) { - const {pods} = this.props; - return ( - - {pods[index].getName()} - {pods[index].getAge()} - {pods[index].getStatus()} - - ) - } + getTableRow = (pod: Pod) => { + return ( + + {pods[index].getName()} + {pods[index].getAge()} + {pods[index].getStatus()} + + ) + }; render() { - const {pods} = this.props - if (!pods?.length) { - return null; - } + const { pods } = this.props + + if (!pods?.length) { + return null; + } - return ( -
- - - Name - Age - Status - - { - pods.map((pod, index) => this.getTableRow(index)) - } - -
- ) + return ( +
+ + + Name + Age + Status + + { pods.map(this.getTableRow) } +
+
+ ); } } ``` @@ -909,9 +992,9 @@ export class PodsDetailsList extends React.Component { ![DetailsWithPods](images/kubeobjectdetailitemwithpods.png) -Obtain the name, age, and status for each pod using the `K8sApi.Pod` methods. -Construct the table using the `Component.Table` and related elements. +Obtain the name, age, and status for each pod using the `Renderer.K8sApi.Pod` methods. +Construct the table using the `Renderer.Component.Table` and related elements. -For each pod the name, age, and status are obtained using the `K8sApi.Pod` methods. -The table is constructed using the `Component.Table` and related elements. +For each pod the name, age, and status are obtained using the `Renderer.K8sApi.Pod` methods. +The table is constructed using the `Renderer.Component.Table` and related elements. See [Component documentation](https://docs.k8slens.dev/latest/extensions/api/modules/_renderer_api_components_/) for further details. diff --git a/docs/extensions/guides/stores.md b/docs/extensions/guides/stores.md index 2eaa589198..3a990f58b7 100644 --- a/docs/extensions/guides/stores.md +++ b/docs/extensions/guides/stores.md @@ -24,14 +24,14 @@ This is so that your data is kept up to date and not persisted longer than expec The following example code creates a store for the `appPreferences` guide example: ``` typescript -import { Store } from "@k8slens/extensions"; +import { Common } from "@k8slens/extensions"; import { observable, makeObservable } from "mobx"; export type ExamplePreferencesModel = { enabled: boolean; }; -export class ExamplePreferencesStore extends Store.ExtensionStore { +export class ExamplePreferencesStore extends Common.Store.ExtensionStore { @observable enabled = false; @@ -87,10 +87,10 @@ The following example code, modified from the [`appPreferences`](../renderer-ext This can be done in `./main.ts`: ``` typescript -import { LensMainExtension } from "@k8slens/extensions"; +import { Main } from "@k8slens/extensions"; import { ExamplePreferencesStore } from "./src/example-preference-store"; -export default class ExampleMainExtension extends LensMainExtension { +export default class ExampleMainExtension extends Main.LensExtension { async onActivate() { await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this); } @@ -102,12 +102,12 @@ Similarly, `ExamplePreferencesStore` must load in the renderer process where the This can be done in `./renderer.ts`: ``` typescript -import { LensRendererExtension } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference"; import { ExamplePreferencesStore } from "./src/example-preference-store"; import React from "react"; -export default class ExampleRendererExtension extends LensRendererExtension { +export default class ExampleRendererExtension extends Renderer.LensExtension { async onActivate() { await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this); @@ -130,17 +130,23 @@ Again, `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)` is ca `ExamplePreferenceInput` is defined in `./src/example-preference.tsx`: ``` typescript -import { Component } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; import { observer } from "mobx-react"; import React from "react"; import { ExamplePreferencesStore } from "./example-preference-store"; +const { + Component: { + Checkbox, + }, +} = Renderer; + @observer export class ExamplePreferenceInput extends React.Component { render() { return ( - { ExamplePreferencesStore.getInstace().enabled = v; }} diff --git a/docs/extensions/testing-and-publishing/testing.md b/docs/extensions/testing-and-publishing/testing.md index af178efeb7..cd51cbe3b0 100644 --- a/docs/extensions/testing-and-publishing/testing.md +++ b/docs/extensions/testing-and-publishing/testing.md @@ -14,7 +14,13 @@ My component `GlobalPageMenuIcon` ```typescript import React from "react" -import { Component: { Icon } } from "@k8slens/extensions"; +import { Renderer } from "@k8slens/extensions"; + +const { + Component: { + Icon, + }, +} = Renderer; const GlobalPageMenuIcon = ({ navigate }: { navigate?: () => void }): JSX.Element => (