From 49935a73069ad669b45c50c16b1b7d65ed73ff68 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:34:16 -0400 Subject: [PATCH] Add migration details for extension api docs (#3074) * updating extension api docs for 5.0 Signed-off-by: Jim Ehrismann * beefed up migration details Signed-off-by: Jim Ehrismann * address review comments Signed-off-by: Jim Ehrismann * updated cluster features docs (#3094) Signed-off-by: Jim Ehrismann * revert extension package-lock.json files again Signed-off-by: Jim Ehrismann --- docs/README.md | 8 + .../capabilities/common-capabilities.md | 30 -- docs/extensions/extension-migration.md | 24 ++ docs/extensions/guides/README.md | 5 +- docs/extensions/guides/catalog.md | 5 + .../guides/images/clusterfeature.png | Bin 0 -> 133771 bytes docs/extensions/guides/main-extension.md | 31 +- docs/extensions/guides/renderer-extension.md | 288 +++--------------- docs/extensions/guides/resource-stack.md | 238 +++++++++++++++ mkdocs.yml | 2 + 10 files changed, 343 insertions(+), 288 deletions(-) create mode 100644 docs/extensions/extension-migration.md create mode 100644 docs/extensions/guides/catalog.md create mode 100644 docs/extensions/guides/images/clusterfeature.png create mode 100644 docs/extensions/guides/resource-stack.md diff --git a/docs/README.md b/docs/README.md index f98403eb75..f9ca9e6779 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,6 +35,14 @@ Just like Lens itself, the extension API updates on a monthly cadence, rolling o Keep up with Lens and the Lens Extension API by reviewing the [release notes](https://github.com/lensapp/lens/releases). +## Important changes since Lens v4 + +Lens has undergone major design improvements in v5, which have resulted in several large changes to the extension API. +Workspaces are gone, and the catalog is introduced for containing clusters, as well as other items, including custom entities. +Lens has migrated from using mobx 5 to mobx 6 for internal state management, and this may have ramifications for extension implementations. +Although the API retains many components from v4, given these changes, extensions written for Lens v4 are not compatible with the Lens v5 extension API. +See the [Lens v4 to v5 extension migration notes](extensions/extension-migration.md) on getting old extensions working in Lens v5. + ## Looking for Help If you have questions for extension development, try asking on the [Lens Dev Slack](http://k8slens.slack.com/). It's a public chatroom for Lens developers, where Lens team members chime in from time to time. diff --git a/docs/extensions/capabilities/common-capabilities.md b/docs/extensions/capabilities/common-capabilities.md index a399a7bc03..4993e94181 100644 --- a/docs/extensions/capabilities/common-capabilities.md +++ b/docs/extensions/capabilities/common-capabilities.md @@ -189,36 +189,6 @@ export default class ExampleExtension extends Renderer.LensExtension { ``` -### Cluster Features - -This extension can register installable features for a cluster. -These features are visible in the "Cluster Settings" page. - -```typescript -import React from "react" -import { Renderer } from "@k8slens/extensions" -import { MyCustomFeature } from "./src/my-custom-feature" - -export default class ExampleExtension extends Renderer.LensExtension { - clusterFeatures = [ - { - title: "My Custom Feature", - components: { - Description: () => { - return ( - - Just an example. - - ) - } - }, - feature: new MyCustomFeature() - } - ] -} - -``` - ### Top Bar Items This extension can register custom components to a top bar area. diff --git a/docs/extensions/extension-migration.md b/docs/extensions/extension-migration.md new file mode 100644 index 0000000000..93ccd0d923 --- /dev/null +++ b/docs/extensions/extension-migration.md @@ -0,0 +1,24 @@ +# Lens v4 to v5 Extension Migration Notes + +* Lens v5 inspects the version of the extension to ensure it is compatible. +The `package.json` for your extension must have an `"engines"` field specifying the lens version that your extension is targeted for, e.g: +``` + "engines": { + "lens": "^5.0.0-beta.7" + }, +``` +Note that Lens v5 supports all the range semantics that [semver](https://www.npmjs.com/package/semver) provides. +* Types and components have been reorganized, many have been grouped by process (`Main` and `Renderer`) plus those not specific to a process (`Common`). +For example the `LensMainExtension` class is now referred to by `Main.LensExtension`. +See the [API Reference](api/README.md) for the new organization. +* The `globalPageMenus` field of the Renderer extension class (now `Renderer.LensExtension`) is removed. +Global pages can still be made accessible via the application menus and the status bar, as well as from the newly added Welcome menu. +* The `clusterFeatures` field of the Renderer extension class (now `Renderer.LensExtension`) is removed. +Cluster features can still be implemented but Lens no longer dictates how a feature's lifecycle (install/upgrade/uninstall) is managed. +`Renderer.K8sApi.ResourceStack` provides the functionality to input and apply kubernetes resources to a cluster. +It is up to the extension developer to manage the lifecycle. +It could be applied automatically to a cluster by the extension or the end-user could be expected to install it, etc. from the cluster **Settings** page. +* Lens v5 now relies on mobx 6 for state management. Extensions that use mobx will need to be modified to work with mobx 6. +See [Migrating from Mobx 4/5](https://mobx.js.org/migrating-from-4-or-5.html) for specific details. + +For an example of an existing extension that is compatible with Lens v5 see the [Lens Resource Map Extension](https://github.com/nevalla/lens-resource-map-extension) diff --git a/docs/extensions/guides/README.md b/docs/extensions/guides/README.md index c1c2521f5a..434aec7444 100644 --- a/docs/extensions/guides/README.md +++ b/docs/extensions/guides/README.md @@ -17,8 +17,9 @@ Each guide or code sample includes the following: | Guide | APIs | | ----- | ----- | | [Generate new extension project](generator.md) || -| [Main process extension](main-extension.md) | LensMainExtension | -| [Renderer process extension](renderer-extension.md) | LensRendererExtension | +| [Main process extension](main-extension.md) | Main.LensExtension | +| [Renderer process extension](renderer-extension.md) | Renderer.LensExtension | +| [Resource stack (cluster feature)](resource-stack.md) | | | [Stores](stores.md) | | | [Components](components.md) | | | [KubeObjectListLayout](kube-object-list-layout.md) | | diff --git a/docs/extensions/guides/catalog.md b/docs/extensions/guides/catalog.md new file mode 100644 index 0000000000..5425382638 --- /dev/null +++ b/docs/extensions/guides/catalog.md @@ -0,0 +1,5 @@ +# Catalog (WIP) + +## CatalogCategoryRegistry + +## CatalogEntityRegistry \ No newline at end of file diff --git a/docs/extensions/guides/images/clusterfeature.png b/docs/extensions/guides/images/clusterfeature.png new file mode 100644 index 0000000000000000000000000000000000000000..ada8beba6f5375bc2cb82617db61abfdcbf528eb GIT binary patch literal 133771 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 literal 0 HcmV?d00001 diff --git a/docs/extensions/guides/main-extension.md b/docs/extensions/guides/main-extension.md index 32d24acdef..8a501ca533 100644 --- a/docs/extensions/guides/main-extension.md +++ b/docs/extensions/guides/main-extension.md @@ -2,13 +2,14 @@ The Main Extension API is the interface to Lens's main process. Lens runs in both main and renderer processes. -The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items, and run custom code in Lens's main process. +The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items and [protocol handlers](protocol-handlers.md), and run custom code in Lens's main process. +It also provides convenient methods for navigating to built-in Lens pages and extension pages, as well as adding and removing sources of catalog entities. -## `LensMainExtension` Class +## `Main.LensExtension` Class ### `onActivate()` and `onDeactivate()` Methods -To create a main extension simply extend the `LensMainExtension` class: +To create a main extension simply extend the `Main.LensExtension` class: ```typescript import { Main } from "@k8slens/extensions"; @@ -75,3 +76,27 @@ Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`. 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 [`Renderer.LensExtension`](renderer-extension.md) class and can be defined in the process of extending it. + +The following example demonstrates how an application menu can be used to navigate to such a page: + +``` typescript +import { Main } from "@k8slens/extensions"; + +export default class SamplePageMainExtension extends Main.LensExtension { + appMenus = [ + { + parentId: "help", + label: "Sample", + click: () => this.navigate("myGlobalPage") + } + ] +} +``` + +When the menu item is clicked the `navigate()` method looks for and displays a global page with id `"myGlobalPage"`. +This page would be defined in your extension's `Renderer.LensExtension` implmentation (See [`Renderer.LensExtension`](renderer-extension.md)). + +### `addCatalogSource()` and `removeCatalogSource()` Methods + +The `Main.LensExtension` class also provides the `addCatalogSource()` and `removeCatalogSource()` methods, for managing custom catalog items (or entities). +See the [`Catalog`](catalog.md) documentation for full details about the catalog. \ No newline at end of file diff --git a/docs/extensions/guides/renderer-extension.md b/docs/extensions/guides/renderer-extension.md index c96391fde0..110cf3331f 100644 --- a/docs/extensions/guides/renderer-extension.md +++ b/docs/extensions/guides/renderer-extension.md @@ -1,27 +1,40 @@ -# Renderer Extension +# Renderer Extension (WIP) The Renderer Extension API is the interface to Lens's renderer process. Lens runs in both the main and renderer processes. -The Renderer Extension API allows you to access, configure, and customize Lens data, add custom Lens UI elements, and run custom code in Lens's renderer process. +The Renderer Extension API allows you to access, configure, and customize Lens data, add custom Lens UI elements, protocol handlers, and command palette commands, as well as run custom code in Lens's renderer process. The custom Lens UI elements that you can add include: * [Cluster pages](#clusterpages) * [Cluster page menus](#clusterpagemenus) * [Global pages](#globalpages) -* [Global page menus](#globalpagemenus) +* [Welcome menus](#welcomemenus) * [App preferences](#apppreferences) +* [Top bar items](#topbaritems) * [Status bar items](#statusbaritems) * [KubeObject menu items](#kubeobjectmenuitems) * [KubeObject detail items](#kubeobjectdetailitems) +* [KubeObject status texts](#kubeobjectstatustexts) +* [Kube workloads overview items](#kubeworkloadsoverviewitems) + +as well as catalog-related UI elements: + +* [Entity settings](#entitysettings) +* [Catalog entity detail items](#catalogentitydetailitems) All UI elements are based on React components. -## `LensRendererExtension` Class +Finally, you can also add commands and protocol handlers: + +* [Command palette commands](#commandpalettecommands) +* [protocol handlers](protocol-handlers.md) + +## `Renderer.LensExtension` Class ### `onActivate()` and `onDeactivate()` Methods -To create a renderer extension, extend the `LensRendererExtension` class: +To create a renderer extension, extend the `Renderer.LensExtension` class: ```typescript import { Renderer } from "@k8slens/extensions"; @@ -319,254 +332,7 @@ Global pages can be made available in the following ways: * To add global pages as an interactive element in the blue status bar along the bottom of the Lens UI, see [`statusBarItems`](#statusbaritems). * To add global pages to the left side menu, see [`globalPageMenus`](#globalpagemenus). -### `globalPageMenus` - -`globalPageMenus` allows you to add global page menu items to the left nav. - -By expanding on the above example, you can add a global page menu item to the `HelpExtension` definition: - -```typescript -import { Renderer } from "@k8slens/extensions"; -import { HelpIcon, HelpPage } from "./page" -import React from "react" - -export default class HelpExtension extends Renderer.LensExtension { - globalPages = [ - { - id: "help", - components: { - Page: () => , - } - } - ]; - - globalPageMenus = [ - { - target: { pageId: "help" }, - title: "Help", - components: { - Icon: HelpIcon, - } - }, - ]; -} -``` - -`globalPageMenus` is an array of objects that satisfy the `PageMenuRegistration` interface. -This element defines how the global page menu item will appear and what it will do when you click it. -The properties of the `globalPageMenus` array objects are defined as follows: - -* `target` links to the relevant global page using `pageId`. -* `pageId` takes the value of the relevant global page's `id` property. -* `title` sets the name of the global page menu item that will display as a tooltip in the left nav. -* `components` is used to set an icon that appears in the left nav. - -The above example creates a "Help" icon menu item. -When users click the icon, the Lens UI will display the contents of `ExamplePage`. - -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 { Renderer } from "@k8slens/extensions"; -import React from "react" - -type IconProps = Renderer.Component.IconProps; - -const { - Component: { Icon }, -} = Renderer; - -export function HelpIcon(props: IconProps) { - return -} - -export class HelpPage extends React.Component<{ extension: Renderer.LensExtension }> { - render() { - return ( -
-

Help

-
- ) - } -} -``` - -Lens includes various built-in components available for extension developers to use. -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. - -This is what the example will look like, including how the menu item will appear in the left nav: - -![globalPageMenus](images/globalpagemenus.png) - -### `clusterFeatures` - -Cluster features are Kubernetes resources that can be applied to and managed within the active cluster. -They can be installed and uninstalled by the Lens user from the cluster **Settings** page. - -!!! info - To access the cluster **Settings** page, right-click the relevant cluster in the left side menu and click **Settings**. - -The following example shows how to add a cluster feature as part of a `LensRendererExtension`: - -```typescript -import { Renderer } from "@k8slens/extensions" -import { ExampleFeature } from "./src/example-feature" -import React from "react" - -export default class ExampleFeatureExtension extends Renderer.LensExtension { - clusterFeatures = [ - { - title: "Example Feature", - components: { - Description: () => { - return ( - - Enable an example feature. - - ) - } - }, - feature: new ExampleFeature() - } - ]; -} -``` - -The properties of the `clusterFeatures` array objects are defined as follows: - -* `title` and `components.Description` provide content that appears on the cluster settings page, in the **Features** section. -* `feature` specifies an instance which extends the abstract class `ClusterFeature.Feature`, and specifically implements the following methods: - -```typescript - abstract install(cluster: Cluster): Promise; - abstract upgrade(cluster: Cluster): Promise; - abstract uninstall(cluster: Cluster): Promise; - abstract updateStatus(cluster: Cluster): Promise; -``` - -The four methods listed above are defined as follows: - -* The `install()` method installs Kubernetes resources using the `applyResources()` method, or by directly accessing the [Kubernetes API](../api/README.md). -This method is typically called when a user indicates that they want to install the feature (i.e., by clicking **Install** for the feature in the cluster settings page). - -* The `upgrade()` method upgrades the Kubernetes resources already installed, if they are relevant to the feature. -This method is typically called when a user indicates that they want to upgrade the feature (i.e., by clicking **Upgrade** for the feature in the cluster settings page). - -* The `uninstall()` method uninstalls Kubernetes resources using the [Kubernetes API](../api/README.md). -This method is typically called when a user indicates that they want to uninstall the feature (i.e., by clicking **Uninstall** for the feature in the cluster settings page). - -* The `updateStatus()` method provides the current status information in the `status` field of the `ClusterFeature.Feature` parent class. -Lens periodically calls this method to determine details about the feature's current status. -The implementation of this method should uninstall Kubernetes resources using the Kubernetes api (`K8sApi`) -Consider using the following properties with `updateStatus()`: - - * `status.currentVersion` and `status.latestVersion` may be displayed by Lens in the feature's description. - - * `status.installed` should be set to `true` if the feature is installed, and `false` otherwise. - - * `status.canUpgrade` is set according to a rule meant to determine whether the feature can be upgraded. - This rule can involve `status.currentVersion` and `status.latestVersion`, if desired. - -The following shows a very simple implementation of a `ClusterFeature`: - -```typescript -import { Renderer, Common } from "@k8slens/extensions"; -import * as path from "path"; - -const { - K8sApi: { - ResourceStack, - forCluster, - StorageClass, - Namespace, - } -} = Renderer; - -type ResourceStack = Renderer.K8sApi.ResourceStack; -type Pod = Renderer.K8sApi.Pod; -type KubernetesCluster = Common.Catalog.KubernetesCluster; - -export interface MetricsStatus { - installed: boolean; - canUpgrade: boolean; -} - -export class ExampleFeature { - protected stack: ResourceStack; - - constructor(protected cluster: KubernetesCluster) { - this.stack = new ResourceStack(cluster, this.name); - } - - install(): Promise { - return this.stack.kubectlApplyFolder(path.join(__dirname, "../resources/")); - } - - upgrade(): Promise { - return this.install(config); - } - - async getStatus(): Promise { - const status: MetricsStatus = { installed: false, canUpgrade: false}; - - try { - const pod = forCluster(cluster, Pod); - const examplePod = await pod.get({name: "example-pod", namespace: "default"}); - - if (examplePod?.kind) { - 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 { - status.installed = false; - status.canUpgrade = false; - } - } catch(e) { - if (e?.error?.code === 404) { - status.installed = false; - status.canUpgrade = false; - } - } - - return status; - } - - async uninstall(): Promise { - return this.stack.kubectlDeleteFolder(this.resourceFolder); - } -} -``` - -This example implements the `install()` method by invoking the helper `applyResources()` method. -`applyResources()` tries to apply all resources read from all files found in the folder path provided. -In this case the folder path is the `../resources` subfolder relative to the current source code's folder. -The file `../resources/example-pod.yml` could contain: - -``` yaml -apiVersion: v1 -kind: Pod -metadata: - name: example-pod -spec: - containers: - - name: example-pod - image: nginx -``` - -The example above implements the four methods as follows: - -* It implements `upgrade()` by invoking the `install()` method. -Depending on the feature to be supported by an extension, upgrading may require additional and/or different steps. - -* It implements `uninstall()` by utilizing the [Kubernetes API](../api/README.md) which Lens provides to delete the `example-pod` applied by the `install()` method. - -* It implements `updateStatus()` by using the [Kubernetes API](../api/README.md) which Lens provides to determine whether the `example-pod` is installed, what version is associated with it, and whether it can be upgraded. -The implementation determines what the status is for a specific cluster feature. - +### `welcomeMenus` ### `appPreferences` The Lens **Preferences** page is a built-in global page. @@ -677,6 +443,8 @@ Note that you can manage an extension's state data using an `ExtensionStore` obj To simplify this guide, the example above defines a `preference` field in the `ExampleRendererExtension` class definition to hold the extension's state. However, we recommend that you manage your extension's state data using [`ExtensionStore`](../stores#extensionstore). +### `topBarItems` + ### `statusBarItems` The status bar is the blue strip along the bottom of the Lens UI. @@ -998,3 +766,17 @@ Construct the table using the `Renderer.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. + +### `kubeObjectStatusTexts` + +### `kubeWorkloadsOverviewItems` + +### `entitySettings` + +### `catalogEntityDetailItems` + +### `commandPaletteCommands` + +### `protocolHandlers` + +See the [Protocol Handlers Guide](protocol-handlers.md) diff --git a/docs/extensions/guides/resource-stack.md b/docs/extensions/guides/resource-stack.md new file mode 100644 index 0000000000..8d36bb6495 --- /dev/null +++ b/docs/extensions/guides/resource-stack.md @@ -0,0 +1,238 @@ +# Resource Stack (Cluster Feature) + +A cluster feature is a set of Kubernetes resources that can be applied to and managed within the active cluster. +The `Renderer.K8sApi.ResourceStack` class provides the functionality to input and apply kubernetes resources to a cluster. +It is up to the extension developer to manage the life cycle of the resource stack. +It could be applied automatically to a cluster by the extension, or the end-user could be required to install it. + +The code examples in this section show how to create a resource stack, and define a cluster feature that is configurable from the cluster **Settings** page. + +!!! info + To access the cluster **Settings** page, right-click the relevant cluster in the left side menu and click **Settings**. + +The resource stack in this example consists of a single kubernetes resource: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: example-pod +spec: + containers: + - name: example-pod + image: nginx +``` + +It is simply a pod named `example-pod`, running nginx. Assume this content is in the file `../resources/example-pod.yml`. + +The following code sample shows how to use the `Renderer.K8sApi.ResourceStack` to manage installing and uninstalling this resource stack: + +```typescript +import { Renderer, Common } from "@k8slens/extensions"; +import * as path from "path"; + +const { + K8sApi: { + ResourceStack, + forCluster, + Pod, + } +} = Renderer; + +type ResourceStack = Renderer.K8sApi.ResourceStack; +type Pod = Renderer.K8sApi.Pod; +type KubernetesCluster = Common.Catalog.KubernetesCluster; + +export class ExampleClusterFeature { + protected stack: ResourceStack; + + constructor(protected cluster: KubernetesCluster) { + this.stack = new ResourceStack(cluster, "example-resource-stack"); + } + + get resourceFolder() { + return path.join(__dirname, "../resources/"); + } + + install(): Promise { + console.log("installing example-pod"); + return this.stack.kubectlApplyFolder(this.resourceFolder); + } + + async isInstalled(): Promise { + try { + const podApi = forCluster(this.cluster, Pod); + const examplePod = await podApi.get({name: "example-pod", namespace: "default"}); + + if (examplePod?.kind) { + console.log("found example-pod"); + return true; + } + } catch(e) { + console.log("Error getting example-pod:", e); + } + console.log("didn't find example-pod"); + + return false; + } + + async uninstall(): Promise { + console.log("uninstalling example-pod"); + return this.stack.kubectlDeleteFolder(this.resourceFolder); + } +} +``` + +The `ExampleClusterFeature` class constructor takes a `Common.Catalog.KubernetesCluster` argument. +This is the cluster that the resource stack will be applied to, and the constructor instantiates a `Renderer.K8sApi.ResourceStack` as such. +`ExampleClusterFeature` implements an `install()` method which simply invokes the `kubectlApplyFolder()` method of the `Renderer.K8sApi.ResourceStack` class. +`kubectlApplyFolder()` applies to the cluster all kubernetes resources found in the folder passed to it, in this case `../resources`. +Similarly, `ExampleClusterFeature` implements an `uninstall()` method which simply invokes the `kubectlDeleteFolder()` method of the `Renderer.K8sApi.ResourceStack` class. +`kubectlDeleteFolder()` tries to delete from the cluster all kubernetes resources found in the folder passed to it, again in this case `../resources`. + +`ExampleClusterFeature` also implements an `isInstalled()` method, which demonstrates how you can utiliize the kubernetes api to inspect the resource stack status. +`isInstalled()` simply tries to find a pod named `example-pod`, as a way to determine if the pod is already installed. +This method can be useful in creating a context-sensitive UI for installing/uninstalling the feature, as demonstrated in the next sample code. + +To allow the end-user to control the life cycle of this cluster feature the following code sample shows how to implement a user interface based on React and custom Lens UI components: + +```typescript + import React from "react"; + import { Common, Renderer } from "@k8slens/extensions"; + import { observer } from "mobx-react"; + import { computed, observable, makeObservable } from "mobx"; + import { ExampleClusterFeature } from "./example-cluster-feature"; + + const { + Component: { + SubTitle, Button, + } + } = Renderer; + + interface Props { + cluster: Common.Catalog.KubernetesCluster; + } + + @observer + export class ExampleClusterFeatureSettings extends React.Component { + constructor(props: Props) { + super(props); + makeObservable(this); + } + + @observable installed = false; + @observable inProgress = false; + + feature: ExampleClusterFeature; + + async componentDidMount() { + this.feature = new ExampleClusterFeature(this.props.cluster); + + await this.updateFeatureState(); + } + + async updateFeatureState() { + this.installed = await this.feature.isInstalled(); + } + + async save() { + this.inProgress = true; + + try { + if (this.installed) { + await this.feature.uninstall(); + } else { + await this.feature.install(); + } + } finally { + this.inProgress = false; + + await this.updateFeatureState(); + } + } + + @computed get buttonLabel() { + if (this.inProgress && this.installed) return "Uninstalling ..."; + if (this.inProgress) return "Applying ..."; + + if (this.installed) { + return "Uninstall"; + } + + return "Apply"; + } + + render() { + return ( + <> +
+ +
+ + ); + } +} +``` + +The `ExampleClusterFeatureSettings` class extends `React.Component` and simply renders a subtitle and a button. +`ExampleClusterFeatureSettings` takes the cluster as a prop and when the React component has mounted the `ExampleClusterFeature` is instantiated using this cluster (in `componentDidMount()`). +The rest of the logic concerns the button appearance and action, based on the `ExampleClusterFeatureSettings` fields `installed` and `inProgress`. +The `installed` value is of course determined using the aforementioned `ExampleClusterFeature` method `isInstalled()`. +The `inProgress` value is true while waiting for the feature to be installed (or uninstalled). + +Note that the button is a `Renderer.Component.Button` element and this example takes advantage of its `waiting` prop to show a "waiting" animation while the install (or uninstall) is in progress. +Using elements from `Renderer.Component` is encouraged, to take advantage of their built-in properties, and to ensure a common look and feel. + +Also note that [MobX 6](https://mobx.js.org/README.html) is used for state management, ensuring that the UI is rerendered when state has changed. +The `ExampleClusterFeatureSettings` class is marked as an `@observer`, and its constructor must call `makeObservable()`. +As well, the `installed` and `inProgress` fields are marked as `@observable`, ensuring that the button gets rerendered any time these fields change. + +Finally, `ExampleClusterFeatureSettings` needs to be connected to the extension, and would typically appear on the cluster **Settings** page via a `Renderer.LensExtension.entitySettings` entry. +The `ExampleExtension` would look like this: + +```typescript +import { Common, Renderer } from "@k8slens/extensions"; +import { ExampleClusterFeatureSettings } from "./src/example-cluster-feature-settings" +import React from "react" + +export default class ExampleExtension extends Renderer.LensExtension { + entitySettings = [ + { + apiVersions: ["entity.k8slens.dev/v1alpha1"], + kind: "KubernetesCluster", + title: "Example Cluster Feature", + priority: 5, + components: { + View: ({ entity = null }: { entity: Common.Catalog.KubernetesCluster}) => { + return ( + + ); + } + } + } + ]; + +} +``` + +An entity setting is added to the `entitySettings` array field of the `Renderer.LensExtension` class. +Because Lens's catalog can contain different kinds of entities, the kind must be identified. +For more details about the catalog see the [Catalog Guide](catalog.md). +Clusters are a built-in kind, so the `apiVersions` and `kind` fields should be set as above. +The `title` is shown as a navigation item on the cluster **Settings** page and the `components.View` is displayed when the navigation item is clicked on. +The `components.View` definition above shows how the `ExampleClusterFeatureSettings` element is included, and how its `cluster` prop is set. +`priority` determines the order of the entity settings, the higher the number the higher in the navigation panel the setting is placed. The default value is 50. + +The final result looks like this: + +![Cluster Feature Settings](images/clusterfeature.png) + +`ExampleClusterFeature` and `ExampleClusterFeatureSettings` demonstrate a cluster feature for a simple resource stack. +In practice a resource stack can include many resources, and require more sophisticated life cycle management (upgrades, partial installations, etc.) +Using `Renderer.K8sApi.ResourceStack` and `entitySettings` it is possible to implement solutions for more complex cluster features. +The **Lens Metrics** setting (on the cluster **Settings** page) is a good example of an advanced solution. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 51ad079eaf..4ed763e6d3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,6 +22,8 @@ nav: - Generator: extensions/guides/generator.md - Main Extension: extensions/guides/main-extension.md - Renderer Extension: extensions/guides/renderer-extension.md + - Catalog: extensions/guides/catalog.md + - Resource Stack: extensions/guides/resource-stack.md - Stores: extensions/guides/stores.md - Working with MobX: extensions/guides/working-with-mobx.md - Protocol Handlers: extensions/guides/protocol-handlers.md