1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

more DRY, remove DrawerSection, DrawerSubSection, and DrawerSubTitle

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-03-11 08:57:54 -05:00
parent 2b2395d4dd
commit 3c530ab0ed
31 changed files with 245 additions and 286 deletions

View File

@ -6,29 +6,29 @@ The Renderer Extension API allows you to access, configure, and customize Lens d
The custom Lens UI elements that you can add include: The custom Lens UI elements that you can add include:
* [Cluster pages](#clusterpages) - [Cluster pages](#clusterpages)
* [Cluster page menus](#clusterpagemenus) - [Cluster page menus](#clusterpagemenus)
* [Global pages](#globalpages) - [Global pages](#globalpages)
* [Welcome menus](#welcomemenus) - [Welcome menus](#welcomemenus)
* [App preferences](#apppreferences) - [App preferences](#apppreferences)
* [Top bar items](#topbaritems) - [Top bar items](#topbaritems)
* [Status bar items](#statusbaritems) - [Status bar items](#statusbaritems)
* [KubeObject menu items](#kubeobjectmenuitems) - [KubeObject menu items](#kubeobjectmenuitems)
* [KubeObject detail items](#kubeobjectdetailitems) - [KubeObject detail items](#kubeobjectdetailitems)
* [KubeObject status texts](#kubeobjectstatustexts) - [KubeObject status texts](#kubeobjectstatustexts)
* [Kube workloads overview items](#kubeworkloadsoverviewitems) - [Kube workloads overview items](#kubeworkloadsoverviewitems)
as well as catalog-related UI elements: as well as catalog-related UI elements:
* [Entity settings](#entitysettings) - [Entity settings](#entitysettings)
* [Catalog entity detail items](#catalogentitydetailitems) - [Catalog entity detail items](#catalogentitydetailitems)
All UI elements are based on React components. All UI elements are based on React components.
Finally, you can also add commands and protocol handlers: Finally, you can also add commands and protocol handlers:
* [Command palette commands](#commandpalettecommands) - [Command palette commands](#commandpalettecommands)
* [protocol handlers](protocol-handlers.md) - [protocol handlers](protocol-handlers.md)
## `Renderer.LensExtension` Class ## `Renderer.LensExtension` Class
@ -41,11 +41,11 @@ import { Renderer } from "@k8slens/extensions";
export default class ExampleExtensionMain extends Renderer.LensExtension { export default class ExampleExtensionMain extends Renderer.LensExtension {
onActivate() { onActivate() {
console.log('custom renderer process extension code started'); console.log("custom renderer process extension code started");
} }
onDeactivate() { onDeactivate() {
console.log('custom renderer process extension de-activated'); console.log("custom renderer process extension de-activated");
} }
} }
``` ```
@ -56,7 +56,7 @@ You can initiate custom code by implementing `onActivate()`.
Implementing `onDeactivate()` gives you the opportunity to clean up after your extension. Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
!!! info !!! info
Disable extensions from the Lens Extensions page: Disable extensions from the Lens Extensions page:
1. Navigate to **File** > **Extensions** in the top menu bar. 1. Navigate to **File** > **Extensions** in the top menu bar.
(On Mac, it is **Lens** > **Extensions**.) (On Mac, it is **Lens** > **Extensions**.)
@ -75,17 +75,17 @@ Add a cluster page definition to a `Renderer.LensExtension` subclass with the fo
```typescript ```typescript
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page" import { ExampleIcon, ExamplePage } from "./page";
import React from "react" import React from "react";
export default class ExampleExtension extends Renderer.LensExtension { export default class ExampleExtension extends Renderer.LensExtension {
clusterPages = [ clusterPages = [
{ {
id: "hello", id: "hello",
components: { components: {
Page: () => <ExamplePage extension={this}/>, Page: () => <ExamplePage extension={this} />,
} },
} },
]; ];
} }
``` ```
@ -93,24 +93,26 @@ export default class ExampleExtension extends Renderer.LensExtension {
`clusterPages` is an array of objects that satisfy the `PageRegistration` interface. `clusterPages` is an array of objects that satisfy the `PageRegistration` interface.
The properties of the `clusterPages` array objects are defined as follows: The properties of the `clusterPages` array objects are defined as follows:
* `id` is a string that identifies the page. - `id` is a string that identifies the page.
* `components` matches the `PageComponents` interface for which there is one field, `Page`. - `components` matches the `PageComponents` interface for which there is one field, `Page`.
* `Page` is of type ` React.ComponentType<any>`. - `Page` is of type ` React.ComponentType<any>`.
It offers flexibility in defining the appearance and behavior of your page. It offers flexibility in defining the appearance and behavior of your page.
`ExamplePage` in the example above can be defined in `page.tsx`: `ExamplePage` in the example above can be defined in `page.tsx`:
```typescript ```typescript
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import React from "react" import React from "react";
export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> { export class ExamplePage extends React.Component<{
extension: LensRendererExtension;
}> {
render() { render() {
return ( return (
<div> <div>
<p>Hello world!</p> <p>Hello world!</p>
</div> </div>
) );
} }
} }
``` ```
@ -130,17 +132,17 @@ By expanding on the above example, you can add a cluster page menu item to the `
```typescript ```typescript
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page" import { ExampleIcon, ExamplePage } from "./page";
import React from "react" import React from "react";
export default class ExampleExtension extends Renderer.LensExtension { export default class ExampleExtension extends Renderer.LensExtension {
clusterPages = [ clusterPages = [
{ {
id: "hello", id: "hello",
components: { components: {
Page: () => <ExamplePage extension={this}/>, Page: () => <ExamplePage extension={this} />,
} },
} },
]; ];
clusterPageMenus = [ clusterPageMenus = [
@ -149,7 +151,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
title: "Hello World", title: "Hello World",
components: { components: {
Icon: ExampleIcon, Icon: ExampleIcon,
} },
}, },
]; ];
} }
@ -159,10 +161,10 @@ export default class ExampleExtension extends Renderer.LensExtension {
This element defines how the cluster page menu item will appear and what it will do when you click it. This element defines how the cluster page menu item will appear and what it will do when you click it.
The properties of the `clusterPageMenus` array objects are defined as follows: The properties of the `clusterPageMenus` array objects are defined as follows:
* `target` links to the relevant cluster page using `pageId`. - `target` links to the relevant cluster page using `pageId`.
* `pageId` takes the value of the relevant cluster page's `id` property. - `pageId` takes the value of the relevant cluster page's `id` property.
* `title` sets the name of the cluster page menu item that will appear in the left side menu. - `title` sets the name of the cluster page menu item that will appear in the left side menu.
* `components` is used to set an icon that appears to the left of the `title` text in the left side menu. - `components` is used to set an icon that appears to the left of the `title` text in the left side menu.
The above example creates a menu item that reads **Hello World**. The above example creates a menu item that reads **Hello World**.
When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`. When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`.
@ -171,7 +173,7 @@ This example requires the definition of another React-based component, `ExampleI
```typescript ```typescript
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import React from "react" import React from "react";
type IconProps = Renderer.Component.IconProps; type IconProps = Renderer.Component.IconProps;
@ -180,16 +182,18 @@ const {
} = Renderer; } = Renderer;
export function ExampleIcon(props: IconProps) { export function ExampleIcon(props: IconProps) {
return <Icon {...props} material="pages" tooltip={"Hi!"}/> return <Icon {...props} material="pages" tooltip={"Hi!"} />;
} }
export class ExamplePage extends React.Component<{ extension: Renderer.LensExtension }> { export class ExamplePage extends React.Component<{
extension: Renderer.LensExtension;
}> {
render() { render() {
return ( return (
<div> <div>
<p>Hello world!</p> <p>Hello world!</p>
</div> </div>
) );
} }
} }
``` ```
@ -198,32 +202,31 @@ Lens includes various built-in components available for extension developers to
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). 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: The properties that `Renderer.Component.Icon` uses are defined as follows:
* `material` takes the name of the icon you want to use. - `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. - `tooltip` sets the text you want to appear when a user hovers over the icon.
`clusterPageMenus` can also be used to define sub menu items, so that you can create groups of cluster pages. `clusterPageMenus` can also be used to define sub menu items, so that you can create groups of cluster pages.
The following example groups two sub menu items under one parent menu item: The following example groups two sub menu items under one parent menu item:
```typescript ```typescript
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page" import { ExampleIcon, ExamplePage } from "./page";
import React from "react" import React from "react";
export default class ExampleExtension extends Renderer.LensExtension { export default class ExampleExtension extends Renderer.LensExtension {
clusterPages = [ clusterPages = [
{ {
id: "hello", id: "hello",
components: { components: {
Page: () => <ExamplePage extension={this}/>, Page: () => <ExamplePage extension={this} />,
} },
}, },
{ {
id: "bonjour", id: "bonjour",
components: { components: {
Page: () => <ExemplePage extension={this}/>, Page: () => <ExemplePage extension={this} />,
} },
} },
]; ];
clusterPageMenus = [ clusterPageMenus = [
@ -232,7 +235,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
title: "Greetings", title: "Greetings",
components: { components: {
Icon: ExampleIcon, Icon: ExampleIcon,
} },
}, },
{ {
parentId: "example", parentId: "example",
@ -240,7 +243,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
title: "Hello World", title: "Hello World",
components: { components: {
Icon: ExampleIcon, Icon: ExampleIcon,
} },
}, },
{ {
parentId: "example", parentId: "example",
@ -248,8 +251,8 @@ export default class ExampleExtension extends Renderer.LensExtension {
title: "Bonjour le monde", title: "Bonjour le monde",
components: { components: {
Icon: ExempleIcon, Icon: ExempleIcon,
} },
} },
]; ];
} }
``` ```
@ -280,18 +283,18 @@ Unlike cluster pages, users can trigger global pages even when there is no activ
The following example defines a `Renderer.LensExtension` subclass with a single global page definition: The following example defines a `Renderer.LensExtension` subclass with a single global page definition:
```typescript ```typescript
import { Renderer } from '@k8slens/extensions'; import { Renderer } from "@k8slens/extensions";
import { HelpPage } from './page'; import { HelpPage } from "./page";
import React from 'react'; import React from "react";
export default class HelpExtension extends Renderer.LensExtension { export default class HelpExtension extends Renderer.LensExtension {
globalPages = [ globalPages = [
{ {
id: "help", id: "help",
components: { components: {
Page: () => <HelpPage extension={this}/>, Page: () => <HelpPage extension={this} />,
} },
} },
]; ];
} }
``` ```
@ -299,24 +302,26 @@ export default class HelpExtension extends Renderer.LensExtension {
`globalPages` is an array of objects that satisfy the `PageRegistration` interface. `globalPages` is an array of objects that satisfy the `PageRegistration` interface.
The properties of the `globalPages` array objects are defined as follows: The properties of the `globalPages` array objects are defined as follows:
* `id` is a string that identifies the page. - `id` is a string that identifies the page.
* `components` matches the `PageComponents` interface for which there is one field, `Page`. - `components` matches the `PageComponents` interface for which there is one field, `Page`.
* `Page` is of type `React.ComponentType<any>`. - `Page` is of type `React.ComponentType<any>`.
It offers flexibility in defining the appearance and behavior of your page. It offers flexibility in defining the appearance and behavior of your page.
`HelpPage` in the example above can be defined in `page.tsx`: `HelpPage` in the example above can be defined in `page.tsx`:
```typescript ```typescript
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import React from "react" import React from "react";
export class HelpPage extends React.Component<{ extension: LensRendererExtension }> { export class HelpPage extends React.Component<{
extension: LensRendererExtension;
}> {
render() { render() {
return ( return (
<div> <div>
<p>Help yourself</p> <p>Help yourself</p>
</div> </div>
) );
} }
} }
``` ```
@ -328,11 +333,12 @@ This way, `HelpPage` can access all `HelpExtension` subclass data.
This example code shows how to create a global page, but not how to make that page available to the Lens user. This example code shows how to create a global page, but not how to make that page available to the Lens user.
Global pages are typically made available in the following ways: Global pages are typically made available in the following ways:
* To add global pages to the top menu bar, see [`appMenus`](../main-extension#appmenus) in the Main Extension guide. - To add global pages to the top menu bar, see [`appMenus`](../main-extension#appmenus) in the Main Extension guide.
* 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 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 Welcome Page, see [`welcomeMenus`](#welcomemenus). - To add global pages to the Welcome Page, see [`welcomeMenus`](#welcomemenus).
### `welcomeMenus` ### `welcomeMenus`
### `appPreferences` ### `appPreferences`
The Lens **Preferences** page is a built-in global page. The Lens **Preferences** page is a built-in global page.
@ -342,22 +348,24 @@ The following example demonstrates adding a custom preference:
```typescript ```typescript
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference"; import {
ExamplePreferenceHint,
ExamplePreferenceInput,
} from "./src/example-preference";
import { observable } from "mobx"; import { observable } from "mobx";
import React from "react"; import React from "react";
export default class ExampleRendererExtension extends Renderer.LensExtension { export default class ExampleRendererExtension extends Renderer.LensExtension {
@observable preference = { enabled: false }; @observable preference = { enabled: false };
appPreferences = [ appPreferences = [
{ {
title: "Example Preferences", title: "Example Preferences",
components: { components: {
Input: () => <ExamplePreferenceInput preference={this.preference}/>, Input: () => <ExamplePreferenceInput preference={this.preference} />,
Hint: () => <ExamplePreferenceHint/> Hint: () => <ExamplePreferenceHint />,
} },
} },
]; ];
} }
``` ```
@ -365,13 +373,13 @@ export default class ExampleRendererExtension extends Renderer.LensExtension {
`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface. `appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface.
The properties of the `appPreferences` array objects are defined as follows: The properties of the `appPreferences` array objects are defined as follows:
* `title` sets the heading text displayed on the Preferences page. - `title` sets the heading text displayed on the Preferences page.
* `components` specifies two `React.Component` objects that define the interface for the preference. - `components` specifies two `React.Component` objects that define the interface for the preference.
* `Input` specifies an interactive input element for the preference. - `Input` specifies an interactive input element for the preference.
* `Hint` provides descriptive information for the preference, shown below the `Input` element. - `Hint` provides descriptive information for the preference, shown below the `Input` element.
!!! note !!! note
Note that the input and the hint can be comprised of more sophisticated elements, according to the needs of the extension. Note that the input and the hint can be comprised of more sophisticated elements, according to the needs of the extension.
`ExamplePreferenceInput` expects its React props to be set to an `ExamplePreferenceProps` instance. `ExamplePreferenceInput` expects its React props to be set to an `ExamplePreferenceProps` instance.
This is how `ExampleRendererExtension` handles the state of the preference input. This is how `ExampleRendererExtension` handles the state of the preference input.
@ -386,22 +394,19 @@ import { observer } from "mobx-react";
import React from "react"; import React from "react";
const { const {
Component: { Component: { Checkbox },
Checkbox,
},
} = Renderer; } = Renderer;
export class ExamplePreferenceProps { export class ExamplePreferenceProps {
preference: { preference: {
enabled: boolean; enabled: boolean;
} };
} }
@observer @observer
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> { export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
public constructor() { public constructor() {
super({preference: { enabled: false}}); super({ preference: { enabled: false } });
makeObservable(this); makeObservable(this);
} }
@ -411,7 +416,9 @@ export class ExamplePreferenceInput extends React.Component<ExamplePreferencePro
<Checkbox <Checkbox
label="I understand appPreferences" label="I understand appPreferences"
value={preference.enabled} value={preference.enabled}
onChange={v => { preference.enabled = v; }} onChange={(v) => {
preference.enabled = v;
}}
/> />
); );
} }
@ -419,18 +426,16 @@ export class ExamplePreferenceInput extends React.Component<ExamplePreferencePro
export class ExamplePreferenceHint extends React.Component { export class ExamplePreferenceHint extends React.Component {
render() { render() {
return ( return <span>This is an example of an appPreference for extensions.</span>;
<span>This is an example of an appPreference for extensions.</span>
);
} }
} }
``` ```
`ExamplePreferenceInput` implements a simple checkbox using Lens's `Renderer.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. - `label` sets the text that displays next to the checkbox.
* `value` is initially set to `preference.enabled`. - `value` is initially set to `preference.enabled`.
* `onChange` is a function that responds when the state of the checkbox changes. - `onChange` is a function that responds when the state of the checkbox changes.
`ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props. `ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props.
This is an object with the single `enabled` property. This is an object with the single `enabled` property.
@ -461,18 +466,18 @@ 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): It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
```typescript ```typescript
import { Renderer } from '@k8slens/extensions'; import { Renderer } from "@k8slens/extensions";
import { HelpIcon, HelpPage } from "./page" import { HelpIcon, HelpPage } from "./page";
import React from 'react'; import React from "react";
export default class HelpExtension extends Renderer.LensExtension { export default class HelpExtension extends Renderer.LensExtension {
globalPages = [ globalPages = [
{ {
id: "help", id: "help",
components: { components: {
Page: () => <HelpPage extension={this}/>, Page: () => <HelpPage extension={this} />,
} },
} },
]; ];
statusBarItems = [ statusBarItems = [
@ -486,7 +491,7 @@ export default class HelpExtension extends Renderer.LensExtension {
<HelpIcon /> <HelpIcon />
My Status Bar Item My Status Bar Item
</div> </div>
) ),
}, },
}, },
]; ];
@ -495,14 +500,14 @@ export default class HelpExtension extends Renderer.LensExtension {
The properties of the `statusBarItems` array objects are defined as follows: The properties of the `statusBarItems` array objects are defined as follows:
* `Item` specifies the `React.Component` that will be shown on the status bar. - `Item` specifies the `React.Component` that will be shown on the status bar.
By default, items are added starting from the right side of the status bar. By default, items are added starting from the right side of the status bar.
Due to limited space in the status bar, `Item` will typically specify only an icon or a short string of text. Due to limited space in the status bar, `Item` will typically specify only an icon or a short string of text.
The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus). The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus).
* `onClick` determines what the `statusBarItem` does when it is clicked. - `onClick` determines what the `statusBarItem` does when it is clicked.
In the example, `onClick` is set to a function that calls the `LensRendererExtension` `navigate()` method. In the example, `onClick` is set to a function that calls the `LensRendererExtension` `navigate()` method.
`navigate` takes the `id` of the associated global page as a parameter. `navigate` takes the `id` of the associated global page as a parameter.
Thus, clicking the status bar item activates the associated global pages. Thus, clicking the status bar item activates the associated global pages.
### `kubeObjectMenuItems` ### `kubeObjectMenuItems`
@ -518,9 +523,9 @@ They also appear on the title bar of the details page for specific resources:
The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action: The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action:
```typescript ```typescript
import React from "react" import React from "react";
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import { NamespaceMenuItem } from "./src/namespace-menu-item" import { NamespaceMenuItem } from "./src/namespace-menu-item";
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps; type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
type Namespace = Renderer.K8sApi.Namespace; type Namespace = Renderer.K8sApi.Namespace;
@ -531,23 +536,24 @@ export default class ExampleExtension extends Renderer.LensExtension {
kind: "Namespace", kind: "Namespace",
apiVersions: ["v1"], apiVersions: ["v1"],
components: { components: {
MenuItem: (props: KubeObjectMenuProps<Namespace>) => <NamespaceMenuItem {...props} /> MenuItem: (props: KubeObjectMenuProps<Namespace>) => (
} <NamespaceMenuItem {...props} />
} ),
},
},
]; ];
} }
``` ```
`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface. `kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface.
The example above adds a menu item for namespaces in the cluster dashboard. The example above adds a menu item for namespaces in the cluster dashboard.
The properties of the `kubeObjectMenuItems` array objects are defined as follows: The properties of the `kubeObjectMenuItems` array objects are defined as follows:
* `kind` specifies the Kubernetes resource type the menu item will apply to. - `kind` specifies the Kubernetes resource type the menu item will apply to.
* `apiVersion` specifies the Kubernetes API version number to use with the resource type. - `apiVersion` specifies the Kubernetes API version number to use with the resource type.
* `components` defines the menu item's appearance and behavior. - `components` defines the menu item's appearance and behavior.
* `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties. - `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties.
In this example a `NamespaceMenuItem` object is returned. In this example a `NamespaceMenuItem` object is returned.
`NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`: `NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`:
@ -556,11 +562,7 @@ import React from "react";
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
const { const {
Component: { Component: { terminalStore, MenuItem, Icon },
terminalStore,
MenuItem,
Icon,
},
Navigation, Navigation,
} = Renderer; } = Renderer;
@ -587,12 +589,15 @@ export function NamespaceMenuItem(props: KubeObjectMenuProps<Namespace>) {
return ( return (
<MenuItem onClick={getPods}> <MenuItem onClick={getPods}>
<Icon material="speaker_group" interactive={toolbar} title="Get pods in terminal"/> <Icon
material="speaker_group"
interactive={toolbar}
title="Get pods in terminal"
/>
<span className="title">Get Pods</span> <span className="title">Get Pods</span>
</MenuItem> </MenuItem>
); );
} }
``` ```
`NamespaceMenuItem` returns a `Renderer.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.
@ -629,9 +634,11 @@ export default class ExampleExtension extends Renderer.LensExtension {
apiVersions: ["v1"], apiVersions: ["v1"],
priority: 10, priority: 10,
components: { components: {
Details: (props: KubeObjectDetailsProps<Namespace>) => <NamespaceDetailsItem {...props} /> Details: (props: KubeObjectDetailsProps<Namespace>) => (
} <NamespaceDetailsItem {...props} />
} ),
},
},
]; ];
} }
``` ```
@ -640,15 +647,15 @@ export default class ExampleExtension extends Renderer.LensExtension {
This example above adds a detail item for namespaces in the cluster dashboard. This example above adds a detail item for namespaces in the cluster dashboard.
The properties of the `kubeObjectDetailItems` array objects are defined as follows: The properties of the `kubeObjectDetailItems` array objects are defined as follows:
* `kind` specifies the Kubernetes resource type the detail item will apply to. - `kind` specifies the Kubernetes resource type the detail item will apply to.
* `apiVersion` specifies the Kubernetes API version number to use with the resource type. - `apiVersion` specifies the Kubernetes API version number to use with the resource type.
* `components` defines the detail item's appearance and behavior. - `components` defines the detail item's appearance and behavior.
* `Details` provides a function that returns a `React.Component` given a set of detail item properties. - `Details` provides a function that returns a `React.Component` given a set of detail item properties.
In this example a `NamespaceDetailsItem` object is returned. In this example a `NamespaceDetailsItem` object is returned.
`NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`: `NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`:
``` typescript ```typescript
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
import { PodsDetailsList } from "./pods-details-list"; import { PodsDetailsList } from "./pods-details-list";
import React from "react"; import React from "react";
@ -656,12 +663,8 @@ import { observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
const { const {
K8sApi: { K8sApi: { podsApi },
podsApi, Component: { DrawerTitle },
},
Component: {
DrawerTitle,
},
} = Renderer; } = Renderer;
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps; type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
@ -669,7 +672,9 @@ type Namespace = Renderer.K8sApi.Namespace;
type Pod = Renderer.K8sApi.Pod; type Pod = Renderer.K8sApi.Pod;
@observer @observer
export class NamespaceDetailsItem extends React.Component<KubeObjectDetailsProps<Namespace>> { export class NamespaceDetailsItem extends React.Component<
KubeObjectDetailsProps<Namespace>
> {
@observable private pods: Pod[]; @observable private pods: Pod[];
async componentDidMount() { async componentDidMount() {
@ -681,10 +686,10 @@ export class NamespaceDetailsItem extends React.Component<KubeObjectDetailsProps
render() { render() {
return ( return (
<div> <div>
<DrawerTitle title="Pods" /> <DrawerTitle>Pods</DrawerTitle>
<PodsDetailsList pods={this.pods}/> <PodsDetailsList pods={this.pods} />
</div> </div>
) );
} }
} }
``` ```
@ -709,17 +714,12 @@ Details are placed in drawers, and using `Renderer.Component.DrawerTitle` provid
Multiple details in a drawer can be placed in `<Renderer.Component.DrawerItem>` elements for further separation, if desired. Multiple details in a drawer can be placed in `<Renderer.Component.DrawerItem>` elements for further separation, if desired.
The rest of this example's details are defined in `PodsDetailsList`, found in `./pods-details-list.tsx`: The rest of this example's details are defined in `PodsDetailsList`, found in `./pods-details-list.tsx`:
``` typescript ```typescript
import React from "react"; import React from "react";
import { Renderer } from "@k8slens/extensions"; import { Renderer } from "@k8slens/extensions";
const { const {
Component: { Component: { TableHead, TableRow, TableCell, Table },
TableHead,
TableRow,
TableCell,
Table,
},
} = Renderer; } = Renderer;
type Pod = Renderer.K8sApi.Pod; type Pod = Renderer.K8sApi.Pod;
@ -736,11 +736,11 @@ export class PodsDetailsList extends React.Component<PodsDetailsListProps> {
<TableCell className="podAge">{pods[index].getAge()}</TableCell> <TableCell className="podAge">{pods[index].getAge()}</TableCell>
<TableCell className="podStatus">{pods[index].getStatus()}</TableCell> <TableCell className="podStatus">{pods[index].getStatus()}</TableCell>
</TableRow> </TableRow>
) );
}; };
render() { render() {
const { pods } = this.props const { pods } = this.props;
if (!pods?.length) { if (!pods?.length) {
return null; return null;
@ -754,7 +754,7 @@ export class PodsDetailsList extends React.Component<PodsDetailsListProps> {
<TableCell className="podAge">Age</TableCell> <TableCell className="podAge">Age</TableCell>
<TableCell className="podStatus">Status</TableCell> <TableCell className="podStatus">Status</TableCell>
</TableHead> </TableHead>
{ pods.map(this.getTableRow) } {pods.map(this.getTableRow)}
</Table> </Table>
</div> </div>
); );

View File

@ -134,7 +134,7 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
})} })}
</DrawerItem> </DrawerItem>
<DrawerTitle title="Metrics"/> <DrawerTitle>Metrics</DrawerTitle>
<div className="metrics"> <div className="metrics">
{this.renderMetrics()} {this.renderMetrics()}
</div> </div>

View File

@ -85,7 +85,7 @@ export class ConfigMapDetails extends React.Component<ConfigMapDetailsProps> {
{ {
data.length > 0 && ( data.length > 0 && (
<> <>
<DrawerTitle title="Data"/> <DrawerTitle>Data</DrawerTitle>
{ {
data.map(([name, value]) => ( data.map(([name, value]) => (
<div key={name} className="data"> <div key={name} className="data">

View File

@ -82,7 +82,7 @@ export class ResourceQuotaDetails extends React.Component<ResourceQuotaDetailsPr
{quota.getScopeSelector().length > 0 && ( {quota.getScopeSelector().length > 0 && (
<> <>
<DrawerTitle title="Scope Selector"/> <DrawerTitle>Scope Selector</DrawerTitle>
<Table className="paths"> <Table className="paths">
<TableHead> <TableHead>
<TableCell>Operator</TableCell> <TableCell>Operator</TableCell>

View File

@ -116,7 +116,7 @@ export class SecretDetails extends React.Component<SecretDetailsProps> {
return ( return (
<> <>
<DrawerTitle title="Data" /> <DrawerTitle>Data</DrawerTitle>
{secrets.map(this.renderSecret)} {secrets.map(this.renderSecret)}
<Button <Button
primary primary

View File

@ -92,7 +92,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
}) })
} }
</DrawerItem> </DrawerItem>
<DrawerTitle title="Names"/> <DrawerTitle>Names</DrawerTitle>
<Table selectable className="names box grow"> <Table selectable className="names box grow">
<TableHead> <TableHead>
<TableCell>plural</TableCell> <TableCell>plural</TableCell>
@ -109,7 +109,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
</Table> </Table>
{printerColumns.length > 0 && {printerColumns.length > 0 &&
<> <>
<DrawerTitle title="Additional Printer Columns"/> <DrawerTitle>Additional Printer Columns</DrawerTitle>
<Table selectable className="printer-columns box grow"> <Table selectable className="printer-columns box grow">
<TableHead> <TableHead>
<TableCell className="name">Name</TableCell> <TableCell className="name">Name</TableCell>
@ -136,7 +136,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
} }
{validation && {validation &&
<> <>
<DrawerTitle title="Validation"/> <DrawerTitle>Validation</DrawerTitle>
<MonacoEditor <MonacoEditor
readOnly readOnly
value={validation} value={validation}

View File

@ -71,7 +71,7 @@ export class EventDetails extends React.Component<EventDetailsProps> {
<span className={kebabCase(type)}>{type}</span> <span className={kebabCase(type)}>{type}</span>
</DrawerItem> </DrawerItem>
<DrawerTitle title="Involved object"/> <DrawerTitle>Involved object</DrawerTitle>
<Table> <Table>
<TableHead> <TableHead>
<TableCell>Name</TableCell> <TableCell>Name</TableCell>

View File

@ -113,7 +113,7 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
return ( return (
<div className="values"> <div className="values">
<DrawerTitle title="Values" /> <DrawerTitle>Values</DrawerTitle>
<div className="flex column gaps"> <div className="flex column gaps">
<Checkbox <Checkbox
label="User-supplied values only" label="User-supplied values only"
@ -238,9 +238,9 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
/> />
</DrawerItem> </DrawerItem>
{this.renderValues()} {this.renderValues()}
<DrawerTitle title="Notes"/> <DrawerTitle>Notes</DrawerTitle>
{this.renderNotes()} {this.renderNotes()}
<DrawerTitle title="Resources"/> <DrawerTitle>Resources</DrawerTitle>
{resources && this.renderResources(resources)} {resources && this.renderResources(resources)}
</div> </div>
); );

View File

@ -35,7 +35,7 @@ export class EndpointDetails extends React.Component<EndpointDetailsProps> {
return ( return (
<div className="EndpointDetails"> <div className="EndpointDetails">
<KubeObjectMeta object={endpoint}/> <KubeObjectMeta object={endpoint}/>
<DrawerTitle title="Subsets"/> <DrawerTitle>Subsets</DrawerTitle>
{ {
endpoint.getEndpointSubsets().map((subset) => ( endpoint.getEndpointSubsets().map((subset) => (
<EndpointSubsetList key={subset.toString()} subset={subset} endpoint={endpoint} /> <EndpointSubsetList key={subset.toString()} subset={subset} endpoint={endpoint} />

View File

@ -160,10 +160,10 @@ export class IngressDetails extends React.Component<IngressDetailsProps> {
{serviceName}:{servicePort} {serviceName}:{servicePort}
</DrawerItem> </DrawerItem>
} }
<DrawerTitle title="Rules"/> <DrawerTitle>Rules</DrawerTitle>
{this.renderPaths(ingress)} {this.renderPaths(ingress)}
<DrawerTitle title="Load-Balancer Ingress Points"/> <DrawerTitle>Load-Balancer Ingress Points</DrawerTitle>
{this.renderIngressPoints(ingressPoints)} {this.renderIngressPoints(ingressPoints)}
</div> </div>
); );

View File

@ -170,7 +170,7 @@ export class NetworkPolicyDetails extends React.Component<NetworkPolicyDetailsPr
{ingress && ( {ingress && (
<> <>
<DrawerTitle title="Ingress"/> <DrawerTitle>Ingress</DrawerTitle>
{ingress.map((ingress, i) => ( {ingress.map((ingress, i) => (
<div key={i} data-testid={`ingress-${i}`}> <div key={i} data-testid={`ingress-${i}`}>
{this.renderNetworkPolicyPorts(ingress.ports)} {this.renderNetworkPolicyPorts(ingress.ports)}
@ -182,7 +182,7 @@ export class NetworkPolicyDetails extends React.Component<NetworkPolicyDetailsPr
{egress && ( {egress && (
<> <>
<DrawerTitle title="Egress"/> <DrawerTitle>Egress</DrawerTitle>
{egress.map((egress, i) => ( {egress.map((egress, i) => (
<div key={i} data-testid={`egress-${i}`}> <div key={i} data-testid={`egress-${i}`}>
{this.renderNetworkPolicyPorts(egress.ports)} {this.renderNetworkPolicyPorts(egress.ports)}

View File

@ -86,7 +86,7 @@ class NonInjectedServiceDetails extends React.Component<ServiceDetailsProps & De
{spec.sessionAffinity} {spec.sessionAffinity}
</DrawerItem> </DrawerItem>
<DrawerTitle title="Connection"/> <DrawerTitle>Connection</DrawerTitle>
<DrawerItem name="Cluster IP"> <DrawerItem name="Cluster IP">
{spec.clusterIP} {spec.clusterIP}
@ -129,7 +129,7 @@ class NonInjectedServiceDetails extends React.Component<ServiceDetailsProps & De
{spec.loadBalancerIP} {spec.loadBalancerIP}
</DrawerItem> </DrawerItem>
)} )}
<DrawerTitle title="Endpoint"/> <DrawerTitle>Endpoint</DrawerTitle>
<ServiceDetailsEndpoint endpoint={endpoint}/> <ServiceDetailsEndpoint endpoint={endpoint}/>
</div> </div>

View File

@ -169,9 +169,9 @@ class NonInjectedNodeDetails extends React.Component<NodeDetailsProps & Dependen
} }
</DrawerItem> </DrawerItem>
} }
<DrawerTitle title="Capacity"/> <DrawerTitle>Capacity</DrawerTitle>
<NodeDetailsResources node={node} type={"capacity"}/> <NodeDetailsResources node={node} type={"capacity"}/>
<DrawerTitle title="Allocatable"/> <DrawerTitle>Allocatable</DrawerTitle>
<NodeDetailsResources node={node} type={"allocatable"}/> <NodeDetailsResources node={node} type={"allocatable"}/>
<PodDetailsList <PodDetailsList
pods={childPods} pods={childPods}

View File

@ -161,7 +161,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
{allowedHostPaths && ( {allowedHostPaths && (
<> <>
<DrawerTitle title="Allowed Host Paths"/> <DrawerTitle>Allowed Host Paths</DrawerTitle>
<Table> <Table>
<TableHead> <TableHead>
<TableCell>Path Prefix</TableCell> <TableCell>Path Prefix</TableCell>
@ -186,7 +186,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
{runtimeClass && ( {runtimeClass && (
<> <>
<DrawerTitle title="Runtime Class"/> <DrawerTitle>Runtime Class</DrawerTitle>
<DrawerItem name="Allowed Runtime Class Names"> <DrawerItem name="Allowed Runtime Class Names">
{runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"} {runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"}
</DrawerItem> </DrawerItem>
@ -198,7 +198,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
{seLinux && ( {seLinux && (
<> <>
<DrawerTitle title="Se Linux"/> <DrawerTitle>Se Linux</DrawerTitle>
<DrawerItem name="Rule"> <DrawerItem name="Rule">
{seLinux.rule} {seLinux.rule}
</DrawerItem> </DrawerItem>

View File

@ -80,7 +80,7 @@ class NonInjectedStorageClassDetails extends React.Component<StorageClassDetails
)} )}
{parameters && ( {parameters && (
<> <>
<DrawerTitle title="Parameters"/> <DrawerTitle>Parameters</DrawerTitle>
{ {
Object.entries(parameters).map(([name, value]) => ( Object.entries(parameters).map(([name, value]) => (
<DrawerItem key={name + value} name={startCase(name)}> <DrawerItem key={name + value} name={startCase(name)}>

View File

@ -102,7 +102,7 @@ export class PersistentVolumeClaimDetails extends React.Component<PersistentVolu
{volumeClaim.getStatus()} {volumeClaim.getStatus()}
</DrawerItem> </DrawerItem>
<DrawerTitle title="Selector"/> <DrawerTitle>Selector</DrawerTitle>
<DrawerItem name="Match Labels" labelsOnly> <DrawerItem name="Match Labels" labelsOnly>
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)} {volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)}

View File

@ -67,7 +67,7 @@ export class VolumeDetailsList extends React.Component<VolumeDetailsListProps> {
return ( return (
<div className="VolumeDetailsList flex column"> <div className="VolumeDetailsList flex column">
<DrawerTitle title="Persistent Volumes"/> <DrawerTitle>Persistent Volumes</DrawerTitle>
<Table <Table
tableId="storage_volume_details_list" tableId="storage_volume_details_list"
items={persistentVolumes} items={persistentVolumes}

View File

@ -65,7 +65,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
{nfs && ( {nfs && (
<> <>
<DrawerTitle title="Network File System"/> <DrawerTitle>Network File System</DrawerTitle>
{ {
Object.entries(nfs).map(([name, value]) => ( Object.entries(nfs).map(([name, value]) => (
<DrawerItem key={name} name={startCase(name)}> <DrawerItem key={name} name={startCase(name)}>
@ -78,7 +78,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
{flexVolume && ( {flexVolume && (
<> <>
<DrawerTitle title="FlexVolume"/> <DrawerTitle>FlexVolume</DrawerTitle>
<DrawerItem name="Driver"> <DrawerItem name="Driver">
{flexVolume.driver} {flexVolume.driver}
</DrawerItem> </DrawerItem>
@ -94,7 +94,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
{claimRef && ( {claimRef && (
<> <>
<DrawerTitle title="Claim"/> <DrawerTitle>Claim</DrawerTitle>
<DrawerItem name="Type"> <DrawerItem name="Type">
{claimRef.kind} {claimRef.kind}
</DrawerItem> </DrawerItem>

View File

@ -68,7 +68,7 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
<div className="ClusterRoleBindingDetails"> <div className="ClusterRoleBindingDetails">
<KubeObjectMeta object={clusterRoleBinding} /> <KubeObjectMeta object={clusterRoleBinding} />
<DrawerTitle title="Reference" /> <DrawerTitle>Reference</DrawerTitle>
<Table> <Table>
<TableHead> <TableHead>
<TableCell>Kind</TableCell> <TableCell>Kind</TableCell>
@ -82,7 +82,7 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
</TableRow> </TableRow>
</Table> </Table>
<DrawerTitle title="Bindings" /> <DrawerTitle>Bindings</DrawerTitle>
{subjects.length > 0 && ( {subjects.length > 0 && (
<Table selectable className="bindings box grow"> <Table selectable className="bindings box grow">
<TableHead> <TableHead>

View File

@ -28,7 +28,7 @@ export class ClusterRoleDetails extends React.Component<ClusterRoleDetailsProps>
<div className="ClusterRoleDetails"> <div className="ClusterRoleDetails">
<KubeObjectMeta object={clusterRole}/> <KubeObjectMeta object={clusterRole}/>
<DrawerTitle title="Rules"/> <DrawerTitle>Rules</DrawerTitle>
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => { {rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
return ( return (
<div className="rule" key={index}> <div className="rule" key={index}>

View File

@ -64,7 +64,7 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
<div className="RoleBindingDetails"> <div className="RoleBindingDetails">
<KubeObjectMeta object={roleBinding} /> <KubeObjectMeta object={roleBinding} />
<DrawerTitle title="Reference" /> <DrawerTitle>Reference</DrawerTitle>
<Table> <Table>
<TableHead> <TableHead>
<TableCell>Kind</TableCell> <TableCell>Kind</TableCell>
@ -78,7 +78,7 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
</TableRow> </TableRow>
</Table> </Table>
<DrawerTitle title="Bindings" /> <DrawerTitle>Bindings</DrawerTitle>
{subjects.length > 0 && ( {subjects.length > 0 && (
<Table selectable className="bindings box grow"> <Table selectable className="bindings box grow">
<TableHead> <TableHead>

View File

@ -27,7 +27,7 @@ export class RoleDetails extends React.Component<RoleDetailsProps> {
return ( return (
<div className="RoleDetails"> <div className="RoleDetails">
<KubeObjectMeta object={role}/> <KubeObjectMeta object={role}/>
<DrawerTitle title="Rules"/> <DrawerTitle>Rules</DrawerTitle>
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => { {rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
return ( return (
<div className="rule" key={index}> <div className="rule" key={index}>

View File

@ -143,7 +143,7 @@ export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetai
</DrawerItem> </DrawerItem>
} }
<DrawerTitle title="Mountable secrets"/> <DrawerTitle>Mountable secrets</DrawerTitle>
<div className="secrets"> <div className="secrets">
{this.renderSecrets()} {this.renderSecrets()}
</div> </div>

View File

@ -78,7 +78,7 @@ class NonInjectedCronJobDetails extends React.Component<CronJobDetailsProps & De
</DrawerItem> </DrawerItem>
{childJobs.length > 0 && {childJobs.length > 0 &&
<> <>
<DrawerTitle title="Jobs"/> <DrawerTitle>Jobs</DrawerTitle>
{childJobs.map((job: Job) => { {childJobs.map((job: Job) => {
const selectors = job.getSelectors(); const selectors = job.getSelectors();
const condition = job.getCondition(); const condition = job.getCondition();

View File

@ -53,7 +53,7 @@ export class DeploymentReplicaSets extends React.Component<DeploymentReplicaSets
return ( return (
<div className="ReplicaSets flex column"> <div className="ReplicaSets flex column">
<DrawerTitle title="Deploy Revisions"/> <DrawerTitle>Deploy Revisions</DrawerTitle>
<Table <Table
selectable selectable
tableId="deployment_replica_sets_view" tableId="deployment_replica_sets_view"

View File

@ -131,7 +131,7 @@ export class PodDetailsList extends React.Component<PodDetailsListProps> {
return ( return (
<div className="PodDetailsList flex column"> <div className="PodDetailsList flex column">
<DrawerTitle title="Pods" /> <DrawerTitle>Pods</DrawerTitle>
<Table <Table
tableId="workloads_pod_details_list" tableId="workloads_pod_details_list"
items={pods} items={pods}

View File

@ -10,7 +10,7 @@ import { Link } from "react-router-dom";
import { configMapApi, Pod, PodVolume, PodVolumeKind, PodVolumeVariants, pvcApi, SecretReference, secretsApi } from "../../../common/k8s-api/endpoints"; import { configMapApi, Pod, PodVolume, PodVolumeKind, PodVolumeVariants, pvcApi, SecretReference, secretsApi } from "../../../common/k8s-api/endpoints";
import type { KubeApi } from "../../../common/k8s-api/kube-api"; import type { KubeApi } from "../../../common/k8s-api/kube-api";
import type { KubeObject, LocalObjectReference } from "../../../common/k8s-api/kube-object"; import type { KubeObject, LocalObjectReference } from "../../../common/k8s-api/kube-object";
import { DrawerItem, DrawerItemLabels, DrawerSection, DrawerSubSection } from "../drawer"; import { DrawerItem, DrawerItemLabels, DrawerTitle } from "../drawer";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { getDetailsUrl } from "../kube-detail-params"; import { getDetailsUrl } from "../kube-detail-params";
@ -341,7 +341,8 @@ const volumeRenderers: PodVolumeVariantRenderers = {
sources.map(({ secret, downwardAPI, configMap, serviceAccountToken }, index) => ( sources.map(({ secret, downwardAPI, configMap, serviceAccountToken }, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
{secret && ( {secret && (
<DrawerSubSection title="Secret"> <>
<DrawerTitle size="sub-title">Secret</DrawerTitle>
<DrawerItem name="Name"> <DrawerItem name="Name">
{secret.name} {secret.name}
</DrawerItem> </DrawerItem>
@ -352,10 +353,11 @@ const volumeRenderers: PodVolumeVariantRenderers = {
</ul> </ul>
</DrawerItem> </DrawerItem>
)} )}
</DrawerSubSection> </>
)} )}
{downwardAPI && ( {downwardAPI && (
<DrawerSubSection title="Downward API"> <>
<DrawerTitle size="sub-title">Downward API</DrawerTitle>
{downwardAPI.items && ( {downwardAPI.items && (
<DrawerItem name="Items"> <DrawerItem name="Items">
<ul> <ul>
@ -363,10 +365,11 @@ const volumeRenderers: PodVolumeVariantRenderers = {
</ul> </ul>
</DrawerItem> </DrawerItem>
)} )}
</DrawerSubSection> </>
)} )}
{configMap && ( {configMap && (
<DrawerSubSection title="Config Map"> <>
<DrawerTitle size="sub-title">Config Map</DrawerTitle>
<DrawerItem name="Name"> <DrawerItem name="Name">
{configMap.name} {configMap.name}
</DrawerItem> </DrawerItem>
@ -377,10 +380,11 @@ const volumeRenderers: PodVolumeVariantRenderers = {
</ul> </ul>
</DrawerItem> </DrawerItem>
)} )}
</DrawerSubSection> </>
)} )}
{serviceAccountToken && ( {serviceAccountToken && (
<DrawerSubSection title="Service Account Token"> <>
<DrawerTitle size="sub-title">Service Account Token</DrawerTitle>
<DrawerItem name="Audience" hidden={!serviceAccountToken.audience}> <DrawerItem name="Audience" hidden={!serviceAccountToken.audience}>
{serviceAccountToken.audience} {serviceAccountToken.audience}
</DrawerItem> </DrawerItem>
@ -390,7 +394,7 @@ const volumeRenderers: PodVolumeVariantRenderers = {
<DrawerItem name="Path"> <DrawerItem name="Path">
{serviceAccountToken.path} {serviceAccountToken.path}
</DrawerItem> </DrawerItem>
</DrawerSubSection> </>
)} )}
</React.Fragment> </React.Fragment>
)) ))
@ -639,9 +643,14 @@ export const PodVolumes = observer(({ pod }: PodVolumesProps) => {
const volumes = pod.getVolumes(); const volumes = pod.getVolumes();
if (volumes.length === 0) {
return null;
}
return ( return (
<DrawerSection title="Volumes" hidden={volumes.length === 0}> <>
<DrawerTitle>Volumes</DrawerTitle>
{volumes.map(renderVolume)} {volumes.map(renderVolume)}
</DrawerSection> </>
); );
}); });

View File

@ -1,47 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { DrawerTitle, DrawerSubTitle } from "./drawer-title";
export interface DrawerSectionProps {
className?: string;
title?: React.ReactNode;
hidden?: boolean;
}
export class DrawerSection extends React.Component<DrawerSectionProps> {
render() {
const { title, children, className, hidden } = this.props;
if (hidden) {
return null;
}
return (
<>
<DrawerTitle className={className} title={title} />
{children}
</>
);
}
}
export class DrawerSubSection extends React.Component<DrawerSectionProps> {
render() {
const { title, children, className, hidden } = this.props;
if (hidden) {
return null;
}
return (
<>
<DrawerSubTitle className={className} title={title} />
{children}
</>
);
}
}

View File

@ -4,12 +4,14 @@
*/ */
.DrawerTitle { .DrawerTitle {
padding: $padding * 1.5 $padding * 3;
margin: $margin * 3 (-$margin * 3); margin: $margin * 3 (-$margin * 3);
background: var(--drawerSubtitleBackground); background: var(--drawerSubtitleBackground);
} }
.DrawerSubTitle { .DrawerTitle.title {
padding: $padding $padding * 2; padding: $padding * 1.5 $padding * 3;
background: var(--drawerSubtitleBackground); }
.DrawerTitle.sub-title {
padding: $padding $padding * 2;
} }

View File

@ -3,34 +3,29 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import "./drawer-title.scss"; import styles from "./drawer-title.module.css";
import React from "react"; import React from "react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
export interface DrawerTitleProps { export interface DrawerTitleProps {
className?: string; className?: string;
title?: React.ReactNode; children: React.ReactNode;
/**
* Specifies how large this title is
*
* @default "title"
*/
size?: "sub-title" | "title";
} }
export class DrawerTitle extends React.Component<DrawerTitleProps> { export function DrawerTitle({ className, children, size = "title" }: DrawerTitleProps) {
render() { return (
const { title, children, className } = this.props; <div className={cssNames(styles.DrawerTitle, className, {
[styles.title]: size === "title",
return ( [styles["sub-title"]]: size === "sub-title",
<div className={cssNames("DrawerTitle", className)}> })}>
{title || children} {children}
</div> </div>
); );
}
}
export class DrawerSubTitle extends React.Component<DrawerTitleProps> {
render() {
const { title, children, className } = this.props;
return (
<div className={cssNames("DrawerSubTitle", className)}>
{title || children}
</div>
);
}
} }

View File

@ -17,7 +17,7 @@ export function initCatalogEntityDetailRegistry() {
components: { components: {
Details: ({ entity }: CatalogEntityDetailsProps<KubernetesCluster>) => ( Details: ({ entity }: CatalogEntityDetailsProps<KubernetesCluster>) => (
<> <>
<DrawerTitle title="Kubernetes Information" /> <DrawerTitle>Kubernetes Information</DrawerTitle>
<div className="box grow EntityMetadata"> <div className="box grow EntityMetadata">
<DrawerItem name="Distribution"> <DrawerItem name="Distribution">
{entity.metadata.distro || "unknown"} {entity.metadata.distro || "unknown"}
@ -36,7 +36,7 @@ export function initCatalogEntityDetailRegistry() {
components: { components: {
Details: ({ entity }: CatalogEntityDetailsProps<WebLink>) => ( Details: ({ entity }: CatalogEntityDetailsProps<WebLink>) => (
<> <>
<DrawerTitle title="More Information" /> <DrawerTitle>More Information</DrawerTitle>
<DrawerItem name="URL"> <DrawerItem name="URL">
{entity.spec.url} {entity.spec.url}
</DrawerItem> </DrawerItem>