diff --git a/docs/extensions/guides/renderer-extension.md b/docs/extensions/guides/renderer-extension.md index e9439fb0a9..5d41dff89d 100644 --- a/docs/extensions/guides/renderer-extension.md +++ b/docs/extensions/guides/renderer-extension.md @@ -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: -* [Cluster pages](#clusterpages) -* [Cluster page menus](#clusterpagemenus) -* [Global pages](#globalpages) -* [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) +- [Cluster pages](#clusterpages) +- [Cluster page menus](#clusterpagemenus) +- [Global pages](#globalpages) +- [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) +- [Entity settings](#entitysettings) +- [Catalog entity detail items](#catalogentitydetailitems) All UI elements are based on React components. Finally, you can also add commands and protocol handlers: -* [Command palette commands](#commandpalettecommands) -* [protocol handlers](protocol-handlers.md) +- [Command palette commands](#commandpalettecommands) +- [protocol handlers](protocol-handlers.md) ## `Renderer.LensExtension` Class @@ -41,11 +41,11 @@ import { Renderer } from "@k8slens/extensions"; export default class ExampleExtensionMain extends Renderer.LensExtension { onActivate() { - console.log('custom renderer process extension code started'); + console.log("custom renderer process extension code started"); } 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. !!! 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. (On Mac, it is **Lens** > **Extensions**.) @@ -75,17 +75,17 @@ Add a cluster page definition to a `Renderer.LensExtension` subclass with the fo ```typescript import { Renderer } from "@k8slens/extensions"; -import { ExampleIcon, ExamplePage } from "./page" -import React from "react" +import { ExampleIcon, ExamplePage } from "./page"; +import React from "react"; export default class ExampleExtension extends Renderer.LensExtension { clusterPages = [ { id: "hello", components: { - Page: () => , - } - } + Page: () => , + }, + }, ]; } ``` @@ -93,24 +93,26 @@ export default class ExampleExtension extends Renderer.LensExtension { `clusterPages` is an array of objects that satisfy the `PageRegistration` interface. The properties of the `clusterPages` array objects are defined as follows: -* `id` is a string that identifies the page. -* `components` matches the `PageComponents` interface for which there is one field, `Page`. -* `Page` is of type ` React.ComponentType`. -It offers flexibility in defining the appearance and behavior of your page. +- `id` is a string that identifies the page. +- `components` matches the `PageComponents` interface for which there is one field, `Page`. +- `Page` is of type ` React.ComponentType`. + 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 { 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() { return (

Hello world!

- ) + ); } } ``` @@ -130,17 +132,17 @@ By expanding on the above example, you can add a cluster page menu item to the ` ```typescript import { Renderer } from "@k8slens/extensions"; -import { ExampleIcon, ExamplePage } from "./page" -import React from "react" +import { ExampleIcon, ExamplePage } from "./page"; +import React from "react"; export default class ExampleExtension extends Renderer.LensExtension { clusterPages = [ { id: "hello", components: { - Page: () => , - } - } + Page: () => , + }, + }, ]; clusterPageMenus = [ @@ -149,7 +151,7 @@ export default class ExampleExtension extends Renderer.LensExtension { title: "Hello World", components: { 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. The properties of the `clusterPageMenus` array objects are defined as follows: -* `target` links to the relevant cluster page using `pageId`. -* `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. -* `components` is used to set an icon that appears to the left of the `title` text in the left side menu. +- `target` links to the relevant cluster page using `pageId`. +- `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. +- `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**. 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 import { Renderer } from "@k8slens/extensions"; -import React from "react" +import React from "react"; type IconProps = Renderer.Component.IconProps; @@ -180,16 +182,18 @@ const { } = Renderer; export function ExampleIcon(props: IconProps) { - return + return ; } -export class ExamplePage extends React.Component<{ extension: Renderer.LensExtension }> { +export class ExamplePage extends React.Component<{ + extension: Renderer.LensExtension; +}> { render() { return (

Hello world!

- ) + ); } } ``` @@ -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). 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. +- `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. `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: - ```typescript import { Renderer } from "@k8slens/extensions"; -import { ExampleIcon, ExamplePage } from "./page" -import React from "react" +import { ExampleIcon, ExamplePage } from "./page"; +import React from "react"; export default class ExampleExtension extends Renderer.LensExtension { clusterPages = [ { id: "hello", components: { - Page: () => , - } + Page: () => , + }, }, { id: "bonjour", components: { - Page: () => , - } - } + Page: () => , + }, + }, ]; clusterPageMenus = [ @@ -232,7 +235,7 @@ export default class ExampleExtension extends Renderer.LensExtension { title: "Greetings", components: { Icon: ExampleIcon, - } + }, }, { parentId: "example", @@ -240,7 +243,7 @@ export default class ExampleExtension extends Renderer.LensExtension { title: "Hello World", components: { Icon: ExampleIcon, - } + }, }, { parentId: "example", @@ -248,8 +251,8 @@ export default class ExampleExtension extends Renderer.LensExtension { title: "Bonjour le monde", components: { 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: ```typescript -import { Renderer } from '@k8slens/extensions'; -import { HelpPage } from './page'; -import React from 'react'; +import { Renderer } from "@k8slens/extensions"; +import { HelpPage } from "./page"; +import React from "react"; export default class HelpExtension extends Renderer.LensExtension { globalPages = [ { id: "help", components: { - Page: () => , - } - } + Page: () => , + }, + }, ]; } ``` @@ -299,24 +302,26 @@ export default class HelpExtension extends Renderer.LensExtension { `globalPages` is an array of objects that satisfy the `PageRegistration` interface. The properties of the `globalPages` array objects are defined as follows: -* `id` is a string that identifies the page. -* `components` matches the `PageComponents` interface for which there is one field, `Page`. -* `Page` is of type `React.ComponentType`. -It offers flexibility in defining the appearance and behavior of your page. +- `id` is a string that identifies the page. +- `components` matches the `PageComponents` interface for which there is one field, `Page`. +- `Page` is of type `React.ComponentType`. + 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 { 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() { return (

Help yourself

- ) + ); } } ``` @@ -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. 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 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 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 to the Welcome Page, see [`welcomeMenus`](#welcomemenus). ### `welcomeMenus` + ### `appPreferences` The Lens **Preferences** page is a built-in global page. @@ -342,22 +348,24 @@ The following example demonstrates adding a custom preference: ```typescript import { Renderer } from "@k8slens/extensions"; -import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference"; +import { + ExamplePreferenceHint, + ExamplePreferenceInput, +} from "./src/example-preference"; import { observable } from "mobx"; import React from "react"; export default class ExampleRendererExtension extends Renderer.LensExtension { - @observable preference = { enabled: false }; appPreferences = [ { title: "Example Preferences", components: { - Input: () => , - Hint: () => - } - } + Input: () => , + Hint: () => , + }, + }, ]; } ``` @@ -365,13 +373,13 @@ export default class ExampleRendererExtension extends Renderer.LensExtension { `appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface. The properties of the `appPreferences` array objects are defined as follows: -* `title` sets the heading text displayed on the Preferences page. -* `components` specifies two `React.Component` objects that define the interface for the preference. - * `Input` specifies an interactive input element for the preference. - * `Hint` provides descriptive information for the preference, shown below the `Input` element. +- `title` sets the heading text displayed on the Preferences page. +- `components` specifies two `React.Component` objects that define the interface for the preference. + - `Input` specifies an interactive input element for the preference. + - `Hint` provides descriptive information for the preference, shown below the `Input` element. !!! 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. This is how `ExampleRendererExtension` handles the state of the preference input. @@ -386,22 +394,19 @@ import { observer } from "mobx-react"; import React from "react"; const { - Component: { - Checkbox, - }, + Component: { Checkbox }, } = Renderer; export class ExamplePreferenceProps { preference: { enabled: boolean; - } + }; } @observer export class ExamplePreferenceInput extends React.Component { - public constructor() { - super({preference: { enabled: false}}); + super({ preference: { enabled: false } }); makeObservable(this); } @@ -411,7 +416,9 @@ export class ExamplePreferenceInput extends React.Component { preference.enabled = v; }} + onChange={(v) => { + preference.enabled = v; + }} /> ); } @@ -419,18 +426,16 @@ export class ExamplePreferenceInput extends React.ComponentThis is an example of an appPreference for extensions. - ); + return This is an example of an appPreference for extensions.; } } ``` `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`. -* `onChange` is a function that responds when the state of the checkbox changes. +- `label` sets the text that displays next to the checkbox. +- `value` is initially set to `preference.enabled`. +- `onChange` is a function that responds when the state of the checkbox changes. `ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props. 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): ```typescript -import { Renderer } from '@k8slens/extensions'; -import { HelpIcon, HelpPage } from "./page" -import React from 'react'; +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: () => , - } - } + Page: () => , + }, + }, ]; statusBarItems = [ @@ -486,7 +491,7 @@ export default class HelpExtension extends Renderer.LensExtension { My Status Bar Item - ) + ), }, }, ]; @@ -495,14 +500,14 @@ export default class HelpExtension extends Renderer.LensExtension { The properties of the `statusBarItems` array objects are defined as follows: -* `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. -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). -* `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. -`navigate` takes the `id` of the associated global page as a parameter. -Thus, clicking the status bar item activates the associated global pages. +- `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. + 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). +- `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. + `navigate` takes the `id` of the associated global page as a parameter. + Thus, clicking the status bar item activates the associated global pages. ### `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: ```typescript -import React from "react" +import React from "react"; 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 Namespace = Renderer.K8sApi.Namespace; @@ -531,23 +536,24 @@ export default class ExampleExtension extends Renderer.LensExtension { kind: "Namespace", apiVersions: ["v1"], components: { - MenuItem: (props: KubeObjectMenuProps) => - } - } + MenuItem: (props: KubeObjectMenuProps) => ( + + ), + }, + }, ]; } - ``` `kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface. The example above adds a menu item for namespaces in the cluster dashboard. The properties of the `kubeObjectMenuItems` array objects are defined as follows: -* `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. -* `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. -In this example a `NamespaceMenuItem` object is returned. +- `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. +- `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. + In this example a `NamespaceMenuItem` object is returned. `NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`: @@ -556,11 +562,7 @@ import React from "react"; import { Renderer } from "@k8slens/extensions"; const { - Component: { - terminalStore, - MenuItem, - Icon, - }, + Component: { terminalStore, MenuItem, Icon }, Navigation, } = Renderer; @@ -587,12 +589,15 @@ export function NamespaceMenuItem(props: KubeObjectMenuProps) { return ( - + Get Pods ); } - ``` `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"], priority: 10, components: { - Details: (props: KubeObjectDetailsProps) => - } - } + Details: (props: KubeObjectDetailsProps) => ( + + ), + }, + }, ]; } ``` @@ -640,15 +647,15 @@ export default class ExampleExtension extends Renderer.LensExtension { This example above adds a detail item for namespaces in the cluster dashboard. The properties of the `kubeObjectDetailItems` array objects are defined as follows: -* `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. -* `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. -In this example a `NamespaceDetailsItem` object is returned. +- `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. +- `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. + In this example a `NamespaceDetailsItem` object is returned. `NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`: -``` typescript +```typescript import { Renderer } from "@k8slens/extensions"; import { PodsDetailsList } from "./pods-details-list"; import React from "react"; @@ -656,12 +663,8 @@ import { observable } from "mobx"; import { observer } from "mobx-react"; const { - K8sApi: { - podsApi, - }, - Component: { - DrawerTitle, - }, + K8sApi: { podsApi }, + Component: { DrawerTitle }, } = Renderer; type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps; @@ -669,7 +672,9 @@ type Namespace = Renderer.K8sApi.Namespace; type Pod = Renderer.K8sApi.Pod; @observer -export class NamespaceDetailsItem extends React.Component> { +export class NamespaceDetailsItem extends React.Component< + KubeObjectDetailsProps +> { @observable private pods: Pod[]; async componentDidMount() { @@ -681,10 +686,10 @@ export class NamespaceDetailsItem extends React.Component - - + Pods + - ) + ); } } ``` @@ -709,17 +714,12 @@ Details are placed in drawers, and using `Renderer.Component.DrawerTitle` provid 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 +```typescript import React from "react"; import { Renderer } from "@k8slens/extensions"; const { - Component: { - TableHead, - TableRow, - TableCell, - Table, - }, + Component: { TableHead, TableRow, TableCell, Table }, } = Renderer; type Pod = Renderer.K8sApi.Pod; @@ -736,11 +736,11 @@ export class PodsDetailsList extends React.Component { {pods[index].getAge()} {pods[index].getStatus()} - ) + ); }; render() { - const { pods } = this.props + const { pods } = this.props; if (!pods?.length) { return null; @@ -754,7 +754,7 @@ export class PodsDetailsList extends React.Component { Age Status - { pods.map(this.getTableRow) } + {pods.map(this.getTableRow)} ); diff --git a/package.json b/package.json index 586b5ae55c..0264aa2155 100644 --- a/package.json +++ b/package.json @@ -393,7 +393,7 @@ "ts-jest": "26.5.6", "ts-loader": "^9.2.6", "ts-node": "^10.7.0", - "type-fest": "^1.4.0", + "type-fest": "^2.12.0", "typed-emitter": "^1.4.0", "typedoc": "0.22.10", "typedoc-plugin-markdown": "^3.11.12", diff --git a/src/common/k8s-api/endpoints/metrics.api.ts b/src/common/k8s-api/endpoints/metrics.api.ts index 88efc166d1..ad4044c5d3 100644 --- a/src/common/k8s-api/endpoints/metrics.api.ts +++ b/src/common/k8s-api/endpoints/metrics.api.ts @@ -141,8 +141,11 @@ export function isMetricsEmpty(metrics: Record) { return Object.values(metrics).every(metric => !metric?.data?.result?.length); } -export function getItemMetrics(metrics: Record, itemName: string): Record | void { - if (!metrics) return; +export function getItemMetrics(metrics: Record, itemName: string): Record | undefined { + if (!metrics) { + return undefined; + } + const itemMetrics = { ...metrics }; for (const metric in metrics) { diff --git a/src/common/k8s-api/endpoints/persistent-volume-claims.api.ts b/src/common/k8s-api/endpoints/persistent-volume-claims.api.ts index a7815e774c..cbe920421d 100644 --- a/src/common/k8s-api/endpoints/persistent-volume-claims.api.ts +++ b/src/common/k8s-api/endpoints/persistent-volume-claims.api.ts @@ -31,17 +31,25 @@ export interface IPvcMetrics { diskCapacity: T; } -export interface PersistentVolumeClaim { - spec: { - accessModes: string[]; - storageClassName: string; - selector: LabelSelector; - resources: { - requests: { - storage: string; // 8Gi - }; - }; +export interface PersistentVolumeClaimSpec { + accessModes: string[]; + selector: LabelSelector; + resources: { + requests?: Record; + limits?: Record; }; + volumeName?: string; + storageClassName?: string; + volumeMode?: string; + dataSource?: { + apiGroup: string; + kind: string; + name: string; + }; +} + +export interface PersistentVolumeClaim { + spec: PersistentVolumeClaimSpec; status: { phase: string; // Pending }; diff --git a/src/common/k8s-api/endpoints/pods.api.ts b/src/common/k8s-api/endpoints/pods.api.ts index facc1fa3c3..83d2d2234e 100644 --- a/src/common/k8s-api/endpoints/pods.api.ts +++ b/src/common/k8s-api/endpoints/pods.api.ts @@ -9,6 +9,10 @@ import { IMetrics, metricsApi } from "./metrics.api"; import { KubeApi } from "../kube-api"; import type { KubeJsonApiData } from "../kube-json-api"; import { isClusterPageContext } from "../../utils/cluster-id-url-parsing"; +import type { RequireExactlyOne } from "type-fest"; +import type { KubeObjectMetadata, LocalObjectReference } from "../kube-object"; +import type { SecretReference } from "./secret.api"; +import type { PersistentVolumeClaimSpec } from "./persistent-volume-claims.api"; export class PodsApi extends KubeApi { getLogs = async (params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise => { @@ -112,12 +116,8 @@ export interface IPodContainer extends Partial; + spec: PersistentVolumeClaimSpec; + }; +} + +export interface EmptyDirSource { + medium?: string; + sizeLimit?: string; +} + +export interface FiberChannelSource { + /** + * A list of World Wide Names + */ + targetWWNs: string[]; + /** + * Logical Unit number + */ + lun: number; + /** + * The type of filesystem + * @default "ext4" + */ + fsType?: string; + readOnly: boolean; +} + +export interface FlockerSource { + datasetName: string; +} + +export interface FlexVolumeSource { + driver: string; + fsType?: string; + secretRef?: LocalObjectReference; + /** + * @default false + */ + readOnly?: boolean; + options?: Record; +} + +export interface GcePersistentDiskSource { + pdName: string; + fsType: string; +} + +export interface GitRepoSource { + repository: string; + revision: string; +} + +export interface GlusterFsSource { + /** + * The name of the Endpoints object that represents a Gluster cluster configuration. + */ + endpoints: string; + /** + * The Glusterfs volume name. + */ + path: string; + /** + * The boolean that sets the mountpoint readOnly or readWrite. + */ + readOnly: boolean; +} + +export interface HostPathSource { + path: string; + /** + * Determines the sorts of checks that will be done + * @default "" + */ + type?: "" | "DirectoryOrCreate" | "Directory" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice"; +} + +export interface IScsiSource { + targetPortal: string; + iqn: string; + lun: number; + fsType: string; + readOnly: boolean; + chapAuthDiscovery?: boolean; + chapAuthSession?: boolean; + secretRef?: SecretReference; +} + +export interface LocalSource { + path: string; +} + +export interface NetworkFsSource { + server: string; + path: string; + readOnly?: boolean; +} + +export interface PersistentVolumeClaimSource { + claimName: string; +} + +export interface PhotonPersistentDiskSource { + pdID: string; + /** + * @default "ext4" + */ + fsType?: string; +} + +export interface PortworxVolumeSource { + volumeID: string; + fsType?: string; + readOnly?: boolean; +} + +export interface ProjectedSource { + sources: { + secret?: { + name: string; + items?: { + key: string; + path: string; + mode?: number; + }[]; + }; + downwardAPI?: { + items?: { + path: string; + fieldRef?: { + fieldPath: string; + apiVersion?: string; + }; + resourceFieldRef?: { + resource: string; + containerName?: string; + }; + mode?: number; + }[]; + }; + configMap?: { + name: string; + items?: { + key: string; + path: string; + mode?: number; + }[]; + optional?: boolean; + }; + serviceAccountToken?: { + audience?: string; + expirationSeconds?: number; + path: string; + }; + }[]; + defaultMode: number; +} + +export interface QuobyteSource { + registry: string; + volume: string; + /** + * @default false + */ + readOnly?: boolean; + /** + * @default "serivceaccount" + */ + user?: string; + group?: string; + tenant?: string; +} + +export interface RadosBlockDeviceSource { + monitors: string[]; + image: string; + /** + * @default "ext4" + */ + fsType?: string; + /** + * @default "rbd" + */ + pool?: string; + /** + * @default "admin" + */ + user?: string; + /** + * @default "/etc/ceph/keyring" + */ + keyring?: string; + secretRef?: SecretReference; + /** + * @default false + */ + readOnly?: boolean; +} + +export interface ScaleIoSource { + gateway: string; + system: string; + secretRef?: LocalObjectReference; + /** + * @default false + */ + sslEnabled?: boolean; + protectionDomain?: string; + storagePool?: string; + /** + * @default "ThinProvisioned" + */ + storageMode?: "ThickProvisioned" | "ThinProvisioned"; + volumeName: string; + /** + * @default "xfs" + */ + fsType?: string; + /** + * @default false + */ + readOnly?: boolean; +} + +export interface SecretSource { + secretName: string; + items?: { + key: string; + path: string; + mode?: number; + }[]; + defaultMode?: number; + optional?: boolean; +} + +export interface StorageOsSource { + volumeName: string; + /** + * @default Pod.metadata.namespace + */ + volumeNamespace?: string; + /** + * @default "ext4" + */ + fsType?: string; + /** + * @default false + */ + readOnly?: boolean; + secretRef?: LocalObjectReference; +} + +export interface VsphereVolumeSource { + volumePath: string; + /** + * @default "ext4" + */ + fsType?: string; + storagePolicyName?: string; + storagePolicyID?: string; +} + +export interface ContainerStorageInterfaceSource { + driver: string; + /** + * @default false + */ + readOnly?: boolean; + /** + * @default "ext4" + */ + fsType?: string; + volumeAttributes?: Record; + controllerPublishSecretRef?: SecretReference; + nodeStageSecretRef?: SecretReference; + nodePublishSecretRef?: SecretReference; + controllerExpandSecretRef?: SecretReference; +} + +export interface PodVolumeVariants { + awsElasticBlockStore: AwsElasticBlockStoreSource; + azureDisk: AzureDiskSource; + azureFile: AzureFileSource; + cephfs: CephfsSource; + cinder: CinderSource; + configMap: ConfigMapSource; + csi: ContainerStorageInterfaceSource; + downwardAPI: DownwardApiSource; + emptyDir: EmptyDirSource; + ephemeral: EphemeralSource; + fc: FiberChannelSource; + flexVolume: FlexVolumeSource; + flocker: FlockerSource; + gcePersistentDisk: GcePersistentDiskSource; + gitRepo: GitRepoSource; + glusterfs: GlusterFsSource; + hostPath: HostPathSource; + iscsi: IScsiSource; + local: LocalSource; + nfs: NetworkFsSource; + persistentVolumeClaim: PersistentVolumeClaimSource; + photonPersistentDisk: PhotonPersistentDiskSource; + portworxVolume: PortworxVolumeSource; + projected: ProjectedSource; + quobyte: QuobyteSource; + rbd: RadosBlockDeviceSource; + scaleIO: ScaleIoSource; + secret: SecretSource; + storageos: StorageOsSource; + vsphereVolume: VsphereVolumeSource; +} + +/** + * The valid kinds of volume + */ +export type PodVolumeKind = keyof PodVolumeVariants; + +export type PodVolume = RequireExactlyOne & { + name: string; +}; + export class Pod extends WorkloadKubeObject { static kind = "Pod"; static namespaced = true; @@ -206,23 +644,7 @@ export class Pod extends WorkloadKubeObject { } declare spec?: { - volumes?: { - name: string; - persistentVolumeClaim: { - claimName: string; - }; - emptyDir: { - medium?: string; - sizeLimit?: string; - }; - configMap: { - name: string; - }; - secret: { - secretName: string; - defaultMode: number; - }; - }[]; + volumes?: PodVolume[]; initContainers: IPodContainer[]; containers: IPodContainer[]; restartPolicy?: string; @@ -239,9 +661,7 @@ export class Pod extends WorkloadKubeObject { [selector: string]: string; }; securityContext?: {}; - imagePullSecrets?: { - name: string; - }[]; + imagePullSecrets?: LocalObjectReference[]; hostNetwork?: boolean; hostPID?: boolean; hostIPC?: boolean; diff --git a/src/common/k8s-api/endpoints/secret.api.ts b/src/common/k8s-api/endpoints/secret.api.ts index a3b821fcf8..93953ebf48 100644 --- a/src/common/k8s-api/endpoints/secret.api.ts +++ b/src/common/k8s-api/endpoints/secret.api.ts @@ -25,6 +25,11 @@ export interface ISecretRef { name: string; } +export interface SecretReference { + name: string; + namespace?: string; +} + export interface SecretData extends KubeJsonApiData { type: SecretType; data?: Record; diff --git a/src/common/k8s-api/kube-object.ts b/src/common/k8s-api/kube-object.ts index afabeaf638..8e1b6daee2 100644 --- a/src/common/k8s-api/kube-object.ts +++ b/src/common/k8s-api/kube-object.ts @@ -21,6 +21,13 @@ export type KubeObjectConstructor = (new (data: KubeJsonAp apiBase?: string; }; +/** + * A reference to an object in the same namespace + */ +export interface LocalObjectReference { + name: string; +} + export interface KubeObjectMetadata { uid: string; name: string; diff --git a/src/renderer/components/+config-autoscalers/hpa-details.tsx b/src/renderer/components/+config-autoscalers/hpa-details.tsx index 476ef07ade..45c35eacd3 100644 --- a/src/renderer/components/+config-autoscalers/hpa-details.tsx +++ b/src/renderer/components/+config-autoscalers/hpa-details.tsx @@ -134,7 +134,7 @@ export class HpaDetails extends React.Component { })} - + Metrics
{this.renderMetrics()}
diff --git a/src/renderer/components/+config-maps/config-map-details.tsx b/src/renderer/components/+config-maps/config-map-details.tsx index 074003a5bf..61d2ea4ae1 100644 --- a/src/renderer/components/+config-maps/config-map-details.tsx +++ b/src/renderer/components/+config-maps/config-map-details.tsx @@ -85,7 +85,7 @@ export class ConfigMapDetails extends React.Component { { data.length > 0 && ( <> - + Data { data.map(([name, value]) => (
diff --git a/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx b/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx index 363ba75662..3a1cff2080 100644 --- a/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx +++ b/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx @@ -82,7 +82,7 @@ export class ResourceQuotaDetails extends React.Component 0 && ( <> - + Scope Selector Operator diff --git a/src/renderer/components/+config-secrets/secret-details.tsx b/src/renderer/components/+config-secrets/secret-details.tsx index adb1877799..aecce6e1a0 100644 --- a/src/renderer/components/+config-secrets/secret-details.tsx +++ b/src/renderer/components/+config-secrets/secret-details.tsx @@ -116,7 +116,7 @@ export class SecretDetails extends React.Component { return ( <> - + Data {secrets.map(this.renderSecret)}
plural @@ -109,7 +109,7 @@ export class CRDDetails extends React.Component {
{printerColumns.length > 0 && <> - + Additional Printer Columns Name @@ -136,7 +136,7 @@ export class CRDDetails extends React.Component { } {validation && <> - + Validation { {type} - + Involved object
Name diff --git a/src/renderer/components/+helm-releases/release-details/release-details.tsx b/src/renderer/components/+helm-releases/release-details/release-details.tsx index 4346b11691..5f4168ed57 100644 --- a/src/renderer/components/+helm-releases/release-details/release-details.tsx +++ b/src/renderer/components/+helm-releases/release-details/release-details.tsx @@ -113,7 +113,7 @@ class NonInjectedReleaseDetails extends Component - + Values
{this.renderValues()} - + Notes {this.renderNotes()} - + Resources {resources && this.renderResources(resources)}
); diff --git a/src/renderer/components/+network-endpoints/endpoint-details.tsx b/src/renderer/components/+network-endpoints/endpoint-details.tsx index 5de9fdfe18..13a3a99ff0 100644 --- a/src/renderer/components/+network-endpoints/endpoint-details.tsx +++ b/src/renderer/components/+network-endpoints/endpoint-details.tsx @@ -35,7 +35,7 @@ export class EndpointDetails extends React.Component { return (
- + Subsets { endpoint.getEndpointSubsets().map((subset) => ( diff --git a/src/renderer/components/+network-ingresses/ingress-details.tsx b/src/renderer/components/+network-ingresses/ingress-details.tsx index 5254b2c663..9634e0149c 100644 --- a/src/renderer/components/+network-ingresses/ingress-details.tsx +++ b/src/renderer/components/+network-ingresses/ingress-details.tsx @@ -160,10 +160,10 @@ export class IngressDetails extends React.Component { {serviceName}:{servicePort} } - + Rules {this.renderPaths(ingress)} - + Load-Balancer Ingress Points {this.renderIngressPoints(ingressPoints)}
); diff --git a/src/renderer/components/+network-policies/network-policy-details.tsx b/src/renderer/components/+network-policies/network-policy-details.tsx index cc73a7ae1b..36e1e6da29 100644 --- a/src/renderer/components/+network-policies/network-policy-details.tsx +++ b/src/renderer/components/+network-policies/network-policy-details.tsx @@ -170,7 +170,7 @@ export class NetworkPolicyDetails extends React.Component - + Ingress {ingress.map((ingress, i) => (
{this.renderNetworkPolicyPorts(ingress.ports)} @@ -182,7 +182,7 @@ export class NetworkPolicyDetails extends React.Component - + Egress {egress.map((egress, i) => (
{this.renderNetworkPolicyPorts(egress.ports)} diff --git a/src/renderer/components/+network-services/service-details.tsx b/src/renderer/components/+network-services/service-details.tsx index 833fd7b59d..9bfa65bbbc 100644 --- a/src/renderer/components/+network-services/service-details.tsx +++ b/src/renderer/components/+network-services/service-details.tsx @@ -86,7 +86,7 @@ class NonInjectedServiceDetails extends React.Component - + Connection {spec.clusterIP} @@ -129,7 +129,7 @@ class NonInjectedServiceDetails extends React.Component )} - + Endpoint
diff --git a/src/renderer/components/+nodes/details.tsx b/src/renderer/components/+nodes/details.tsx index f9646478c1..feffd12d21 100644 --- a/src/renderer/components/+nodes/details.tsx +++ b/src/renderer/components/+nodes/details.tsx @@ -169,9 +169,9 @@ class NonInjectedNodeDetails extends React.Component } - + Capacity - + Allocatable { - renderRuleGroup( title: React.ReactNode, group: RuleGroup) { - if (!group) return null; - const { rule, ranges } = group; - - return ( - <> - - - {rule} + renderRuleGroup = (title: React.ReactNode, { rule, ranges }: RuleGroup) => ( + <> + {title} + + {rule} + + {ranges && ( + + {ranges.map(({ min, max }, index) => ( + + ))} - {ranges && ( - - {ranges.map(({ min, max }, index) => { - return ; - })} - - )} - - ); - } + )} + + ); render() { const { object: psp } = this.props; @@ -161,7 +158,7 @@ export class PodSecurityPolicyDetails extends React.Component - + Allowed Host Paths
Path Prefix @@ -179,14 +176,14 @@ export class PodSecurityPolicyDetails extends React.Component )} - {this.renderRuleGroup("Fs Group", fsGroup)} - {this.renderRuleGroup("Run As Group", runAsGroup)} - {this.renderRuleGroup("Run As User", runAsUser)} - {this.renderRuleGroup("Supplemental Groups", supplementalGroups)} + {fsGroup && this.renderRuleGroup("Fs Group", fsGroup)} + {runAsGroup && this.renderRuleGroup("Run As Group", runAsGroup)} + {runAsUser && this.renderRuleGroup("Run As User", runAsUser)} + {supplementalGroups && this.renderRuleGroup("Supplemental Groups", supplementalGroups)} {runtimeClass && ( <> - + Runtime Class {runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"} @@ -198,7 +195,7 @@ export class PodSecurityPolicyDetails extends React.Component - + Se Linux {seLinux.rule} diff --git a/src/renderer/components/+storage-classes/storage-class-details.tsx b/src/renderer/components/+storage-classes/storage-class-details.tsx index dc8a118d6b..0a5f14161c 100644 --- a/src/renderer/components/+storage-classes/storage-class-details.tsx +++ b/src/renderer/components/+storage-classes/storage-class-details.tsx @@ -80,7 +80,7 @@ class NonInjectedStorageClassDetails extends React.Component - + Parameters { Object.entries(parameters).map(([name, value]) => ( diff --git a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx index a13d2175a5..bf2d824729 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx +++ b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx @@ -102,7 +102,7 @@ export class PersistentVolumeClaimDetails extends React.Component - + Selector {volumeClaim.getMatchLabels().map(label => )} diff --git a/src/renderer/components/+storage-volumes/volume-details-list.tsx b/src/renderer/components/+storage-volumes/volume-details-list.tsx index a58d41f6cf..1e9a8a93c4 100644 --- a/src/renderer/components/+storage-volumes/volume-details-list.tsx +++ b/src/renderer/components/+storage-volumes/volume-details-list.tsx @@ -67,7 +67,7 @@ export class VolumeDetailsList extends React.Component { return (
- + Persistent Volumes
- + Network File System { Object.entries(nfs).map(([name, value]) => ( @@ -78,7 +78,7 @@ export class PersistentVolumeDetails extends React.Component - + FlexVolume {flexVolume.driver} @@ -94,7 +94,7 @@ export class PersistentVolumeDetails extends React.Component - + Claim {claimRef.kind} diff --git a/src/renderer/components/+user-management/+cluster-role-bindings/details.tsx b/src/renderer/components/+user-management/+cluster-role-bindings/details.tsx index 6730227768..30704e5166 100644 --- a/src/renderer/components/+user-management/+cluster-role-bindings/details.tsx +++ b/src/renderer/components/+user-management/+cluster-role-bindings/details.tsx @@ -68,7 +68,7 @@ export class ClusterRoleBindingDetails extends React.Component - + Reference
Kind @@ -82,7 +82,7 @@ export class ClusterRoleBindingDetails extends React.Component
- + Bindings {subjects.length > 0 && ( diff --git a/src/renderer/components/+user-management/+cluster-roles/details.tsx b/src/renderer/components/+user-management/+cluster-roles/details.tsx index 96584e7f2c..f06e9785d1 100644 --- a/src/renderer/components/+user-management/+cluster-roles/details.tsx +++ b/src/renderer/components/+user-management/+cluster-roles/details.tsx @@ -28,7 +28,7 @@ export class ClusterRoleDetails extends React.Component
- + Rules {rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => { return (
diff --git a/src/renderer/components/+user-management/+role-bindings/details.tsx b/src/renderer/components/+user-management/+role-bindings/details.tsx index 2961a148cc..bbc3d56015 100644 --- a/src/renderer/components/+user-management/+role-bindings/details.tsx +++ b/src/renderer/components/+user-management/+role-bindings/details.tsx @@ -64,7 +64,7 @@ export class RoleBindingDetails extends React.Component
- + Reference
Kind @@ -78,7 +78,7 @@ export class RoleBindingDetails extends React.Component
- + Bindings {subjects.length > 0 && ( diff --git a/src/renderer/components/+user-management/+roles/details.tsx b/src/renderer/components/+user-management/+roles/details.tsx index 7342715fd1..0f24e9e711 100644 --- a/src/renderer/components/+user-management/+roles/details.tsx +++ b/src/renderer/components/+user-management/+roles/details.tsx @@ -27,7 +27,7 @@ export class RoleDetails extends React.Component { return (
- + Rules {rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => { return (
diff --git a/src/renderer/components/+user-management/+service-accounts/details.tsx b/src/renderer/components/+user-management/+service-accounts/details.tsx index f627b04b21..7d0beedfd8 100644 --- a/src/renderer/components/+user-management/+service-accounts/details.tsx +++ b/src/renderer/components/+user-management/+service-accounts/details.tsx @@ -143,7 +143,7 @@ export class ServiceAccountsDetails extends React.Component } - + Mountable secrets
{this.renderSecrets()}
diff --git a/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx b/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx index 19d49b2de8..4e73d11e6f 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx +++ b/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx @@ -78,7 +78,7 @@ class NonInjectedCronJobDetails extends React.Component {childJobs.length > 0 && <> - + Jobs {childJobs.map((job: Job) => { const selectors = job.getSelectors(); const condition = job.getCondition(); diff --git a/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx b/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx index 110b1da86e..46795770f1 100644 --- a/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx @@ -53,7 +53,7 @@ export class DeploymentReplicaSets extends React.Component - + Deploy Revisions
{ + variant: PodVolumeVariants[Kind]; + pod: Pod; + volumeName: string; +} + +export type VolumeVariantComponent = React.FunctionComponent>; + +export interface LocalRefProps { + pod: Pod; + title: string; + kubeRef: LocalObjectReference | SecretReference | undefined; + api: KubeApi; +} + +export const LocalRef = ({ pod, title, kubeRef: ref, api }: LocalRefProps) => { + if (!ref) { + return null; + } + + return ( + + + {ref.name} + + + ); +}; diff --git a/src/renderer/components/+workloads-pods/details/volumes/variant.tsx b/src/renderer/components/+workloads-pods/details/volumes/variant.tsx new file mode 100644 index 0000000000..e08dc69527 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variant.tsx @@ -0,0 +1,291 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import type { Pod, PodVolume, PodVolumeKind } from "../../../../../common/k8s-api/endpoints"; +import { DrawerItem } from "../../../drawer"; +import { Icon } from "../../../icon"; +import { AwsElasticBlockStore } from "./variants/aws-elastic-block-store"; +import { AzureDisk } from "./variants/azure-disk"; +import { AzureFile } from "./variants/azure-file"; +import { CephFs } from "./variants/ceph-fs"; +import { Cinder } from "./variants/cinder"; +import { ConfigMap } from "./variants/config-map"; +import { ContainerStorageInterface } from "./variants/container-storage-interface"; +import { DownwardAPI } from "./variants/downward-api"; +import { EmptyDir } from "./variants/empty-dir"; +import { Ephemeral } from "./variants/ephemeral"; +import { FiberChannel } from "./variants/fiber-channel"; +import { FlexVolume } from "./variants/flex-volume"; +import { Flocker } from "./variants/flocker"; +import { GcePersistentDisk } from "./variants/gce-persistent-disk"; +import { GitRepo } from "./variants/git-repo"; +import { GlusterFs } from "./variants/gluster-fs"; +import { HostPath } from "./variants/host-path"; +import { IScsi } from "./variants/i-scsi"; +import { Local } from "./variants/local"; +import { NetworkFs } from "./variants/network-fs"; +import { PersistentVolumeClaim } from "./variants/persistent-volume-claim"; +import { PhotonPersistentDisk } from "./variants/photon-persistent-disk"; +import { PortworxVolume } from "./variants/portworx-volume"; +import { Projected } from "./variants/projected"; +import { Quobyte } from "./variants/quobyte"; +import { RadosBlockDevice } from "./variants/rados-block-device"; +import { ScaleIo } from "./variants/scale-io"; +import { Secret } from "./variants/secret"; +import { StorageOs } from "./variants/storage-os"; +import { VsphereVolume } from "./variants/vsphere-volume"; + +const deprecatedVolumeTypes = new Set([ + "flocker", + "gitRepo", + "quobyte", + "storageos", +]); + +interface VolumeVariantProps { + pod: Pod; + volume: PodVolume; +} + +interface VolumeVariantRender { + kind: PodVolumeKind; + element: JSX.Element; +} + +function renderVolumeVariant({ pod, volume }: VolumeVariantProps): VolumeVariantRender | null { + if (volume.awsElasticBlockStore) { + return { + kind: "awsElasticBlockStore", + element: , + }; + } + + if (volume.azureDisk) { + return { + kind: "azureDisk", + element: , + }; + } + + if (volume.azureFile) { + return { + kind: "azureFile", + element: , + }; + } + + if (volume.cephfs) { + return { + kind: "cephfs", + element: , + }; + } + + if (volume.cinder) { + return { + kind: "cinder", + element: , + }; + } + + if (volume.configMap) { + return { + kind: "configMap", + element: , + }; + } + + if (volume.csi) { + return { + kind: "csi", + element: , + }; + } + + if (volume.downwardAPI) { + return { + kind: "downwardAPI", + element: , + }; + } + + if (volume.emptyDir) { + return { + kind: "emptyDir", + element: , + }; + } + + if (volume.ephemeral) { + return { + kind: "ephemeral", + element: , + }; + } + + if (volume.fc) { + return { + kind: "fc", + element: , + }; + } + + if (volume.flexVolume) { + return { + kind: "flexVolume", + element: , + }; + } + + if (volume.flocker) { + return { + kind: "flocker", + element: , + }; + } + + if (volume.gcePersistentDisk) { + return { + kind: "gcePersistentDisk", + element: , + }; + } + + if (volume.gitRepo) { + return { + kind: "gitRepo", + element: , + }; + } + + if (volume.glusterfs) { + return { + kind: "glusterfs", + element: , + }; + } + + if (volume.hostPath) { + return { + kind: "hostPath", + element: , + }; + } + + if (volume.iscsi) { + return { + kind: "iscsi", + element: , + }; + } + + if (volume.local) { + return { + kind: "local", + element: , + }; + } + + if (volume.nfs) { + return { + kind: "nfs", + element: , + }; + } + + if (volume.persistentVolumeClaim) { + return { + kind: "persistentVolumeClaim", + element: , + }; + } + + if (volume.photonPersistentDisk) { + return { + kind: "photonPersistentDisk", + element: , + }; + } + + if (volume.portworxVolume) { + return { + kind: "portworxVolume", + element: , + }; + } + + if (volume.projected) { + return { + kind: "projected", + element: , + }; + } + + if (volume.quobyte) { + return { + kind: "quobyte", + element: , + }; + } + + if (volume.rbd) { + return { + kind: "rbd", + element: , + }; + } + + if (volume.scaleIO) { + return { + kind: "scaleIO", + element: , + }; + } + + if (volume.secret) { + return { + kind: "secret", + element: , + }; + } + + if (volume.storageos) { + return { + kind: "storageos", + element: , + }; + } + + if (volume.vsphereVolume) { + return { + kind: "vsphereVolume", + element: , + }; + } + + return null; +} + +export function VolumeVariant(props: VolumeVariantProps) { + const result = renderVolumeVariant(props); + + if (!result) { + return

Error! Unknown pod volume kind

; + } + + const { kind, element } = result; + const isDeprecated = deprecatedVolumeTypes.has(kind); + + return ( + <> + + {kind} + {isDeprecated && } + + {element} + + ); +} diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/aws-elastic-block-store.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/aws-elastic-block-store.tsx new file mode 100644 index 0000000000..285a0fa12e --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/aws-elastic-block-store.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const AwsElasticBlockStore: VolumeVariantComponent<"awsElasticBlockStore"> = ( + ({ variant: { volumeID, fsType = "ext4" }}) => ( + <> + + {volumeID} + + + {fsType} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/azure-disk.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/azure-disk.tsx new file mode 100644 index 0000000000..0e19b41843 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/azure-disk.tsx @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const AzureDisk: VolumeVariantComponent<"azureDisk"> = ( + ({ variant: { diskName, diskURI, kind = "Shared", cachingMode = "None", fsType = "ext4", readonly = false }}) => ( + <> + + {diskName} + + + {diskURI} + + + {kind} + + + {cachingMode} + + + {fsType} + + + {readonly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/azure-file.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/azure-file.tsx new file mode 100644 index 0000000000..c73fd42c02 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/azure-file.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const AzureFile: VolumeVariantComponent<"azureFile"> = ( + ({ variant: { readOnly = false, secretName, shareName, secretNamespace = "default" }}) => ( + <> + + {secretName} + + + {shareName} + + + {secretNamespace} + + + {readOnly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/ceph-fs.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/ceph-fs.tsx new file mode 100644 index 0000000000..2f9260a58c --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/ceph-fs.tsx @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { secretsApi } from "../../../../../../common/k8s-api/endpoints"; +import { DrawerItem } from "../../../../drawer"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const CephFs: VolumeVariantComponent<"cephfs"> = ( + ({ pod, variant: { monitors, path = "/", user = "admin", secretFile = "/etc/ceph/user.secret", secretRef, readOnly }}) => ( + <> + +
    + {monitors.map(monitor =>
  • {monitor}
  • )} +
+
+ + {path} + + + {user} + + { + secretRef + ? ( + + ) + : ( + + {secretFile} + + ) + } + + {readOnly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/cinder.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/cinder.tsx new file mode 100644 index 0000000000..366438e0d8 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/cinder.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const Cinder: VolumeVariantComponent<"cinder"> = ( + ({ variant: { volumeID, fsType = "ext4" }}) => ( + <> + + {volumeID} + + + {fsType} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/config-map.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/config-map.tsx new file mode 100644 index 0000000000..35cace6617 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/config-map.tsx @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { configMapApi } from "../../../../../../common/k8s-api/endpoints"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const ConfigMap: VolumeVariantComponent<"configMap"> = ( + ({ pod, variant: { name }}) => ( + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/container-storage-interface.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/container-storage-interface.tsx new file mode 100644 index 0000000000..57dff33196 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/container-storage-interface.tsx @@ -0,0 +1,67 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { secretsApi } from "../../../../../../common/k8s-api/endpoints"; +import { DrawerItem } from "../../../../drawer"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const ContainerStorageInterface: VolumeVariantComponent<"csi"> = ({ + pod, + variant: { + driver, + readOnly = false, + fsType = "ext4", + volumeAttributes = {}, + nodePublishSecretRef, + controllerPublishSecretRef, + nodeStageSecretRef, + controllerExpandSecretRef, + }, +}) => ( + <> + + {driver} + + + {readOnly.toString()} + + + {fsType} + + + + + + { + Object.entries(volumeAttributes) + .map(([key, value]) => ( + + {value} + + )) + } + +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/downward-api.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/downward-api.tsx new file mode 100644 index 0000000000..c6c14bda2e --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/downward-api.tsx @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const DownwardAPI: VolumeVariantComponent<"downwardAPI"> = ( + ({ variant: { items }}) => ( + <> + +
    + {items.map(item =>
  • {item.path}
  • )} +
+
+ + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/empty-dir.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/empty-dir.tsx new file mode 100644 index 0000000000..c648d42354 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/empty-dir.tsx @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const EmptyDir: VolumeVariantComponent<"emptyDir"> = ( + ({ variant: { medium, sizeLimit }}) => ( + <> + + {medium || ""} + + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/ephemeral.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/ephemeral.tsx new file mode 100644 index 0000000000..0ec5878c4f --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/ephemeral.tsx @@ -0,0 +1,29 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { dump } from "js-yaml"; +import React from "react"; +import { DrawerItem, DrawerItemLabels } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const Ephemeral: VolumeVariantComponent<"ephemeral"> = ( + ({ pod, volumeName, variant: { volumeClaimTemplate: { metadata, spec }}}) => ( + <> + + {pod.getName()}-{volumeName} + + + + + {dump(spec)} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/fiber-channel.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/fiber-channel.tsx new file mode 100644 index 0000000000..684c4f1f32 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/fiber-channel.tsx @@ -0,0 +1,29 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const FiberChannel: VolumeVariantComponent<"fc"> = ( + ({ variant: { targetWWNs, lun, fsType = "ext4", readOnly = false }}) => ( + <> + +
    + {targetWWNs.map(targetWWN =>
  • {targetWWN}
  • )} +
+
+ + {lun.toString()} + + + {fsType} + + + {readOnly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/flex-volume.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/flex-volume.tsx new file mode 100644 index 0000000000..f7cc60de35 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/flex-volume.tsx @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { secretsApi } from "../../../../../../common/k8s-api/endpoints"; +import { DrawerItem } from "../../../../drawer"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const FlexVolume: VolumeVariantComponent<"flexVolume"> = ( + ({ pod, variant: { driver, fsType, secretRef, readOnly = false, options }}) => ( + <> + + {driver} + + + {fsType || "-- system default --"} + + + + {readOnly.toString()} + + { + ...Object.entries(options) + .map(([key, value]) => ( + + {value} + + )) + } + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/flocker.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/flocker.tsx new file mode 100644 index 0000000000..d4edefcba2 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/flocker.tsx @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const Flocker: VolumeVariantComponent<"flocker"> = ( + ({ variant: { datasetName }}) => ( + <> + + {datasetName} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/gce-persistent-disk.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/gce-persistent-disk.tsx new file mode 100644 index 0000000000..7c15b53109 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/gce-persistent-disk.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const GcePersistentDisk: VolumeVariantComponent<"gcePersistentDisk"> = ( + ({ variant: { pdName, fsType = "ext4" }}) => ( + <> + + {pdName} + + + {fsType} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/git-repo.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/git-repo.tsx new file mode 100644 index 0000000000..23e6421210 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/git-repo.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const GitRepo: VolumeVariantComponent<"gitRepo"> = ( + ({ variant: { repository, revision }}) => ( + <> + + {repository} + + + {revision} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/gluster-fs.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/gluster-fs.tsx new file mode 100644 index 0000000000..b3937b7610 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/gluster-fs.tsx @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const GlusterFs: VolumeVariantComponent<"glusterfs"> = ( + ({ variant: { endpoints, path, readOnly = false }}) => ( + <> + + {endpoints} + + + {path} + + + {readOnly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/host-path.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/host-path.tsx new file mode 100644 index 0000000000..5b80431733 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/host-path.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const HostPath: VolumeVariantComponent<"hostPath"> = ( + ({ variant: { path, type }}) => ( + <> + + {path} + + + {type || "-- none --"} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/i-scsi.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/i-scsi.tsx new file mode 100644 index 0000000000..1146752061 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/i-scsi.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const IScsi: VolumeVariantComponent<"iscsi"> = ( + ({ variant: { targetPortal, iqn, lun, fsType = "ext4", readOnly = false, chapAuthDiscovery, chapAuthSession, secretRef }}) => ( + <> + + {targetPortal} + + + {iqn} + + + {lun.toString()} + + + {fsType} + + + {readOnly.toString()} + + {chapAuthDiscovery && ( + + {chapAuthDiscovery.toString()} + + )} + {chapAuthSession && ( + + {chapAuthSession.toString()} + + )} + { secretRef && ( + + {secretRef.name} + + )} + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/local.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/local.tsx new file mode 100644 index 0000000000..a4e087ae2c --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/local.tsx @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const Local: VolumeVariantComponent<"local"> = ( + ({ variant: { path }}) => ( + <> + + {path} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/network-fs.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/network-fs.tsx new file mode 100644 index 0000000000..d40f2eaa08 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/network-fs.tsx @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const NetworkFs: VolumeVariantComponent<"nfs"> = ( + ({ variant: { server, path, readOnly = false }}) => ( + <> + + {server} + + + {path} + + + {readOnly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/persistent-volume-claim.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/persistent-volume-claim.tsx new file mode 100644 index 0000000000..f7d89907a9 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/persistent-volume-claim.tsx @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { pvcApi } from "../../../../../../common/k8s-api/endpoints"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const PersistentVolumeClaim: VolumeVariantComponent<"persistentVolumeClaim"> = ( + ({ pod, variant: { claimName }}) => ( + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/photon-persistent-disk.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/photon-persistent-disk.tsx new file mode 100644 index 0000000000..457da2f62b --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/photon-persistent-disk.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const PhotonPersistentDisk: VolumeVariantComponent<"photonPersistentDisk"> = ( + ({ variant: { pdID, fsType = "ext4" }}) => ( + <> + + {pdID} + + + {fsType} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/portworx-volume.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/portworx-volume.tsx new file mode 100644 index 0000000000..103be78acf --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/portworx-volume.tsx @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const PortworxVolume: VolumeVariantComponent<"portworxVolume"> = ( + ({ variant: { volumeID, fsType = "ext4", readOnly = false }}) => ( + <> + + {volumeID} + + + {fsType} + + + {readOnly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/projected.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/projected.tsx new file mode 100644 index 0000000000..8b04d61755 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/projected.tsx @@ -0,0 +1,88 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem, DrawerTitle } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const Projected: VolumeVariantComponent<"projected"> = ( + ({ variant: { sources, defaultMode }}) => ( + <> + + 0o{defaultMode.toString(8)} + + + { + sources.map(({ secret, downwardAPI, configMap, serviceAccountToken }, index) => ( + + {secret && ( + <> + Secret + + {secret.name} + + +
    + {secret.items?.map(({ key, path }) => ( +
  • + {key} ⇢ {path} +
  • + ))} +
+
+ + )} + {downwardAPI && ( + <> + Downward API + +
    + {downwardAPI.items?.map(({ path }) => ( +
  • + {path} +
  • + ))} +
+
+ + )} + {configMap && ( + <> + Config Map + + {configMap.name} + + +
    + {configMap.items?.map(({ key, path }) => ( +
  • + {key} ⇢ {path} +
  • + ))} +
+
+ + )} + {serviceAccountToken && ( + <> + Service Account Token + + + {String(serviceAccountToken.expirationSeconds || (60*60 /* an hour */))}s + + + {serviceAccountToken.path} + + + )} +
+ )) + } +
+ + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/quobyte.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/quobyte.tsx new file mode 100644 index 0000000000..a315f254e8 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/quobyte.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const Quobyte: VolumeVariantComponent<"quobyte"> = ( + ({ variant: { registry, volume, readOnly = false, user = "serviceaccount", group, tenant }}) => ( + <> + + {registry} + + + {volume} + + + {readOnly.toString()} + + + {user} + + + {group ?? "-- no group --"} + + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/rados-block-device.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/rados-block-device.tsx new file mode 100644 index 0000000000..060aa14554 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/rados-block-device.tsx @@ -0,0 +1,52 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { secretsApi } from "../../../../../../common/k8s-api/endpoints"; +import { DrawerItem } from "../../../../drawer"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const RadosBlockDevice: VolumeVariantComponent<"rbd"> = ( + ({ pod, variant: { monitors, image, fsType = "ext4", pool = "rbd", user = "admin", keyring = "/etc/ceph/keyright", secretRef, readOnly = false }}) => ( + <> + +
    + {monitors.map(monitor =>
  • {monitor}
  • )} +
+
+ + {image} + + + {fsType} + + + {pool} + + + {user} + + { + secretRef + ? ( + + ) + : ( + + {keyring} + + ) + } + + {readOnly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/scale-io.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/scale-io.tsx new file mode 100644 index 0000000000..be9ae981e8 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/scale-io.tsx @@ -0,0 +1,49 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { secretsApi } from "../../../../../../common/k8s-api/endpoints"; +import { DrawerItem } from "../../../../drawer"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const ScaleIo: VolumeVariantComponent<"scaleIO"> = ( + ({ pod, variant: { gateway, system, secretRef, sslEnabled = false, protectionDomain, storagePool, storageMode = "ThinProvisioned", volumeName, fsType = "xfs", readOnly = false }}) => ( + <> + + {gateway} + + + {system} + + + + {sslEnabled.toString()} + + + + + + {volumeName} + + + {fsType} + + + {readOnly.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/secret.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/secret.tsx new file mode 100644 index 0000000000..ae67a8ce83 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/secret.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { secretsApi } from "../../../../../../common/k8s-api/endpoints"; +import { DrawerItem } from "../../../../drawer"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const Secret: VolumeVariantComponent<"secret"> = ( + ({ pod, variant: { secretName, items = [], defaultMode = 0o644, optional = false }}) => ( + <> + + + + 0o{defaultMode.toString(8)} + + + {optional.toString()} + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/storage-os.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/storage-os.tsx new file mode 100644 index 0000000000..0954b9288f --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/storage-os.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { secretsApi } from "../../../../../../common/k8s-api/endpoints"; +import { DrawerItem } from "../../../../drawer"; +import { LocalRef, VolumeVariantComponent } from "../variant-helpers"; + +export const StorageOs: VolumeVariantComponent<"storageos"> = ( + ({ pod, variant: { volumeName, volumeNamespace, fsType = "ext4", readOnly = false, secretRef }}) => ( + <> + + {volumeName} + + + + {fsType} + + + {readOnly.toString()} + + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/variants/vsphere-volume.tsx b/src/renderer/components/+workloads-pods/details/volumes/variants/vsphere-volume.tsx new file mode 100644 index 0000000000..eb97917e91 --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/variants/vsphere-volume.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import { DrawerItem } from "../../../../drawer"; +import type { VolumeVariantComponent } from "../variant-helpers"; + +export const VsphereVolume: VolumeVariantComponent<"vsphereVolume"> = ( + ({ variant: { volumePath, fsType = "ext4", storagePolicyName, storagePolicyID }}) => ( + <> + + {volumePath} + + + {fsType} + + + + + ) +); diff --git a/src/renderer/components/+workloads-pods/details/volumes/view.tsx b/src/renderer/components/+workloads-pods/details/volumes/view.tsx new file mode 100644 index 0000000000..f90ca6a31e --- /dev/null +++ b/src/renderer/components/+workloads-pods/details/volumes/view.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { observer } from "mobx-react"; +import React from "react"; +import type { Pod } from "../../../../../common/k8s-api/endpoints"; +import { DrawerTitle } from "../../../drawer"; +import { Icon } from "../../../icon"; +import { VolumeVariant } from "./variant"; + +export interface PodVolumesProps { + pod: Pod; +} + +export const PodVolumes = observer(({ pod }: PodVolumesProps) => { + const volumes = pod.getVolumes() ?? []; + + if (volumes.length === 0) { + return null; + } + + return ( + <> + Volumes + {volumes.map(volume => ( +
+
+ + {volume.name} +
+ +
+ ))} + + ); +}); diff --git a/src/renderer/components/+workloads-pods/pod-details-list.tsx b/src/renderer/components/+workloads-pods/pod-details-list.tsx index c7679706fd..12939a4a1f 100644 --- a/src/renderer/components/+workloads-pods/pod-details-list.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-list.tsx @@ -131,7 +131,7 @@ export class PodDetailsList extends React.Component { return (
- + Pods
{ - @observable secrets: Map = observable.map(); +export const PodDetailsSecrets = observer(({ pod }: PodDetailsSecretsProps) => { + const [secrets, setSecrets] = useState(new Map()); - componentDidMount(): void { - disposeOnUnmount(this, [ - autorun(async () => { - const { pod } = this.props; - - const secrets = await Promise.all( - pod.getSecrets().map(secretName => secretsApi.get({ + useEffect(() => ( + reaction( + () => pod.getSecrets(), + async (secretNames) => { + const results = await Promise.allSettled( + secretNames.map(secretName => secretsApi.get({ name: secretName, namespace: pod.getNs(), })), ); - secrets.forEach(secret => secret && this.secrets.set(secret.getName(), secret)); - }), - ]); - } + setSecrets(new Map( + results + .filter(result => result.status === "fulfilled" && result.value) + .map(result => (result as PromiseFulfilledResult).value) + .map(secret => [secret.getName(), secret]), + )); + }, + { + fireImmediately: true, + }) + ), []); - constructor(props: PodDetailsSecretsProps) { - super(props); - makeObservable(this); - } + const renderSecret = (name: string) => { + const secret = secrets.get(name); - render() { - const { pod } = this.props; + if (!secret) { + return {name}; + } - return ( -
- { - pod.getSecrets().map(secretName => { - const secret = this.secrets.get(secretName); - - if (secret) { - return this.renderSecretLink(secret); - } else { - return ( - {secretName} - ); - } - }) - } -
- ); - } - - protected renderSecretLink(secret: Secret) { return ( {secret.getName()} ); - } -} + }; + + return ( +
+ {pod.getSecrets().map(renderSecret)} +
+ ); +}); + diff --git a/src/renderer/components/+workloads-pods/pod-details.tsx b/src/renderer/components/+workloads-pods/pod-details.tsx index 2b592ebe04..857faeefab 100644 --- a/src/renderer/components/+workloads-pods/pod-details.tsx +++ b/src/renderer/components/+workloads-pods/pod-details.tsx @@ -10,14 +10,13 @@ import kebabCase from "lodash/kebabCase"; import { disposeOnUnmount, observer } from "mobx-react"; import { Link } from "react-router-dom"; import { observable, reaction, makeObservable } from "mobx"; -import { type IPodMetrics, nodesApi, Pod, pvcApi, configMapApi, getMetricsForPods } from "../../../common/k8s-api/endpoints"; +import { type IPodMetrics, nodesApi, Pod, getMetricsForPods } from "../../../common/k8s-api/endpoints"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; import { boundMethod, cssNames, toJS } from "../../utils"; import { PodDetailsContainer } from "./pod-details-container"; import { PodDetailsAffinities } from "./pod-details-affinities"; import { PodDetailsTolerations } from "./pod-details-tolerations"; -import { Icon } from "../icon"; import { PodDetailsSecrets } from "./pod-details-secrets"; import { ResourceMetrics } from "../resource-metrics"; import type { KubeObjectDetailsProps } from "../kube-object-details"; @@ -28,6 +27,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { getDetailsUrl } from "../kube-detail-params"; import logger from "../../../common/logger"; +import { PodVolumes } from "./details/volumes/view"; export interface PodDetailsProps extends KubeObjectDetailsProps { } @@ -73,12 +73,13 @@ export class PodDetails extends React.Component { } const { status, spec } = pod; - const { conditions, podIP } = status; + const { conditions = [], podIP } = status ?? {}; const podIPs = pod.getIPs(); - const { nodeName } = spec; + const { nodeName } = spec ?? {}; const nodeSelector = pod.getNodeSelectors(); - const volumes = pod.getVolumes(); const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Pod); + const initContainers = pod.getInitContainers(); + const containers = pod.getContainers(); return (
@@ -90,26 +91,22 @@ export class PodDetails extends React.Component { )} + + {pod.getStatusMessage()} - - {nodeName && ( - - {nodeName} - - )} + {podIP} -
); } diff --git a/src/renderer/components/drawer/drawer-item-labels.tsx b/src/renderer/components/drawer/drawer-item-labels.tsx index c3dfb41bfc..e25e615910 100644 --- a/src/renderer/components/drawer/drawer-item-labels.tsx +++ b/src/renderer/components/drawer/drawer-item-labels.tsx @@ -6,21 +6,30 @@ import React from "react"; import { DrawerItem, DrawerItemProps } from "./drawer-item"; import { Badge } from "../badge"; +import { KubeObject } from "../../../common/k8s-api/kube-object"; export interface DrawerItemLabelsProps extends DrawerItemProps { - labels: string[]; + labels: string[] | Record; } export function DrawerItemLabels(props: DrawerItemLabelsProps) { const { labels, ...itemProps } = props; - if (!labels || !labels.length) { + if (!labels || typeof labels !== "object") { + return null; + } + + const labelStrings = Array.isArray(labels) + ? labels + : KubeObject.stringifyLabels(labels); + + if (labelStrings.length === 0) { return null; } return ( - {labels.map(label => )} + {labelStrings.map(label => )} ); } diff --git a/src/renderer/components/drawer/drawer-item.tsx b/src/renderer/components/drawer/drawer-item.tsx index 2656a635e2..1f49ca92e7 100644 --- a/src/renderer/components/drawer/drawer-item.tsx +++ b/src/renderer/components/drawer/drawer-item.tsx @@ -7,29 +7,32 @@ import "./drawer-item.scss"; import React from "react"; import { cssNames, displayBooleans } from "../../utils"; -export interface DrawerItemProps extends React.HTMLAttributes { +export interface DrawerItemProps extends React.HTMLAttributes { name: React.ReactNode; - className?: string; title?: string; labelsOnly?: boolean; hidden?: boolean; renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean" } -export class DrawerItem extends React.Component { - render() { - const { name, title, labelsOnly, children, hidden, className, renderBoolean, ...elemProps } = this.props; - - if (hidden) return null; - - const classNames = cssNames("DrawerItem", className, { labelsOnly }); - const content = displayBooleans(renderBoolean, children); - - return ( -
- {name} - {content} -
- ); +export function DrawerItem({ + name, + title, + labelsOnly, + children, + hidden = false, + className, + renderBoolean, + ...elemProps +}: DrawerItemProps) { + if (hidden) { + return null; } + + return ( +
+ {name} + {displayBooleans(renderBoolean, children)} +
+ ); } diff --git a/src/renderer/components/drawer/drawer-title.scss b/src/renderer/components/drawer/drawer-title.module.css similarity index 77% rename from src/renderer/components/drawer/drawer-title.scss rename to src/renderer/components/drawer/drawer-title.module.css index 74e525f1d2..7a4be3ee34 100644 --- a/src/renderer/components/drawer/drawer-title.scss +++ b/src/renderer/components/drawer/drawer-title.module.css @@ -4,7 +4,14 @@ */ .DrawerTitle { - padding: $padding * 1.5 $padding * 3; margin: $margin * 3 (-$margin * 3); background: var(--drawerSubtitleBackground); } + +.DrawerTitle.title { + padding: $padding * 1.5 $padding * 3; +} + +.DrawerTitle.sub-title { + padding: $padding $padding * 2; +} diff --git a/src/renderer/components/drawer/drawer-title.tsx b/src/renderer/components/drawer/drawer-title.tsx index 3ef2858c9b..81be36f5ac 100644 --- a/src/renderer/components/drawer/drawer-title.tsx +++ b/src/renderer/components/drawer/drawer-title.tsx @@ -3,23 +3,29 @@ * 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 { cssNames } from "../../utils"; export interface DrawerTitleProps { 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 { - render() { - const { title, children, className } = this.props; - - return ( -
- {title || children} -
- ); - } +export function DrawerTitle({ className, children, size = "title" }: DrawerTitleProps) { + return ( +
+ {children} +
+ ); } diff --git a/src/renderer/components/tabs/tabs.tsx b/src/renderer/components/tabs/tabs.tsx index 58e94f33dc..be70447f5a 100644 --- a/src/renderer/components/tabs/tabs.tsx +++ b/src/renderer/components/tabs/tabs.tsx @@ -17,8 +17,6 @@ interface TabsContextValue { onChange?(value: D): void; } -type Omit = Pick>; - export interface TabsProps extends TabsContextValue, Omit, "onChange"> { className?: string; center?: boolean; diff --git a/src/renderer/initializers/catalog-entity-detail-registry.tsx b/src/renderer/initializers/catalog-entity-detail-registry.tsx index f04f4e7aa9..0ffccd8063 100644 --- a/src/renderer/initializers/catalog-entity-detail-registry.tsx +++ b/src/renderer/initializers/catalog-entity-detail-registry.tsx @@ -17,7 +17,7 @@ export function initCatalogEntityDetailRegistry() { components: { Details: ({ entity }: CatalogEntityDetailsProps) => ( <> - + Kubernetes Information
{entity.metadata.distro || "unknown"} @@ -36,7 +36,7 @@ export function initCatalogEntityDetailRegistry() { components: { Details: ({ entity }: CatalogEntityDetailsProps) => ( <> - + More Information {entity.spec.url} diff --git a/yarn.lock b/yarn.lock index 6d7a162dc8..de2c7ac518 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13168,10 +13168,10 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" - integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +type-fest@^2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.0.tgz#ce342f58cab9114912f54b493d60ab39c3fc82b6" + integrity sha512-Qe5GRT+n/4GoqCNGGVp5Snapg1Omq3V7irBJB3EaKsp7HWDo5Gv2d/67gfNyV+d5EXD+x/RF5l1h4yJ7qNkcGA== type-is@~1.6.18: version "1.6.18"