mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into fix-big-cluster-table-freeze
This commit is contained in:
commit
85703f11e5
@ -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: () => <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.
|
||||
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<any>`.
|
||||
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<any>`.
|
||||
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 (
|
||||
<div>
|
||||
<p>Hello world!</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -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: () => <ExamplePage extension={this}/>,
|
||||
}
|
||||
}
|
||||
Page: () => <ExamplePage extension={this} />,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
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 <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() {
|
||||
return (
|
||||
<div>
|
||||
<p>Hello world!</p>
|
||||
</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).
|
||||
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: () => <ExamplePage extension={this}/>,
|
||||
}
|
||||
Page: () => <ExamplePage extension={this} />,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bonjour",
|
||||
components: {
|
||||
Page: () => <ExemplePage extension={this}/>,
|
||||
}
|
||||
}
|
||||
Page: () => <ExemplePage extension={this} />,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
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: () => <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.
|
||||
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<any>`.
|
||||
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<any>`.
|
||||
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 (
|
||||
<div>
|
||||
<p>Help yourself</p>
|
||||
</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.
|
||||
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: () => <ExamplePreferenceInput preference={this.preference}/>,
|
||||
Hint: () => <ExamplePreferenceHint/>
|
||||
}
|
||||
}
|
||||
Input: () => <ExamplePreferenceInput preference={this.preference} />,
|
||||
Hint: () => <ExamplePreferenceHint />,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
```
|
||||
@ -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<ExamplePreferenceProps> {
|
||||
|
||||
public constructor() {
|
||||
super({preference: { enabled: false}});
|
||||
super({ preference: { enabled: false } });
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@ -411,7 +416,9 @@ export class ExamplePreferenceInput extends React.Component<ExamplePreferencePro
|
||||
<Checkbox
|
||||
label="I understand appPreferences"
|
||||
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 {
|
||||
render() {
|
||||
return (
|
||||
<span>This is an example of an appPreference for extensions.</span>
|
||||
);
|
||||
return <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:
|
||||
|
||||
* `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: () => <HelpPage extension={this}/>,
|
||||
}
|
||||
}
|
||||
Page: () => <HelpPage extension={this} />,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
statusBarItems = [
|
||||
@ -486,7 +491,7 @@ export default class HelpExtension extends Renderer.LensExtension {
|
||||
<HelpIcon />
|
||||
My Status Bar Item
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -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<Namespace>) => <NamespaceMenuItem {...props} />
|
||||
}
|
||||
}
|
||||
MenuItem: (props: KubeObjectMenuProps<Namespace>) => (
|
||||
<NamespaceMenuItem {...props} />
|
||||
),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`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<Namespace>) {
|
||||
|
||||
return (
|
||||
<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>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`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<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.
|
||||
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<KubeObjectDetailsProps<Namespace>> {
|
||||
export class NamespaceDetailsItem extends React.Component<
|
||||
KubeObjectDetailsProps<Namespace>
|
||||
> {
|
||||
@observable private pods: Pod[];
|
||||
|
||||
async componentDidMount() {
|
||||
@ -681,10 +686,10 @@ export class NamespaceDetailsItem extends React.Component<KubeObjectDetailsProps
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<DrawerTitle title="Pods" />
|
||||
<PodsDetailsList pods={this.pods}/>
|
||||
<DrawerTitle>Pods</DrawerTitle>
|
||||
<PodsDetailsList pods={this.pods} />
|
||||
</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.
|
||||
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<PodsDetailsListProps> {
|
||||
<TableCell className="podAge">{pods[index].getAge()}</TableCell>
|
||||
<TableCell className="podStatus">{pods[index].getStatus()}</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { pods } = this.props
|
||||
const { pods } = this.props;
|
||||
|
||||
if (!pods?.length) {
|
||||
return null;
|
||||
@ -754,7 +754,7 @@ export class PodsDetailsList extends React.Component<PodsDetailsListProps> {
|
||||
<TableCell className="podAge">Age</TableCell>
|
||||
<TableCell className="podStatus">Status</TableCell>
|
||||
</TableHead>
|
||||
{ pods.map(this.getTableRow) }
|
||||
{pods.map(this.getTableRow)}
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
|
||||
46
package.json
46
package.json
@ -3,7 +3,7 @@
|
||||
"productName": "OpenLens",
|
||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"version": "5.4.0",
|
||||
"version": "5.5.0-alpha.0",
|
||||
"main": "static/build/main.js",
|
||||
"copyright": "© 2021 OpenLens Authors",
|
||||
"license": "MIT",
|
||||
@ -28,6 +28,8 @@
|
||||
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
||||
"build:win": "yarn run compile && electron-builder --win --dir",
|
||||
"integration": "jest --runInBand --detectOpenHandles --forceExit integration",
|
||||
"test:unit": "jest --watch --testPathIgnorePatterns integration",
|
||||
"test:integration": "func() { jest ${1:-xyz} --watch --runInBand --detectOpenHandles --forceExit --modulePaths=[\"<rootDir>/integration/\"]; }; func",
|
||||
"dist": "yarn run compile && electron-builder --publish onTag",
|
||||
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
|
||||
"download:binaries": "yarn run ts-node build/download_binaries.ts",
|
||||
@ -44,7 +46,7 @@
|
||||
"postversion": "git push --set-upstream ${GIT_REMOTE:-origin} release/v$npm_package_version"
|
||||
},
|
||||
"config": {
|
||||
"k8sProxyVersion": "0.1.5",
|
||||
"k8sProxyVersion": "0.2.1",
|
||||
"bundledKubectlVersion": "1.23.3",
|
||||
"bundledHelmVersion": "3.7.2",
|
||||
"sentryDsn": ""
|
||||
@ -203,7 +205,7 @@
|
||||
"@ogre-tools/injectable": "5.1.2",
|
||||
"@ogre-tools/injectable-react": "5.1.2",
|
||||
"@sentry/electron": "^2.5.4",
|
||||
"@sentry/integrations": "^6.15.0",
|
||||
"@sentry/integrations": "^6.19.2",
|
||||
"@types/circular-dependency-plugin": "5.0.5",
|
||||
"abort-controller": "^3.0.0",
|
||||
"auto-bind": "^4.0.0",
|
||||
@ -214,7 +216,7 @@
|
||||
"conf": "^7.1.2",
|
||||
"crypto-js": "^4.1.1",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-updater": "^4.6.1",
|
||||
"electron-updater": "^4.6.5",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"filehound": "^1.17.5",
|
||||
"fs-extra": "^9.0.1",
|
||||
@ -223,7 +225,7 @@
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"handlebars": "^4.7.7",
|
||||
"http-proxy": "^1.18.1",
|
||||
"immer": "^9.0.6",
|
||||
"immer": "^9.0.12",
|
||||
"joi": "^17.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^16.7.0",
|
||||
@ -243,7 +245,7 @@
|
||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||
"node-fetch": "lensapp/node-fetch#2.x",
|
||||
"node-pty": "^0.10.1",
|
||||
"npm": "^6.14.15",
|
||||
"npm": "^6.14.16",
|
||||
"p-limit": "^3.1.0",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
@ -265,7 +267,7 @@
|
||||
"tempy": "1.0.1",
|
||||
"url-parse": "^1.5.10",
|
||||
"uuid": "^8.3.2",
|
||||
"win-ca": "^3.4.5",
|
||||
"win-ca": "^3.5.0",
|
||||
"winston": "^3.6.0",
|
||||
"winston-console-format": "^1.0.8",
|
||||
"winston-transport-browserconsole": "^1.0.5",
|
||||
@ -277,8 +279,8 @@
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@sentry/types": "^6.18.2",
|
||||
"@testing-library/jest-dom": "^5.16.2",
|
||||
"@sentry/types": "^6.19.2",
|
||||
"@testing-library/jest-dom": "^5.16.3",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/byline": "^4.2.33",
|
||||
@ -293,18 +295,18 @@
|
||||
"@types/gunzip-maybe": "^1.4.0",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/html-webpack-plugin": "^3.2.6",
|
||||
"@types/http-proxy": "^1.17.7",
|
||||
"@types/http-proxy": "^1.17.8",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^16.2.13",
|
||||
"@types/jsdom": "^16.2.14",
|
||||
"@types/jsonpath": "^0.2.0",
|
||||
"@types/lodash": "^4.14.180",
|
||||
"@types/marked": "^4.0.3",
|
||||
"@types/md5-file": "^4.0.2",
|
||||
"@types/mini-css-extract-plugin": "^2.4.0",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/node": "14.17.33",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/node": "14.18.12",
|
||||
"@types/node-fetch": "^2.6.1",
|
||||
"@types/npm": "^2.0.32",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
"@types/randomcolor": "^0.5.6",
|
||||
@ -320,7 +322,7 @@
|
||||
"@types/request": "^2.48.7",
|
||||
"@types/request-promise-native": "^1.0.18",
|
||||
"@types/semver": "^7.3.9",
|
||||
"@types/sharp": "^0.29.4",
|
||||
"@types/sharp": "^0.30.0",
|
||||
"@types/spdy": "^3.4.5",
|
||||
"@types/tar": "^4.0.5",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
@ -348,7 +350,7 @@
|
||||
"electron": "^14.2.7",
|
||||
"electron-builder": "^22.14.13",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"esbuild": "^0.13.15",
|
||||
"esbuild": "^0.14.27",
|
||||
"esbuild-loader": "^2.18.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
@ -372,7 +374,7 @@
|
||||
"node-gyp": "7.1.2",
|
||||
"node-loader": "^2.0.0",
|
||||
"nodemon": "^2.0.15",
|
||||
"playwright": "^1.17.1",
|
||||
"playwright": "^1.20.1",
|
||||
"postcss": "^8.4.12",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"randomcolor": "^0.6.2",
|
||||
@ -384,27 +386,27 @@
|
||||
"react-select-event": "^5.3.0",
|
||||
"react-table": "^7.7.0",
|
||||
"react-window": "^1.8.6",
|
||||
"sass": "^1.45.1",
|
||||
"sass": "^1.49.9",
|
||||
"sass-loader": "^12.6.0",
|
||||
"sharp": "^0.29.3",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.0.7",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"tar-stream": "^2.2.0",
|
||||
"ts-jest": "26.5.6",
|
||||
"ts-loader": "^9.2.6",
|
||||
"ts-loader": "^9.2.8",
|
||||
"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",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typescript": "^4.5.2",
|
||||
"typescript": "^4.5.5",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"webpack": "^5.69.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"xterm": "^4.15.0",
|
||||
"xterm": "^4.18.0",
|
||||
"xterm-addon-fit": "^0.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
30
src/common/__tests__/catalog-entity.test.ts
Normal file
30
src/common/__tests__/catalog-entity.test.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
|
||||
|
||||
class TestCatalogCategory extends CatalogCategory {
|
||||
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||
public readonly kind = "CatalogCategory";
|
||||
public metadata = {
|
||||
name: "Test Category",
|
||||
icon: "",
|
||||
};
|
||||
public spec: CatalogCategorySpec = {
|
||||
group: "entity.k8slens.dev",
|
||||
versions: [],
|
||||
names: {
|
||||
kind: "Test",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("CatalogCategory", () => {
|
||||
it("returns name", () => {
|
||||
const category = new TestCatalogCategory();
|
||||
|
||||
expect(category.getName()).toEqual("Test Category");
|
||||
});
|
||||
});
|
||||
@ -24,8 +24,6 @@ import { createClusterInjectionToken } from "../cluster/create-cluster-injection
|
||||
|
||||
import directoryForUserDataInjectable
|
||||
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import kubeAuthProxyCaInjectable from "../../main/kube-auth-proxy/kube-auth-proxy-ca.injectable";
|
||||
import createKubeAuthProxyCertFilesInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy-cert-files.injectable";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@ -89,8 +87,6 @@ describe("cluster-store", () => {
|
||||
mainDi = dis.mainDi;
|
||||
|
||||
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
mainDi.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
|
||||
mainDi.override(kubeAuthProxyCaInjectable, () => ({} as any));
|
||||
|
||||
await dis.runSetups();
|
||||
|
||||
|
||||
@ -173,6 +173,13 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm
|
||||
return `${this.spec.group}/${this.spec.names.kind}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this category
|
||||
*/
|
||||
public getName(): string {
|
||||
return this.metadata.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter for menu items of catalogAddMenu
|
||||
* @param fn The function that should return a truthy value if that menu item should be displayed
|
||||
|
||||
40
src/common/k8s-api/api-base.ts
Normal file
40
src/common/k8s-api/api-base.ts
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { JsonApi } from "./json-api";
|
||||
import { apiPrefix, isDebugging, isDevelopment } from "../vars";
|
||||
import { appEventBus } from "../app-event-bus/event-bus";
|
||||
|
||||
export let apiBase: JsonApi;
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
appEventBus.addListener((event) => {
|
||||
if (event.name !== "lens-proxy" && event.action !== "listen") return;
|
||||
|
||||
const params = event.params as { port?: number };
|
||||
|
||||
if (!params.port) return;
|
||||
|
||||
apiBase = new JsonApi({
|
||||
serverAddress: `http://127.0.0.1:${params.port}`,
|
||||
apiBase: apiPrefix,
|
||||
debug: isDevelopment || isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `localhost:${params.port}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
apiBase = new JsonApi({
|
||||
serverAddress: `http://127.0.0.1:${window.location.port}`,
|
||||
apiBase: apiPrefix,
|
||||
debug: isDevelopment || isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": window.location.host,
|
||||
},
|
||||
});
|
||||
}
|
||||
20
src/common/k8s-api/api-kube.ts
Normal file
20
src/common/k8s-api/api-kube.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { isClusterPageContext } from "../utils";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
import { apiKubePrefix, isDevelopment } from "../vars";
|
||||
|
||||
export const apiKube = isClusterPageContext()
|
||||
? new KubeJsonApi({
|
||||
serverAddress: `http://127.0.0.1:${window.location.port}`,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDevelopment,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": window.location.host,
|
||||
},
|
||||
})
|
||||
: undefined;
|
||||
@ -141,8 +141,11 @@ export function isMetricsEmpty(metrics: Record<string, IMetrics>) {
|
||||
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
|
||||
}
|
||||
|
||||
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | void {
|
||||
if (!metrics) return;
|
||||
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | undefined {
|
||||
if (!metrics) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const itemMetrics = { ...metrics };
|
||||
|
||||
for (const metric in metrics) {
|
||||
|
||||
@ -31,17 +31,25 @@ export interface IPvcMetrics<T = IMetrics> {
|
||||
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<string, string>;
|
||||
limits?: Record<string, string>;
|
||||
};
|
||||
volumeName?: string;
|
||||
storageClassName?: string;
|
||||
volumeMode?: string;
|
||||
dataSource?: {
|
||||
apiGroup: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PersistentVolumeClaim {
|
||||
spec: PersistentVolumeClaimSpec;
|
||||
status: {
|
||||
phase: string; // Pending
|
||||
};
|
||||
|
||||
@ -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<Pod> {
|
||||
getLogs = async (params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> => {
|
||||
@ -112,12 +116,8 @@ export interface IPodContainer extends Partial<Record<PodContainerProbe, IContai
|
||||
};
|
||||
}[];
|
||||
envFrom?: {
|
||||
configMapRef?: {
|
||||
name: string;
|
||||
};
|
||||
secretRef?: {
|
||||
name: string;
|
||||
};
|
||||
configMapRef?: LocalObjectReference;
|
||||
secretRef?: LocalObjectReference;
|
||||
}[];
|
||||
volumeMounts?: {
|
||||
name: string;
|
||||
@ -195,6 +195,444 @@ export interface IPodContainerStatus {
|
||||
started?: boolean;
|
||||
}
|
||||
|
||||
export interface AwsElasticBlockStoreSource {
|
||||
volumeID: string;
|
||||
fsType: string;
|
||||
}
|
||||
|
||||
export interface AzureDiskSource {
|
||||
/**
|
||||
* The name of the VHD blob object OR the name of an Azure managed data disk if `kind` is `"Managed"`.
|
||||
*/
|
||||
diskName: string;
|
||||
/**
|
||||
* The URI of the vhd blob object OR the `resourceID` of an Azure managed data disk if `kind` is `"Managed"`.
|
||||
*/
|
||||
diskURI: string;
|
||||
/**
|
||||
* Kind of disk
|
||||
* @default "Shared"
|
||||
*/
|
||||
kind?: "Shared" | "Dedicated" | "Managed";
|
||||
/**
|
||||
* Disk caching mode.
|
||||
* @default "None"
|
||||
*/
|
||||
cachingMode?: "None" | "ReadOnly" | "ReadWrite";
|
||||
/**
|
||||
* The filesystem type to mount.
|
||||
* @default "ext4"
|
||||
*/
|
||||
fsType?: string;
|
||||
/**
|
||||
* Whether the filesystem is used as readOnly.
|
||||
* @default false
|
||||
*/
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export interface AzureFileSource {
|
||||
/**
|
||||
* The name of the secret that contains both Azure storage account name and key.
|
||||
*/
|
||||
secretName: string;
|
||||
/**
|
||||
* The share name to be used.
|
||||
*/
|
||||
shareName: string;
|
||||
/**
|
||||
* In case the secret is stored in a different namespace.
|
||||
* @default "default"
|
||||
*/
|
||||
secretNamespace?: string;
|
||||
/**
|
||||
* Whether the filesystem is used as readOnly.
|
||||
*/
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export interface CephfsSource {
|
||||
/**
|
||||
* List of Ceph monitors
|
||||
*/
|
||||
monitors: string[];
|
||||
/**
|
||||
* Used as the mounted root, rather than the full Ceph tree.
|
||||
* @default "/"
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* The RADOS user name.
|
||||
* @default "admin"
|
||||
*/
|
||||
user?: string;
|
||||
/**
|
||||
* The path to the keyring file.
|
||||
* @default "/etc/ceph/user.secret"
|
||||
*/
|
||||
secretFile?: string;
|
||||
/**
|
||||
* Reference to Ceph authentication secrets. If provided, then the secret overrides `secretFile`
|
||||
*/
|
||||
secretRef?: SecretReference;
|
||||
/**
|
||||
* Whether the filesystem is used as readOnly.
|
||||
*/
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export interface CinderSource {
|
||||
volumeID: string;
|
||||
fsType: string;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
secretRef?: SecretReference;
|
||||
}
|
||||
|
||||
export interface ConfigMapSource {
|
||||
name: string;
|
||||
items: {
|
||||
key: string;
|
||||
path: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface DownwardApiSource {
|
||||
items: {
|
||||
path: string;
|
||||
fieldRef: {
|
||||
fieldPath: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface EphemeralSource {
|
||||
volumeClaimTemplate: {
|
||||
/**
|
||||
* All the rest of the fields are ignored and rejected during validation
|
||||
*/
|
||||
metadata?: Pick<KubeObjectMetadata, "labels" | "annotations">;
|
||||
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<string, string>;
|
||||
}
|
||||
|
||||
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<string, string>;
|
||||
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<PodVolumeVariants> & {
|
||||
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;
|
||||
|
||||
@ -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<string, string>;
|
||||
|
||||
@ -3,58 +3,5 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { JsonApi } from "./json-api";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
import { apiKubePrefix, apiPrefix, isDebugging, isDevelopment } from "../../common/vars";
|
||||
import { isClusterPageContext } from "../utils/cluster-id-url-parsing";
|
||||
import { appEventBus } from "../app-event-bus/event-bus";
|
||||
|
||||
let apiBase: JsonApi;
|
||||
let apiKube: KubeJsonApi;
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
appEventBus.addListener((event) => {
|
||||
if (event.name !== "lens-proxy" && event.action !== "listen") return;
|
||||
|
||||
const params = event.params as { port?: number };
|
||||
|
||||
if (!params.port) return;
|
||||
|
||||
apiBase = new JsonApi({
|
||||
serverAddress: `http://127.0.0.1:${params.port}`,
|
||||
apiBase: apiPrefix,
|
||||
debug: isDevelopment || isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `localhost:${params.port}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
apiBase = new JsonApi({
|
||||
serverAddress: `http://127.0.0.1:${window.location.port}`,
|
||||
apiBase: apiPrefix,
|
||||
debug: isDevelopment || isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": window.location.host,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isClusterPageContext()) {
|
||||
apiKube = new KubeJsonApi({
|
||||
serverAddress: `http://127.0.0.1:${window.location.port}`,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDevelopment,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": window.location.host,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
apiBase,
|
||||
apiKube,
|
||||
};
|
||||
export { apiBase } from "./api-base";
|
||||
export { apiKube } from "./api-kube";
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
|
||||
import { JsonApi, JsonApiData, JsonApiError } from "./json-api";
|
||||
import type { Response } from "node-fetch";
|
||||
import { LensProxy } from "../../main/lens-proxy";
|
||||
import { apiKubePrefix, isDebugging } from "../vars";
|
||||
import { apiBase } from "./api-base";
|
||||
|
||||
export interface KubeJsonApiListMetadata {
|
||||
resourceVersion: string;
|
||||
@ -57,15 +57,15 @@ export interface KubeJsonApiError extends JsonApiError {
|
||||
|
||||
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
|
||||
static forCluster(clusterId: string): KubeJsonApi {
|
||||
const port = LensProxy.getInstance().port;
|
||||
const url = new URL(apiBase.config.serverAddress);
|
||||
|
||||
return new this({
|
||||
serverAddress: `http://127.0.0.1:${port}`,
|
||||
serverAddress: apiBase.config.serverAddress,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `${clusterId}.localhost:${port}`,
|
||||
"Host": `${clusterId}.localhost:${url.port}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -21,6 +21,13 @@ export type KubeObjectConstructor<K extends KubeObject> = (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;
|
||||
|
||||
@ -65,6 +65,9 @@ if (ipcMain) {
|
||||
}
|
||||
|
||||
export default winston.createLogger({
|
||||
format: format.simple(),
|
||||
format: format.combine(
|
||||
format.splat(),
|
||||
format.simple(),
|
||||
),
|
||||
transports,
|
||||
});
|
||||
|
||||
@ -72,11 +72,34 @@ const resourcesDir = lazyInitialized(() => (
|
||||
: path.join(process.cwd(), "binaries", "client", normalizedPlatform)
|
||||
));
|
||||
|
||||
/**
|
||||
* @deprecated for being explicit side effect.
|
||||
*/
|
||||
export const baseBinariesDir = lazyInitialized(() => path.join(resourcesDir.get(), normalizedArch));
|
||||
|
||||
/**
|
||||
* @deprecated for being explicit side effect.
|
||||
*/
|
||||
export const kubeAuthProxyBinaryName = getBinaryName("lens-k8s-proxy");
|
||||
|
||||
/**
|
||||
* @deprecated for being explicit side effect.
|
||||
*/
|
||||
export const helmBinaryName = getBinaryName("helm");
|
||||
|
||||
/**
|
||||
* @deprecated for being explicit side effect.
|
||||
*/
|
||||
export const helmBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), helmBinaryName));
|
||||
|
||||
/**
|
||||
* @deprecated for being explicit side effect.
|
||||
*/
|
||||
export const kubectlBinaryName = getBinaryName("kubectl");
|
||||
|
||||
/**
|
||||
* @deprecated for being explicit side effect.
|
||||
*/
|
||||
export const kubectlBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), kubectlBinaryName));
|
||||
|
||||
// Webpack build paths
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
import { asLegacyGlobalForExtensionApi } from "./as-legacy-global-object-for-extension-api";
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
|
||||
/**
|
||||
* @deprecated use asLegacyGlobalForExtensionApi instead, and use proper implementations instead of "modifications".
|
||||
*/
|
||||
export const asLegacyGlobalObjectForExtensionApiWithModifications = <
|
||||
InjectableInstance extends InjectionTokenInstance,
|
||||
InjectionTokenInstance,
|
||||
|
||||
@ -20,6 +20,7 @@ jest.mock("winston", () => ({
|
||||
label: jest.fn(),
|
||||
timestamp: jest.fn(),
|
||||
printf: jest.fn(),
|
||||
splat: jest.fn(),
|
||||
},
|
||||
createLogger: jest.fn().mockReturnValue(logger),
|
||||
transports: {
|
||||
|
||||
@ -10,7 +10,6 @@ import mockFs from "mock-fs";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import kubeAuthProxyCaInjectable from "../kube-auth-proxy/kube-auth-proxy-ca.injectable";
|
||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
@ -83,7 +82,6 @@ describe("ContextHandler", () => {
|
||||
});
|
||||
|
||||
di.override(createKubeAuthProxyInjectable, () => ({} as any));
|
||||
di.override(kubeAuthProxyCaInjectable, () => ({} as any));
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ jest.mock("winston", () => ({
|
||||
printf: jest.fn(),
|
||||
padLevels: jest.fn(),
|
||||
ms: jest.fn(),
|
||||
splat: jest.fn(),
|
||||
},
|
||||
createLogger: jest.fn().mockReturnValue({
|
||||
silly: jest.fn(),
|
||||
@ -51,8 +52,6 @@ import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-p
|
||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||
import path from "path";
|
||||
import spawnInjectable from "../child-process/spawn.injectable";
|
||||
import kubeAuthProxyCaInjectable from "../kube-auth-proxy/kube-auth-proxy-ca.injectable";
|
||||
import createKubeAuthProxyCertFilesInjectable from "../kube-auth-proxy/create-kube-auth-proxy-cert-files.injectable";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
@ -96,8 +95,6 @@ describe("kube auth proxy tests", () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(spawnInjectable, () => mockSpawn);
|
||||
di.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
|
||||
di.override(kubeAuthProxyCaInjectable, () => ({} as any));
|
||||
|
||||
mockFs(mockMinikubeConfig);
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ jest.mock("winston", () => ({
|
||||
padLevels: jest.fn(),
|
||||
ms: jest.fn(),
|
||||
printf: jest.fn(),
|
||||
splat: jest.fn(),
|
||||
},
|
||||
createLogger: jest.fn().mockReturnValue(logger),
|
||||
transports: {
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
|
||||
import { autoUpdater, UpdateInfo } from "electron-updater";
|
||||
import logger from "./logger";
|
||||
import { isLinux, isMac, isPublishConfigured, isTestEnv } from "../common/vars";
|
||||
import { isPublishConfigured, isTestEnv } from "../common/vars";
|
||||
import { delay } from "../common/utils";
|
||||
import { areArgsUpdateAvailableToBackchannel, AutoUpdateChecking, AutoUpdateLogPrefix, AutoUpdateNoUpdateAvailable, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
||||
import { once } from "lodash";
|
||||
import { ipcMain, autoUpdater as electronAutoUpdater } from "electron";
|
||||
import { ipcMain } from "electron";
|
||||
import { nextUpdateChannel } from "./utils/update-channel";
|
||||
import { UserStore } from "../common/user-store";
|
||||
|
||||
@ -23,25 +23,7 @@ function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: Upd
|
||||
if (arg.doUpdate) {
|
||||
if (arg.now) {
|
||||
logger.info(`${AutoUpdateLogPrefix}: User chose to update now`);
|
||||
setImmediate(() => {
|
||||
if (isMac) {
|
||||
/**
|
||||
* This is a necessary workaround until electron-updater is fixed.
|
||||
* The problem is that it downloads it but then never tries to
|
||||
* download it from itself via electron.
|
||||
*/
|
||||
electronAutoUpdater.checkForUpdates();
|
||||
} else if (isLinux) {
|
||||
/**
|
||||
* This is a necessary workaround until electron-updater is fixed.
|
||||
* The problem is that because linux updating is not implemented at
|
||||
* all via electron. Electron's autoUpdater.quitAndInstall() is never
|
||||
* called.
|
||||
*/
|
||||
electronAutoUpdater.emit("before-quit-for-update");
|
||||
}
|
||||
autoUpdater.quitAndInstall(true, true);
|
||||
});
|
||||
autoUpdater.quitAndInstall(true, true);
|
||||
} else {
|
||||
logger.info(`${AutoUpdateLogPrefix}: User chose to update on quit`);
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
@ -52,10 +34,10 @@ function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: Upd
|
||||
}
|
||||
|
||||
autoUpdater.logger = {
|
||||
info: message => logger.info(`[AUTO-UPDATE]: electron-updater:`, message),
|
||||
warn: message => logger.warn(`[AUTO-UPDATE]: electron-updater:`, message),
|
||||
error: message => logger.error(`[AUTO-UPDATE]: electron-updater:`, message),
|
||||
debug: message => logger.debug(`[AUTO-UPDATE]: electron-updater:`, message),
|
||||
info: message => logger.info(`[AUTO-UPDATE]: electron-updater: %s`, message),
|
||||
warn: message => logger.warn(`[AUTO-UPDATE]: electron-updater: %s`, message),
|
||||
error: message => logger.error(`[AUTO-UPDATE]: electron-updater: %s`, message),
|
||||
debug: message => logger.debug(`[AUTO-UPDATE]: electron-updater: %s`, message),
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -14,11 +14,8 @@ import { ClusterManager } from "../../cluster-manager";
|
||||
import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
||||
import directoryForKubeConfigsInjectable
|
||||
from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import kubeAuthProxyCaInjectable from "../../kube-auth-proxy/kube-auth-proxy-ca.injectable";
|
||||
import createKubeAuthProxyCertFilesInjectable from "../../kube-auth-proxy/create-kube-auth-proxy-cert-files.injectable";
|
||||
|
||||
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@ -42,9 +39,6 @@ describe("kubeconfig-sync.source tests", () => {
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(kubeAuthProxyCaInjectable, () => Promise.resolve(Buffer.from("ca")));
|
||||
di.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
|
||||
|
||||
mockFs();
|
||||
|
||||
await di.runSetups();
|
||||
@ -62,6 +56,7 @@ describe("kubeconfig-sync.source tests", () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
ClusterManager.resetInstance();
|
||||
ClusterStore.resetInstance();
|
||||
});
|
||||
|
||||
describe("configsToModels", () => {
|
||||
|
||||
@ -28,7 +28,7 @@ interface PrometheusServicePreferences {
|
||||
|
||||
interface Dependencies {
|
||||
createKubeAuthProxy: CreateKubeAuthProxy;
|
||||
authProxyCa: Promise<Buffer>;
|
||||
authProxyCa: string;
|
||||
}
|
||||
|
||||
export class ContextHandler {
|
||||
@ -123,7 +123,7 @@ export class ContextHandler {
|
||||
return `https://127.0.0.1:${this.kubeAuthProxy.port}${this.kubeAuthProxy.apiPrefix}${path}`;
|
||||
}
|
||||
|
||||
async resolveAuthProxyCa() {
|
||||
resolveAuthProxyCa() {
|
||||
return this.dependencies.authProxyCa;
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ export class ContextHandler {
|
||||
protected async newApiTarget(timeout: number): Promise<httpProxy.ServerOptions> {
|
||||
await this.ensureServer();
|
||||
|
||||
const caFileContents = await this.resolveAuthProxyCa();
|
||||
const ca = this.dependencies.authProxyCa;
|
||||
const clusterPath = this.clusterUrl.path !== "/" ? this.clusterUrl.path : "";
|
||||
const apiPrefix = `${this.kubeAuthProxy.apiPrefix}${clusterPath}`;
|
||||
|
||||
@ -150,7 +150,7 @@ export class ContextHandler {
|
||||
host: "127.0.0.1",
|
||||
port: this.kubeAuthProxy.port,
|
||||
path: apiPrefix,
|
||||
ca: caFileContents.toString(),
|
||||
ca,
|
||||
},
|
||||
changeOrigin: true,
|
||||
timeout,
|
||||
|
||||
@ -3,23 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import selfsigned from "selfsigned";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import { ContextHandler } from "./context-handler";
|
||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||
import kubeAuthProxyCaInjectable from "../kube-auth-proxy/kube-auth-proxy-ca.injectable";
|
||||
import { getKubeAuthProxyCertificate } from "../kube-auth-proxy/get-kube-auth-proxy-certificate";
|
||||
import URLParse from "url-parse";
|
||||
|
||||
const createContextHandlerInjectable = getInjectable({
|
||||
id: "create-context-handler",
|
||||
|
||||
instantiate: (di) => {
|
||||
const authProxyCa = di.inject(kubeAuthProxyCaInjectable);
|
||||
return (cluster: Cluster) => {
|
||||
const clusterUrl = new URLParse(cluster.apiUrl);
|
||||
|
||||
const dependencies = {
|
||||
createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable),
|
||||
authProxyCa,
|
||||
const dependencies = {
|
||||
createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable),
|
||||
authProxyCa: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate).cert,
|
||||
};
|
||||
|
||||
return new ContextHandler(dependencies, cluster);
|
||||
};
|
||||
|
||||
return (cluster: Cluster) => new ContextHandler(dependencies, cluster);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import * as selfsigned from "selfsigned";
|
||||
import { createKubeAuthProxyCertFiles } from "./create-kube-auth-proxy-cert-files";
|
||||
import writeFileInjectable from "../../common/fs/write-file.injectable";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import path from "path";
|
||||
|
||||
const createKubeAuthProxyCertFilesInjectable = getInjectable({
|
||||
id: "create-kube-auth-proxy-cert-files",
|
||||
|
||||
instantiate: async (di) => {
|
||||
const userData = di.inject(directoryForUserDataInjectable);
|
||||
const certPath = path.join(userData, "kube-auth-proxy");
|
||||
|
||||
return createKubeAuthProxyCertFiles(certPath, {
|
||||
generate: selfsigned.generate,
|
||||
writeFile: di.inject(writeFileInjectable),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default createKubeAuthProxyCertFilesInjectable;
|
||||
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import type * as selfsigned from "selfsigned";
|
||||
|
||||
type SelfSignedGenerate = typeof selfsigned.generate;
|
||||
|
||||
interface CreateKubeAuthProxyCertificateFilesDependencies {
|
||||
generate: SelfSignedGenerate;
|
||||
writeFile: (path: string, content: string | Buffer) => Promise<void>;
|
||||
}
|
||||
|
||||
function getKubeAuthProxyCertificate(generate: SelfSignedGenerate): selfsigned.SelfSignedCert {
|
||||
const opts = [
|
||||
{ name: "commonName", value: "Lens Certificate Authority" },
|
||||
{ name: "organizationName", value: "Lens" },
|
||||
];
|
||||
|
||||
return generate(opts, {
|
||||
keySize: 2048,
|
||||
algorithm: "sha256",
|
||||
days: 365,
|
||||
extensions: [
|
||||
{ name: "basicConstraints", cA: true },
|
||||
{ name: "subjectAltName", altNames: [
|
||||
{ type: 2, value: "localhost" },
|
||||
{ type: 7, ip: "127.0.0.1" },
|
||||
] },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export async function createKubeAuthProxyCertFiles(dir: string, dependencies: CreateKubeAuthProxyCertificateFilesDependencies): Promise<string> {
|
||||
const cert = getKubeAuthProxyCertificate(dependencies.generate);
|
||||
|
||||
await dependencies.writeFile(path.join(dir, "proxy.key"), cert.private);
|
||||
await dependencies.writeFile(path.join(dir, "proxy.crt"), cert.cert);
|
||||
|
||||
return dir;
|
||||
}
|
||||
@ -6,10 +6,11 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { KubeAuthProxy, KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import path from "path";
|
||||
import selfsigned from "selfsigned";
|
||||
import { getBinaryName } from "../../common/vars";
|
||||
import directoryForBundledBinariesInjectable from "../../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable";
|
||||
import spawnInjectable from "../child-process/spawn.injectable";
|
||||
import createKubeAuthProxyCertFilesInjectable from "./create-kube-auth-proxy-cert-files.injectable";
|
||||
import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate";
|
||||
|
||||
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||
|
||||
@ -18,15 +19,17 @@ const createKubeAuthProxyInjectable = getInjectable({
|
||||
|
||||
instantiate: (di): CreateKubeAuthProxy => {
|
||||
const binaryName = getBinaryName("lens-k8s-proxy");
|
||||
const dependencies: KubeAuthProxyDependencies = {
|
||||
proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), binaryName),
|
||||
proxyCertPath: di.inject(createKubeAuthProxyCertFilesInjectable),
|
||||
spawn: di.inject(spawnInjectable),
|
||||
};
|
||||
|
||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => (
|
||||
new KubeAuthProxy(dependencies, cluster, environmentVariables)
|
||||
);
|
||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => {
|
||||
const clusterUrl = new URL(cluster.apiUrl);
|
||||
const dependencies: KubeAuthProxyDependencies = {
|
||||
proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), binaryName),
|
||||
proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate),
|
||||
spawn: di.inject(spawnInjectable),
|
||||
};
|
||||
|
||||
return new KubeAuthProxy(dependencies, cluster, environmentVariables);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
39
src/main/kube-auth-proxy/get-kube-auth-proxy-certificate.ts
Normal file
39
src/main/kube-auth-proxy/get-kube-auth-proxy-certificate.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type * as selfsigned from "selfsigned";
|
||||
|
||||
type SelfSignedGenerate = typeof selfsigned.generate;
|
||||
|
||||
const certCache: Map<string, selfsigned.SelfSignedCert> = new Map();
|
||||
|
||||
export function getKubeAuthProxyCertificate(hostname: string, generate: SelfSignedGenerate, useCache = true): selfsigned.SelfSignedCert {
|
||||
if (useCache && certCache.has(hostname)) {
|
||||
return certCache.get(hostname);
|
||||
}
|
||||
|
||||
const opts = [
|
||||
{ name: "commonName", value: "Lens Certificate Authority" },
|
||||
{ name: "organizationName", value: "Lens" },
|
||||
];
|
||||
|
||||
const cert = generate(opts, {
|
||||
keySize: 2048,
|
||||
algorithm: "sha256",
|
||||
days: 365,
|
||||
extensions: [
|
||||
{ name: "basicConstraints", cA: true },
|
||||
{ name: "subjectAltName", altNames: [
|
||||
{ type: 2, value: hostname },
|
||||
{ type: 2, value: "localhost" },
|
||||
{ type: 7, ip: "127.0.0.1" },
|
||||
] },
|
||||
],
|
||||
});
|
||||
|
||||
certCache.set(hostname, cert);
|
||||
|
||||
return cert;
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import readFileInjectable from "../../common/fs/read-file.injectable";
|
||||
import createKubeAuthProxyCertFilesInjectable from "./create-kube-auth-proxy-cert-files.injectable";
|
||||
|
||||
const kubeAuthProxyCaInjectable = getInjectable({
|
||||
id: "kube-auth-proxy-ca",
|
||||
|
||||
instantiate: async (di) => {
|
||||
const certPath = await di.inject(createKubeAuthProxyCertFilesInjectable);
|
||||
|
||||
const readFile = di.inject(readFileInjectable);
|
||||
|
||||
return readFile(path.join(certPath, "proxy.crt"));
|
||||
},
|
||||
});
|
||||
|
||||
export default kubeAuthProxyCaInjectable;
|
||||
@ -10,12 +10,13 @@ import type { Cluster } from "../../common/cluster/cluster";
|
||||
import logger from "../logger";
|
||||
import { getPortFrom } from "../utils/get-port";
|
||||
import { makeObservable, observable, when } from "mobx";
|
||||
import type { SelfSignedCert } from "selfsigned";
|
||||
|
||||
const startingServeRegex = /starting to serve on (?<address>.+)/i;
|
||||
|
||||
export interface KubeAuthProxyDependencies {
|
||||
proxyBinPath: string;
|
||||
proxyCertPath: Promise<string>;
|
||||
proxyCert: SelfSignedCert;
|
||||
spawn: typeof spawn;
|
||||
}
|
||||
|
||||
@ -44,7 +45,7 @@ export class KubeAuthProxy {
|
||||
}
|
||||
|
||||
const proxyBin = this.dependencies.proxyBinPath;
|
||||
const certPath = await this.dependencies.proxyCertPath;
|
||||
const cert = this.dependencies.proxyCert;
|
||||
|
||||
this.proxyProcess = this.dependencies.spawn(proxyBin, [], {
|
||||
env: {
|
||||
@ -52,7 +53,8 @@ export class KubeAuthProxy {
|
||||
KUBECONFIG: this.cluster.kubeConfigPath,
|
||||
KUBECONFIG_CONTEXT: this.cluster.contextName,
|
||||
API_PREFIX: this.apiPrefix,
|
||||
CERT_PATH: certPath,
|
||||
PROXY_KEY: cert.private,
|
||||
PROXY_CERT: cert.cert,
|
||||
},
|
||||
});
|
||||
this.proxyProcess.on("error", (error) => {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type net from "net";
|
||||
import net from "net";
|
||||
import type http from "http";
|
||||
import spdy from "spdy";
|
||||
import type httpProxy from "http-proxy";
|
||||
@ -79,19 +79,18 @@ export class LensProxy extends Singleton {
|
||||
|
||||
this.proxyServer
|
||||
.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
|
||||
const isInternal = req.url.startsWith(`${apiPrefix}?`);
|
||||
const cluster = getClusterForRequest(req);
|
||||
|
||||
if (!cluster) {
|
||||
logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
|
||||
socket.destroy();
|
||||
} else {
|
||||
const isInternal = req.url.startsWith(`${apiPrefix}?`);
|
||||
const reqHandler = isInternal ? shellApiRequest : kubeApiUpgradeRequest;
|
||||
|
||||
return socket.destroy();
|
||||
(async () => reqHandler({ req, socket, head, cluster }))()
|
||||
.catch(error => logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error));
|
||||
}
|
||||
|
||||
const reqHandler = isInternal ? shellApiRequest : kubeApiUpgradeRequest;
|
||||
|
||||
(async () => reqHandler({ req, socket, head, cluster }))()
|
||||
.catch(error => logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error));
|
||||
});
|
||||
}
|
||||
|
||||
@ -189,7 +188,7 @@ export class LensProxy extends Singleton {
|
||||
});
|
||||
|
||||
proxy.on("error", (error, req, res, target) => {
|
||||
if (this.closed) {
|
||||
if (this.closed || res instanceof net.Socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,8 @@ import { UserStore } from "../../../common/user-store";
|
||||
import mockFs from "mock-fs";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||
|
||||
mockWindow();
|
||||
jest.mock("electron", () => ({
|
||||
@ -102,6 +104,7 @@ describe("<Catalog />", () => {
|
||||
let di: DiContainer;
|
||||
let catalogEntityStore: CatalogEntityStore;
|
||||
let catalogEntityRegistry: CatalogEntityRegistry;
|
||||
let emitEvent: (event: AppEvent) => void;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -125,6 +128,12 @@ describe("<Catalog />", () => {
|
||||
|
||||
di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry);
|
||||
|
||||
emitEvent = jest.fn();
|
||||
|
||||
di.override(appEventBusInjectable, () => ({
|
||||
emit: emitEvent,
|
||||
}));
|
||||
|
||||
catalogEntityStore = di.inject(catalogEntityStoreInjectable);
|
||||
});
|
||||
|
||||
@ -320,4 +329,39 @@ describe("<Catalog />", () => {
|
||||
|
||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
||||
});
|
||||
|
||||
it("emits catalog open AppEvent", () => {
|
||||
render(
|
||||
<Catalog
|
||||
history={history}
|
||||
location={mockLocation}
|
||||
match={mockMatch}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(emitEvent).toHaveBeenCalledWith( {
|
||||
action: "open",
|
||||
name: "catalog",
|
||||
});
|
||||
});
|
||||
|
||||
it("emits catalog change AppEvent when changing the category", () => {
|
||||
render(
|
||||
<Catalog
|
||||
history={history}
|
||||
location={mockLocation}
|
||||
match={mockMatch}
|
||||
/>,
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByText("Web Links"));
|
||||
|
||||
expect(emitEvent).toHaveBeenLastCalledWith({
|
||||
action: "change-category",
|
||||
name: "catalog",
|
||||
params: {
|
||||
category: "Web Links",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -33,6 +33,8 @@ import getCategoryColumnsInjectable from "./get-category-columns.injectable";
|
||||
import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
|
||||
import customCategoryViewsInjectable from "./custom-views.injectable";
|
||||
import type { CustomCategoryViewComponents } from "./custom-views";
|
||||
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
|
||||
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
|
||||
|
||||
export interface CatalogProps extends RouteComponentProps<CatalogViewRouteParam> {}
|
||||
|
||||
@ -41,6 +43,7 @@ interface Dependencies {
|
||||
catalogEntityStore: CatalogEntityStore;
|
||||
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
|
||||
customCategoryViews: IComputedValue<Map<string, Map<string, RegisteredCustomCategoryViewDecl>>>;
|
||||
emitEvent: (event: AppEvent) => void;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -101,6 +104,11 @@ class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
this.props.emitEvent({
|
||||
name: "catalog",
|
||||
action: "open",
|
||||
});
|
||||
}
|
||||
|
||||
addToHotbar(entity: CatalogEntity): void {
|
||||
@ -143,6 +151,14 @@ class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
|
||||
onTabChange = action((tabId: string | null) => {
|
||||
const activeCategory = this.categories.find(category => category.getId() === tabId);
|
||||
|
||||
this.props.emitEvent({
|
||||
name: "catalog",
|
||||
action: "change-category",
|
||||
params: {
|
||||
category: activeCategory ? activeCategory.getName() : "Browse",
|
||||
},
|
||||
});
|
||||
|
||||
if (activeCategory) {
|
||||
navigate(catalogURL({ params: { group: activeCategory.spec.group, kind: activeCategory.spec.names.kind }}));
|
||||
} else {
|
||||
@ -280,6 +296,7 @@ export const Catalog = withInjectables<Dependencies, CatalogProps>( NonInjectedC
|
||||
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
|
||||
getCategoryColumns: di.inject(getCategoryColumnsInjectable),
|
||||
customCategoryViews: di.inject(customCategoryViewsInjectable),
|
||||
emitEvent: di.inject(appEventBusInjectable).emit,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -134,7 +134,7 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
|
||||
})}
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerTitle title="Metrics"/>
|
||||
<DrawerTitle>Metrics</DrawerTitle>
|
||||
<div className="metrics">
|
||||
{this.renderMetrics()}
|
||||
</div>
|
||||
|
||||
@ -85,7 +85,7 @@ export class ConfigMapDetails extends React.Component<ConfigMapDetailsProps> {
|
||||
{
|
||||
data.length > 0 && (
|
||||
<>
|
||||
<DrawerTitle title="Data"/>
|
||||
<DrawerTitle>Data</DrawerTitle>
|
||||
{
|
||||
data.map(([name, value]) => (
|
||||
<div key={name} className="data">
|
||||
|
||||
@ -82,7 +82,7 @@ export class ResourceQuotaDetails extends React.Component<ResourceQuotaDetailsPr
|
||||
|
||||
{quota.getScopeSelector().length > 0 && (
|
||||
<>
|
||||
<DrawerTitle title="Scope Selector"/>
|
||||
<DrawerTitle>Scope Selector</DrawerTitle>
|
||||
<Table className="paths">
|
||||
<TableHead>
|
||||
<TableCell>Operator</TableCell>
|
||||
|
||||
@ -116,7 +116,7 @@ export class SecretDetails extends React.Component<SecretDetailsProps> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerTitle title="Data" />
|
||||
<DrawerTitle>Data</DrawerTitle>
|
||||
{secrets.map(this.renderSecret)}
|
||||
<Button
|
||||
primary
|
||||
|
||||
@ -92,7 +92,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
|
||||
})
|
||||
}
|
||||
</DrawerItem>
|
||||
<DrawerTitle title="Names"/>
|
||||
<DrawerTitle>Names</DrawerTitle>
|
||||
<Table selectable className="names box grow">
|
||||
<TableHead>
|
||||
<TableCell>plural</TableCell>
|
||||
@ -109,7 +109,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
|
||||
</Table>
|
||||
{printerColumns.length > 0 &&
|
||||
<>
|
||||
<DrawerTitle title="Additional Printer Columns"/>
|
||||
<DrawerTitle>Additional Printer Columns</DrawerTitle>
|
||||
<Table selectable className="printer-columns box grow">
|
||||
<TableHead>
|
||||
<TableCell className="name">Name</TableCell>
|
||||
@ -136,7 +136,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
|
||||
}
|
||||
{validation &&
|
||||
<>
|
||||
<DrawerTitle title="Validation"/>
|
||||
<DrawerTitle>Validation</DrawerTitle>
|
||||
<MonacoEditor
|
||||
readOnly
|
||||
value={validation}
|
||||
|
||||
@ -71,7 +71,7 @@ export class EventDetails extends React.Component<EventDetailsProps> {
|
||||
<span className={kebabCase(type)}>{type}</span>
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerTitle title="Involved object"/>
|
||||
<DrawerTitle>Involved object</DrawerTitle>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableCell>Name</TableCell>
|
||||
|
||||
@ -113,7 +113,7 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
||||
|
||||
return (
|
||||
<div className="values">
|
||||
<DrawerTitle title="Values" />
|
||||
<DrawerTitle>Values</DrawerTitle>
|
||||
<div className="flex column gaps">
|
||||
<Checkbox
|
||||
label="User-supplied values only"
|
||||
@ -238,9 +238,9 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
||||
/>
|
||||
</DrawerItem>
|
||||
{this.renderValues()}
|
||||
<DrawerTitle title="Notes"/>
|
||||
<DrawerTitle>Notes</DrawerTitle>
|
||||
{this.renderNotes()}
|
||||
<DrawerTitle title="Resources"/>
|
||||
<DrawerTitle>Resources</DrawerTitle>
|
||||
{resources && this.renderResources(resources)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -35,7 +35,7 @@ export class EndpointDetails extends React.Component<EndpointDetailsProps> {
|
||||
return (
|
||||
<div className="EndpointDetails">
|
||||
<KubeObjectMeta object={endpoint}/>
|
||||
<DrawerTitle title="Subsets"/>
|
||||
<DrawerTitle>Subsets</DrawerTitle>
|
||||
{
|
||||
endpoint.getEndpointSubsets().map((subset) => (
|
||||
<EndpointSubsetList key={subset.toString()} subset={subset} endpoint={endpoint} />
|
||||
|
||||
@ -160,10 +160,10 @@ export class IngressDetails extends React.Component<IngressDetailsProps> {
|
||||
{serviceName}:{servicePort}
|
||||
</DrawerItem>
|
||||
}
|
||||
<DrawerTitle title="Rules"/>
|
||||
<DrawerTitle>Rules</DrawerTitle>
|
||||
{this.renderPaths(ingress)}
|
||||
|
||||
<DrawerTitle title="Load-Balancer Ingress Points"/>
|
||||
<DrawerTitle>Load-Balancer Ingress Points</DrawerTitle>
|
||||
{this.renderIngressPoints(ingressPoints)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -170,7 +170,7 @@ export class NetworkPolicyDetails extends React.Component<NetworkPolicyDetailsPr
|
||||
|
||||
{ingress && (
|
||||
<>
|
||||
<DrawerTitle title="Ingress"/>
|
||||
<DrawerTitle>Ingress</DrawerTitle>
|
||||
{ingress.map((ingress, i) => (
|
||||
<div key={i} data-testid={`ingress-${i}`}>
|
||||
{this.renderNetworkPolicyPorts(ingress.ports)}
|
||||
@ -182,7 +182,7 @@ export class NetworkPolicyDetails extends React.Component<NetworkPolicyDetailsPr
|
||||
|
||||
{egress && (
|
||||
<>
|
||||
<DrawerTitle title="Egress"/>
|
||||
<DrawerTitle>Egress</DrawerTitle>
|
||||
{egress.map((egress, i) => (
|
||||
<div key={i} data-testid={`egress-${i}`}>
|
||||
{this.renderNetworkPolicyPorts(egress.ports)}
|
||||
|
||||
@ -86,7 +86,7 @@ class NonInjectedServiceDetails extends React.Component<ServiceDetailsProps & De
|
||||
{spec.sessionAffinity}
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerTitle title="Connection"/>
|
||||
<DrawerTitle>Connection</DrawerTitle>
|
||||
|
||||
<DrawerItem name="Cluster IP">
|
||||
{spec.clusterIP}
|
||||
@ -129,7 +129,7 @@ class NonInjectedServiceDetails extends React.Component<ServiceDetailsProps & De
|
||||
{spec.loadBalancerIP}
|
||||
</DrawerItem>
|
||||
)}
|
||||
<DrawerTitle title="Endpoint"/>
|
||||
<DrawerTitle>Endpoint</DrawerTitle>
|
||||
|
||||
<ServiceDetailsEndpoint endpoint={endpoint}/>
|
||||
</div>
|
||||
|
||||
@ -169,9 +169,9 @@ class NonInjectedNodeDetails extends React.Component<NodeDetailsProps & Dependen
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
<DrawerTitle title="Capacity"/>
|
||||
<DrawerTitle>Capacity</DrawerTitle>
|
||||
<NodeDetailsResources node={node} type={"capacity"}/>
|
||||
<DrawerTitle title="Allocatable"/>
|
||||
<DrawerTitle>Allocatable</DrawerTitle>
|
||||
<NodeDetailsResources node={node} type={"allocatable"}/>
|
||||
<PodDetailsList
|
||||
pods={childPods}
|
||||
|
||||
@ -28,26 +28,23 @@ interface RuleGroup {
|
||||
|
||||
@observer
|
||||
export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyDetailsProps> {
|
||||
renderRuleGroup( title: React.ReactNode, group: RuleGroup) {
|
||||
if (!group) return null;
|
||||
const { rule, ranges } = group;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerTitle title={title}/>
|
||||
<DrawerItem name="Rule">
|
||||
{rule}
|
||||
renderRuleGroup = (title: React.ReactNode, { rule, ranges }: RuleGroup) => (
|
||||
<>
|
||||
<DrawerTitle>{title}</DrawerTitle>
|
||||
<DrawerItem name="Rule">
|
||||
{rule}
|
||||
</DrawerItem>
|
||||
{ranges && (
|
||||
<DrawerItem name="Ranges (Min-Max)" labelsOnly>
|
||||
{ranges.map(({ min, max }, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
label={`${min} - ${max}`} />
|
||||
))}
|
||||
</DrawerItem>
|
||||
{ranges && (
|
||||
<DrawerItem name="Ranges (Min-Max)" labelsOnly>
|
||||
{ranges.map(({ min, max }, index) => {
|
||||
return <Badge key={index} label={`${min} - ${max}`}/>;
|
||||
})}
|
||||
</DrawerItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
render() {
|
||||
const { object: psp } = this.props;
|
||||
@ -161,7 +158,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
|
||||
|
||||
{allowedHostPaths && (
|
||||
<>
|
||||
<DrawerTitle title="Allowed Host Paths"/>
|
||||
<DrawerTitle>Allowed Host Paths</DrawerTitle>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableCell>Path Prefix</TableCell>
|
||||
@ -179,14 +176,14 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
|
||||
</>
|
||||
)}
|
||||
|
||||
{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 && (
|
||||
<>
|
||||
<DrawerTitle title="Runtime Class"/>
|
||||
<DrawerTitle>Runtime Class</DrawerTitle>
|
||||
<DrawerItem name="Allowed Runtime Class Names">
|
||||
{runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"}
|
||||
</DrawerItem>
|
||||
@ -198,7 +195,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
|
||||
|
||||
{seLinux && (
|
||||
<>
|
||||
<DrawerTitle title="Se Linux"/>
|
||||
<DrawerTitle>Se Linux</DrawerTitle>
|
||||
<DrawerItem name="Rule">
|
||||
{seLinux.rule}
|
||||
</DrawerItem>
|
||||
|
||||
@ -80,7 +80,7 @@ class NonInjectedStorageClassDetails extends React.Component<StorageClassDetails
|
||||
)}
|
||||
{parameters && (
|
||||
<>
|
||||
<DrawerTitle title="Parameters"/>
|
||||
<DrawerTitle>Parameters</DrawerTitle>
|
||||
{
|
||||
Object.entries(parameters).map(([name, value]) => (
|
||||
<DrawerItem key={name + value} name={startCase(name)}>
|
||||
|
||||
@ -102,7 +102,7 @@ export class PersistentVolumeClaimDetails extends React.Component<PersistentVolu
|
||||
{volumeClaim.getStatus()}
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerTitle title="Selector"/>
|
||||
<DrawerTitle>Selector</DrawerTitle>
|
||||
|
||||
<DrawerItem name="Match Labels" labelsOnly>
|
||||
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)}
|
||||
|
||||
@ -67,7 +67,7 @@ export class VolumeDetailsList extends React.Component<VolumeDetailsListProps> {
|
||||
|
||||
return (
|
||||
<div className="VolumeDetailsList flex column">
|
||||
<DrawerTitle title="Persistent Volumes"/>
|
||||
<DrawerTitle>Persistent Volumes</DrawerTitle>
|
||||
<Table
|
||||
tableId="storage_volume_details_list"
|
||||
items={persistentVolumes}
|
||||
|
||||
@ -65,7 +65,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
|
||||
|
||||
{nfs && (
|
||||
<>
|
||||
<DrawerTitle title="Network File System"/>
|
||||
<DrawerTitle>Network File System</DrawerTitle>
|
||||
{
|
||||
Object.entries(nfs).map(([name, value]) => (
|
||||
<DrawerItem key={name} name={startCase(name)}>
|
||||
@ -78,7 +78,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
|
||||
|
||||
{flexVolume && (
|
||||
<>
|
||||
<DrawerTitle title="FlexVolume"/>
|
||||
<DrawerTitle>FlexVolume</DrawerTitle>
|
||||
<DrawerItem name="Driver">
|
||||
{flexVolume.driver}
|
||||
</DrawerItem>
|
||||
@ -94,7 +94,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
|
||||
|
||||
{claimRef && (
|
||||
<>
|
||||
<DrawerTitle title="Claim"/>
|
||||
<DrawerTitle>Claim</DrawerTitle>
|
||||
<DrawerItem name="Type">
|
||||
{claimRef.kind}
|
||||
</DrawerItem>
|
||||
|
||||
@ -68,7 +68,7 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
|
||||
<div className="ClusterRoleBindingDetails">
|
||||
<KubeObjectMeta object={clusterRoleBinding} />
|
||||
|
||||
<DrawerTitle title="Reference" />
|
||||
<DrawerTitle>Reference</DrawerTitle>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableCell>Kind</TableCell>
|
||||
@ -82,7 +82,7 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
|
||||
</TableRow>
|
||||
</Table>
|
||||
|
||||
<DrawerTitle title="Bindings" />
|
||||
<DrawerTitle>Bindings</DrawerTitle>
|
||||
{subjects.length > 0 && (
|
||||
<Table selectable className="bindings box grow">
|
||||
<TableHead>
|
||||
|
||||
@ -28,7 +28,7 @@ export class ClusterRoleDetails extends React.Component<ClusterRoleDetailsProps>
|
||||
<div className="ClusterRoleDetails">
|
||||
<KubeObjectMeta object={clusterRole}/>
|
||||
|
||||
<DrawerTitle title="Rules"/>
|
||||
<DrawerTitle>Rules</DrawerTitle>
|
||||
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
|
||||
return (
|
||||
<div className="rule" key={index}>
|
||||
|
||||
@ -64,7 +64,7 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
|
||||
<div className="RoleBindingDetails">
|
||||
<KubeObjectMeta object={roleBinding} />
|
||||
|
||||
<DrawerTitle title="Reference" />
|
||||
<DrawerTitle>Reference</DrawerTitle>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableCell>Kind</TableCell>
|
||||
@ -78,7 +78,7 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
|
||||
</TableRow>
|
||||
</Table>
|
||||
|
||||
<DrawerTitle title="Bindings" />
|
||||
<DrawerTitle>Bindings</DrawerTitle>
|
||||
{subjects.length > 0 && (
|
||||
<Table selectable className="bindings box grow">
|
||||
<TableHead>
|
||||
|
||||
@ -27,7 +27,7 @@ export class RoleDetails extends React.Component<RoleDetailsProps> {
|
||||
return (
|
||||
<div className="RoleDetails">
|
||||
<KubeObjectMeta object={role}/>
|
||||
<DrawerTitle title="Rules"/>
|
||||
<DrawerTitle>Rules</DrawerTitle>
|
||||
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
|
||||
return (
|
||||
<div className="rule" key={index}>
|
||||
|
||||
@ -143,7 +143,7 @@ export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetai
|
||||
</DrawerItem>
|
||||
}
|
||||
|
||||
<DrawerTitle title="Mountable secrets"/>
|
||||
<DrawerTitle>Mountable secrets</DrawerTitle>
|
||||
<div className="secrets">
|
||||
{this.renderSecrets()}
|
||||
</div>
|
||||
|
||||
@ -78,7 +78,7 @@ class NonInjectedCronJobDetails extends React.Component<CronJobDetailsProps & De
|
||||
</DrawerItem>
|
||||
{childJobs.length > 0 &&
|
||||
<>
|
||||
<DrawerTitle title="Jobs"/>
|
||||
<DrawerTitle>Jobs</DrawerTitle>
|
||||
{childJobs.map((job: Job) => {
|
||||
const selectors = job.getSelectors();
|
||||
const condition = job.getCondition();
|
||||
|
||||
@ -53,7 +53,7 @@ export class DeploymentReplicaSets extends React.Component<DeploymentReplicaSets
|
||||
|
||||
return (
|
||||
<div className="ReplicaSets flex column">
|
||||
<DrawerTitle title="Deploy Revisions"/>
|
||||
<DrawerTitle>Deploy Revisions</DrawerTitle>
|
||||
<Table
|
||||
selectable
|
||||
tableId="deployment_replica_sets_view"
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import type { PodVolumeVariants, Pod, SecretReference } from "../../../../../common/k8s-api/endpoints";
|
||||
import type { KubeApi } from "../../../../../common/k8s-api/kube-api";
|
||||
import type { LocalObjectReference, KubeObject } from "../../../../../common/k8s-api/kube-object";
|
||||
import { DrawerItem } from "../../../drawer";
|
||||
import { getDetailsUrl } from "../../../kube-detail-params";
|
||||
|
||||
export interface PodVolumeVariantSpecificProps<Kind extends keyof PodVolumeVariants> {
|
||||
variant: PodVolumeVariants[Kind];
|
||||
pod: Pod;
|
||||
volumeName: string;
|
||||
}
|
||||
|
||||
export type VolumeVariantComponent<Kind extends keyof PodVolumeVariants> = React.FunctionComponent<PodVolumeVariantSpecificProps<Kind>>;
|
||||
|
||||
export interface LocalRefProps {
|
||||
pod: Pod;
|
||||
title: string;
|
||||
kubeRef: LocalObjectReference | SecretReference | undefined;
|
||||
api: KubeApi<KubeObject>;
|
||||
}
|
||||
|
||||
export const LocalRef = ({ pod, title, kubeRef: ref, api }: LocalRefProps) => {
|
||||
if (!ref) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DrawerItem name={title}>
|
||||
<Link to={getDetailsUrl(api.getUrl({ namespace: pod.getNs(), ...ref }))}>
|
||||
{ref.name}
|
||||
</Link>
|
||||
</DrawerItem>
|
||||
);
|
||||
};
|
||||
@ -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<PodVolumeKind>([
|
||||
"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: <AwsElasticBlockStore variant={volume.awsElasticBlockStore} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.azureDisk) {
|
||||
return {
|
||||
kind: "azureDisk",
|
||||
element: <AzureDisk variant={volume.azureDisk} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.azureFile) {
|
||||
return {
|
||||
kind: "azureFile",
|
||||
element: <AzureFile variant={volume.azureFile} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.cephfs) {
|
||||
return {
|
||||
kind: "cephfs",
|
||||
element: <CephFs variant={volume.cephfs} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.cinder) {
|
||||
return {
|
||||
kind: "cinder",
|
||||
element: <Cinder variant={volume.cinder} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.configMap) {
|
||||
return {
|
||||
kind: "configMap",
|
||||
element: <ConfigMap variant={volume.configMap} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.csi) {
|
||||
return {
|
||||
kind: "csi",
|
||||
element: <ContainerStorageInterface variant={volume.csi} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.downwardAPI) {
|
||||
return {
|
||||
kind: "downwardAPI",
|
||||
element: <DownwardAPI variant={volume.downwardAPI} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.emptyDir) {
|
||||
return {
|
||||
kind: "emptyDir",
|
||||
element: <EmptyDir variant={volume.emptyDir} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.ephemeral) {
|
||||
return {
|
||||
kind: "ephemeral",
|
||||
element: <Ephemeral variant={volume.ephemeral} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.fc) {
|
||||
return {
|
||||
kind: "fc",
|
||||
element: <FiberChannel variant={volume.fc} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.flexVolume) {
|
||||
return {
|
||||
kind: "flexVolume",
|
||||
element: <FlexVolume variant={volume.flexVolume} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.flocker) {
|
||||
return {
|
||||
kind: "flocker",
|
||||
element: <Flocker variant={volume.flocker} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.gcePersistentDisk) {
|
||||
return {
|
||||
kind: "gcePersistentDisk",
|
||||
element: <GcePersistentDisk variant={volume.gcePersistentDisk} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.gitRepo) {
|
||||
return {
|
||||
kind: "gitRepo",
|
||||
element: <GitRepo variant={volume.gitRepo} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.glusterfs) {
|
||||
return {
|
||||
kind: "glusterfs",
|
||||
element: <GlusterFs variant={volume.glusterfs} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.hostPath) {
|
||||
return {
|
||||
kind: "hostPath",
|
||||
element: <HostPath variant={volume.hostPath} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.iscsi) {
|
||||
return {
|
||||
kind: "iscsi",
|
||||
element: <IScsi variant={volume.iscsi} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.local) {
|
||||
return {
|
||||
kind: "local",
|
||||
element: <Local variant={volume.local} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.nfs) {
|
||||
return {
|
||||
kind: "nfs",
|
||||
element: <NetworkFs variant={volume.nfs} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.persistentVolumeClaim) {
|
||||
return {
|
||||
kind: "persistentVolumeClaim",
|
||||
element: <PersistentVolumeClaim variant={volume.persistentVolumeClaim} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.photonPersistentDisk) {
|
||||
return {
|
||||
kind: "photonPersistentDisk",
|
||||
element: <PhotonPersistentDisk variant={volume.photonPersistentDisk} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.portworxVolume) {
|
||||
return {
|
||||
kind: "portworxVolume",
|
||||
element: <PortworxVolume variant={volume.portworxVolume} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.projected) {
|
||||
return {
|
||||
kind: "projected",
|
||||
element: <Projected variant={volume.projected} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.quobyte) {
|
||||
return {
|
||||
kind: "quobyte",
|
||||
element: <Quobyte variant={volume.quobyte} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.rbd) {
|
||||
return {
|
||||
kind: "rbd",
|
||||
element: <RadosBlockDevice variant={volume.rbd} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.scaleIO) {
|
||||
return {
|
||||
kind: "scaleIO",
|
||||
element: <ScaleIo variant={volume.scaleIO} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.secret) {
|
||||
return {
|
||||
kind: "secret",
|
||||
element: <Secret variant={volume.secret} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.storageos) {
|
||||
return {
|
||||
kind: "storageos",
|
||||
element: <StorageOs variant={volume.storageos} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
if (volume.vsphereVolume) {
|
||||
return {
|
||||
kind: "vsphereVolume",
|
||||
element: <VsphereVolume variant={volume.vsphereVolume} pod={pod} volumeName={volume.name} />,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function VolumeVariant(props: VolumeVariantProps) {
|
||||
const result = renderVolumeVariant(props);
|
||||
|
||||
if (!result) {
|
||||
return <p>Error! Unknown pod volume kind</p>;
|
||||
}
|
||||
|
||||
const { kind, element } = result;
|
||||
const isDeprecated = deprecatedVolumeTypes.has(kind);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerItem name="Kind">
|
||||
{kind}
|
||||
{isDeprecated && <Icon title="Deprecated" material="warning_amber" />}
|
||||
</DrawerItem>
|
||||
{element}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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" }}) => (
|
||||
<>
|
||||
<DrawerItem name="Volume ID">
|
||||
{volumeID}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name={kind === "Managed" ? "Disk Name" : "VHD blob Name"}>
|
||||
{diskName}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={kind === "Managed" ? "Resource ID" : "Disk URI"}>
|
||||
{diskURI}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Kind">
|
||||
{kind}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Caching Mode">
|
||||
{cachingMode}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readonly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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" }}) => (
|
||||
<>
|
||||
<DrawerItem name="Secret Name">
|
||||
{secretName}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Share Name">
|
||||
{shareName}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Namespace of Secret">
|
||||
{secretNamespace}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Monitors">
|
||||
<ul>
|
||||
{monitors.map(monitor => <li key={monitor}>{monitor}</li>)}
|
||||
</ul>
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Mount Path">
|
||||
{path}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Username">
|
||||
{user}
|
||||
</DrawerItem>
|
||||
{
|
||||
secretRef
|
||||
? (
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Secret"
|
||||
kubeRef={secretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<DrawerItem name="Secret Filepath">
|
||||
{secretFile}
|
||||
</DrawerItem>
|
||||
)
|
||||
}
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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" }}) => (
|
||||
<>
|
||||
<DrawerItem name="Volume ID">
|
||||
{volumeID}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Name"
|
||||
kubeRef={{ name }}
|
||||
api={configMapApi}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@ -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,
|
||||
},
|
||||
}) => (
|
||||
<>
|
||||
<DrawerItem name="Driver">
|
||||
{driver}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="ReadOnly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Controller Publish Secret"
|
||||
kubeRef={controllerPublishSecretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Controller Expand Secret"
|
||||
kubeRef={controllerExpandSecretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Node Publish Secret"
|
||||
kubeRef={nodePublishSecretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Node Stage Secret"
|
||||
kubeRef={nodeStageSecretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
{
|
||||
Object.entries(volumeAttributes)
|
||||
.map(([key, value]) => (
|
||||
<DrawerItem key={key} name={key}>
|
||||
{value}
|
||||
</DrawerItem>
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Items">
|
||||
<ul>
|
||||
{items.map(item => <li key={item.path}>{item.path}</li>)}
|
||||
</ul>
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Medium">
|
||||
{medium || "<node's default medium>"}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Size Limit" hidden={!sizeLimit}>
|
||||
{sizeLimit}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}}) => (
|
||||
<>
|
||||
<DrawerItem name="PVC Template Name">
|
||||
{pod.getName()}-{volumeName}
|
||||
</DrawerItem>
|
||||
<DrawerItemLabels
|
||||
name="Template Labels"
|
||||
labels={metadata.labels}
|
||||
/>
|
||||
<DrawerItemLabels
|
||||
name="Template Annotations"
|
||||
labels={metadata.annotations}
|
||||
/>
|
||||
<DrawerItem name="Template PVC Spec">
|
||||
{dump(spec)}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Target World Wide Names">
|
||||
<ul>
|
||||
{targetWWNs.map(targetWWN => <li key={targetWWN}>{targetWWN}</li>)}
|
||||
</ul>
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Logical Unit Number">
|
||||
{lun.toString()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Driver">
|
||||
{driver}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType || "-- system default --"}
|
||||
</DrawerItem>
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Secret"
|
||||
kubeRef={secretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
{
|
||||
...Object.entries(options)
|
||||
.map(([key, value]) => (
|
||||
<DrawerItem key={key} name={`Option: ${key}`}>
|
||||
{value}
|
||||
</DrawerItem>
|
||||
))
|
||||
}
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Dataset Name">
|
||||
{datasetName}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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" }}) => (
|
||||
<>
|
||||
<DrawerItem name="Persistent Disk Name">
|
||||
{pdName}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Repository URL">
|
||||
{repository}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Commit Hash">
|
||||
{revision}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Endpoints object name">
|
||||
{endpoints}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Glusterfs volume name">
|
||||
{path}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly Mountpoint">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Node's Host Filesystem Path">
|
||||
{path}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Check Behaviour">
|
||||
{type || "-- none --"}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Target Address">
|
||||
{targetPortal}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="iSCSI qualified name">
|
||||
{iqn}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Logical Unit Number">
|
||||
{lun.toString()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
{chapAuthDiscovery && (
|
||||
<DrawerItem name="CHAP Discovery Authentication">
|
||||
{chapAuthDiscovery.toString()}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{chapAuthSession && (
|
||||
<DrawerItem name="CHAP Session Authentication">
|
||||
{chapAuthSession.toString()}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{ secretRef && (
|
||||
<DrawerItem name="CHAP Secret">
|
||||
{secretRef.name}
|
||||
</DrawerItem>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Path">
|
||||
{path}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Server">
|
||||
{server}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Path">
|
||||
{path}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Name"
|
||||
kubeRef={{ name: claimName }}
|
||||
api={pvcApi}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@ -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" }}) => (
|
||||
<>
|
||||
<DrawerItem name="Persistent Disk ID">
|
||||
{pdID}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Volume ID">
|
||||
{volumeID}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Default Mount Mode">
|
||||
0o{defaultMode.toString(8)}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Sources">
|
||||
{
|
||||
sources.map(({ secret, downwardAPI, configMap, serviceAccountToken }, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{secret && (
|
||||
<>
|
||||
<DrawerTitle size="sub-title">Secret</DrawerTitle>
|
||||
<DrawerItem name="Name">
|
||||
{secret.name}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Items">
|
||||
<ul>
|
||||
{secret.items?.map(({ key, path }) => (
|
||||
<li key={key}>
|
||||
{key} ⇢ {path}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</DrawerItem>
|
||||
</>
|
||||
)}
|
||||
{downwardAPI && (
|
||||
<>
|
||||
<DrawerTitle size="sub-title">Downward API</DrawerTitle>
|
||||
<DrawerItem name="Items">
|
||||
<ul>
|
||||
{downwardAPI.items?.map(({ path }) => (
|
||||
<li key={path}>
|
||||
{path}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</DrawerItem>
|
||||
</>
|
||||
)}
|
||||
{configMap && (
|
||||
<>
|
||||
<DrawerTitle size="sub-title">Config Map</DrawerTitle>
|
||||
<DrawerItem name="Name">
|
||||
{configMap.name}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Items">
|
||||
<ul>
|
||||
{configMap.items?.map(({ key, path }) => (
|
||||
<li key={key}>
|
||||
{key} ⇢ {path}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</DrawerItem>
|
||||
</>
|
||||
)}
|
||||
{serviceAccountToken && (
|
||||
<>
|
||||
<DrawerTitle size="sub-title">Service Account Token</DrawerTitle>
|
||||
<DrawerItem name="Audience" hidden={!serviceAccountToken.audience}>
|
||||
{serviceAccountToken.audience}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Expiration">
|
||||
{String(serviceAccountToken.expirationSeconds || (60*60 /* an hour */))}s
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Path">
|
||||
{serviceAccountToken.path}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Registry">
|
||||
{registry}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Volume">
|
||||
{volume}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="User">
|
||||
{user}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Group">
|
||||
{group ?? "-- no group --"}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Tenant" hidden={!tenant}>
|
||||
{tenant}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Ceph Monitors">
|
||||
<ul>
|
||||
{monitors.map(monitor => <li key={monitor}>{monitor}</li>)}
|
||||
</ul>
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Image">
|
||||
{image}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Pool">
|
||||
{pool}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="User">
|
||||
{user}
|
||||
</DrawerItem>
|
||||
{
|
||||
secretRef
|
||||
? (
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Authentication Secret"
|
||||
kubeRef={secretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<DrawerItem name="Keyright Path">
|
||||
{keyring}
|
||||
</DrawerItem>
|
||||
)
|
||||
}
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Gateway">
|
||||
{gateway}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="System">
|
||||
{system}
|
||||
</DrawerItem>
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Name"
|
||||
kubeRef={secretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
<DrawerItem name="SSL Enabled">
|
||||
{sslEnabled.toString()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Protection Domain Name" hidden={!protectionDomain}>
|
||||
{protectionDomain}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Storage Pool" hidden={!storagePool}>
|
||||
{storagePool}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Storage Mode" hidden={!storageMode}>
|
||||
{storageMode}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Volume Name">
|
||||
{volumeName}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem Type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Name"
|
||||
kubeRef={{ name: secretName }}
|
||||
api={secretsApi}
|
||||
/>
|
||||
<DrawerItem name="Items" hidden={items.length === 0}>
|
||||
<ul>
|
||||
{items.map(({ key }) => <li key={key}>{key}</li>)}
|
||||
</ul>
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Default File Mode">
|
||||
0o{defaultMode.toString(8)}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Optional">
|
||||
{optional.toString()}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Volume Name">
|
||||
{volumeName}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Volume Namespace" hidden={volumeNamespace === "default"}>
|
||||
{
|
||||
volumeNamespace === volumeName
|
||||
? "- no default behaviour -"
|
||||
: volumeNamespace || pod.getNs()
|
||||
}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readonly">
|
||||
{readOnly.toString()}
|
||||
</DrawerItem>
|
||||
<LocalRef
|
||||
pod={pod}
|
||||
title="Secret"
|
||||
kubeRef={secretRef}
|
||||
api={secretsApi}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 }}) => (
|
||||
<>
|
||||
<DrawerItem name="Virtual Machine Disk Volume">
|
||||
{volumePath}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Filesystem type">
|
||||
{fsType}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Storage Policy Based Management Profile Name" hidden={!storagePolicyName}>
|
||||
{storagePolicyName}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Storage Policy Based Management Profile ID" hidden={!storagePolicyID}>
|
||||
{storagePolicyID}
|
||||
</DrawerItem>
|
||||
</>
|
||||
)
|
||||
);
|
||||
@ -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 (
|
||||
<>
|
||||
<DrawerTitle>Volumes</DrawerTitle>
|
||||
{volumes.map(volume => (
|
||||
<div key={volume.name} className="volume">
|
||||
<div className="title flex gaps">
|
||||
<Icon small material="storage" />
|
||||
<span>{volume.name}</span>
|
||||
</div>
|
||||
<VolumeVariant pod={pod} volume={volume} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@ -131,7 +131,7 @@ export class PodDetailsList extends React.Component<PodDetailsListProps> {
|
||||
|
||||
return (
|
||||
<div className="PodDetailsList flex column">
|
||||
<DrawerTitle title="Pods" />
|
||||
<DrawerTitle>Pods</DrawerTitle>
|
||||
<Table
|
||||
tableId="workloads_pod_details_list"
|
||||
items={pods}
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
|
||||
import "./pod-details-secrets.scss";
|
||||
|
||||
import React, { Component } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { autorun, observable, makeObservable } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { reaction } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Pod, Secret, secretsApi } from "../../../common/k8s-api/endpoints";
|
||||
import { getDetailsUrl } from "../kube-detail-params";
|
||||
|
||||
@ -16,59 +16,50 @@ export interface PodDetailsSecretsProps {
|
||||
pod: Pod;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class PodDetailsSecrets extends Component<PodDetailsSecretsProps> {
|
||||
@observable secrets: Map<string, Secret> = observable.map<string, Secret>();
|
||||
export const PodDetailsSecrets = observer(({ pod }: PodDetailsSecretsProps) => {
|
||||
const [secrets, setSecrets] = useState(new Map<string, Secret>());
|
||||
|
||||
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<Secret>).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 <span key={name}>{name}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="PodDetailsSecrets">
|
||||
{
|
||||
pod.getSecrets().map(secretName => {
|
||||
const secret = this.secrets.get(secretName);
|
||||
|
||||
if (secret) {
|
||||
return this.renderSecretLink(secret);
|
||||
} else {
|
||||
return (
|
||||
<span key={secretName}>{secretName}</span>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderSecretLink(secret: Secret) {
|
||||
return (
|
||||
<Link key={secret.getId()} to={getDetailsUrl(secret.selfLink)}>
|
||||
{secret.getName()}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="PodDetailsSecrets">
|
||||
{pod.getSecrets().map(renderSecret)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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<Pod> {
|
||||
}
|
||||
@ -73,12 +73,13 @@ export class PodDetails extends React.Component<PodDetailsProps> {
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="PodDetails">
|
||||
@ -90,26 +91,22 @@ export class PodDetails extends React.Component<PodDetailsProps> {
|
||||
<PodCharts/>
|
||||
</ResourceMetrics>
|
||||
)}
|
||||
|
||||
<KubeObjectMeta object={pod}/>
|
||||
|
||||
<DrawerItem name="Status">
|
||||
<span className={cssNames("status", kebabCase(pod.getStatusMessage()))}>{pod.getStatusMessage()}</span>
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Node">
|
||||
{nodeName && (
|
||||
<Link to={getDetailsUrl(nodesApi.getUrl({ name: nodeName }))}>
|
||||
{nodeName}
|
||||
</Link>
|
||||
)}
|
||||
<DrawerItem name="Node" hidden={!nodeName}>
|
||||
<Link to={getDetailsUrl(nodesApi.getUrl({ name: nodeName }))}>
|
||||
{nodeName}
|
||||
</Link>
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Pod IP">
|
||||
{podIP}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Pod IPs" hidden={!podIPs.length} labelsOnly>
|
||||
{
|
||||
podIPs.map(label => (
|
||||
<Badge key={label} label={label}/>
|
||||
))
|
||||
}
|
||||
<DrawerItem name="Pod IPs" hidden={podIPs.length === 0} labelsOnly>
|
||||
{podIPs.map(label => <Badge key={label} label={label} />)}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Priority Class">
|
||||
{pod.getPriorityClassName()}
|
||||
@ -117,129 +114,55 @@ export class PodDetails extends React.Component<PodDetailsProps> {
|
||||
<DrawerItem name="QoS Class">
|
||||
{pod.getQosClass()}
|
||||
</DrawerItem>
|
||||
{conditions &&
|
||||
<DrawerItem name="Conditions" className="conditions" labelsOnly>
|
||||
{
|
||||
conditions.map(condition => {
|
||||
const { type, status, lastTransitionTime } = condition;
|
||||
|
||||
return (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
disabled={status === "False"}
|
||||
tooltip={`Last transition time: ${lastTransitionTime}`}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
{nodeSelector.length > 0 &&
|
||||
<DrawerItem name="Node Selector">
|
||||
<DrawerItem name="Conditions" className="conditions" hidden={conditions.length === 0} labelsOnly>
|
||||
{
|
||||
nodeSelector.map(label => (
|
||||
<Badge key={label} label={label}/>
|
||||
conditions.map(({ type, status, lastTransitionTime }) => (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
disabled={status === "False"}
|
||||
tooltip={`Last transition time: ${lastTransitionTime}`}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
|
||||
<DrawerItem name="Node Selector" hidden={nodeSelector.length === 0}>
|
||||
{nodeSelector.map(label => <Badge key={label} label={label} />)}
|
||||
</DrawerItem>
|
||||
|
||||
<PodDetailsTolerations workload={pod}/>
|
||||
<PodDetailsAffinities workload={pod}/>
|
||||
|
||||
{pod.getSecrets().length > 0 && (
|
||||
<DrawerItem name="Secrets">
|
||||
<PodDetailsSecrets pod={pod}/>
|
||||
</DrawerItem>
|
||||
)}
|
||||
<DrawerItem name="Secrets" hidden={pod.getSecrets().length === 0}>
|
||||
<PodDetailsSecrets pod={pod}/>
|
||||
</DrawerItem>
|
||||
|
||||
{pod.getInitContainers() && pod.getInitContainers().length > 0 &&
|
||||
<DrawerTitle title="Init Containers"/>
|
||||
}
|
||||
{
|
||||
pod.getInitContainers() && pod.getInitContainers().map(container => {
|
||||
return <PodDetailsContainer key={container.name} pod={pod} container={container}/>;
|
||||
})
|
||||
}
|
||||
<DrawerTitle title="Containers"/>
|
||||
{
|
||||
pod.getContainers().map(container => {
|
||||
const { name } = container;
|
||||
const metrics = getItemMetrics(toJS(this.containerMetrics), name);
|
||||
|
||||
return (
|
||||
{initContainers.length > 0 && (
|
||||
<>
|
||||
<DrawerTitle>Init Containers</DrawerTitle>
|
||||
{initContainers.map(container => (
|
||||
<PodDetailsContainer
|
||||
key={name}
|
||||
key={container.name}
|
||||
pod={pod}
|
||||
container={container}
|
||||
metrics={metrics || null}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
{volumes.length > 0 && (
|
||||
<>
|
||||
<DrawerTitle title="Volumes"/>
|
||||
{volumes.map(volume => {
|
||||
const claimName = volume.persistentVolumeClaim ? volume.persistentVolumeClaim.claimName : null;
|
||||
const configMap = volume.configMap ? volume.configMap.name : null;
|
||||
const type = Object.keys(volume)[1];
|
||||
|
||||
return (
|
||||
<div key={volume.name} className="volume">
|
||||
<div className="title flex gaps">
|
||||
<Icon small material="storage"/>
|
||||
<span>{volume.name}</span>
|
||||
</div>
|
||||
<DrawerItem name="Type">
|
||||
{type}
|
||||
</DrawerItem>
|
||||
{ type == "configMap" && (
|
||||
<div>
|
||||
{configMap && (
|
||||
<DrawerItem name="Name">
|
||||
<Link
|
||||
to={getDetailsUrl(configMapApi.getUrl({
|
||||
name: configMap,
|
||||
namespace: pod.getNs(),
|
||||
}))}>{configMap}
|
||||
</Link>
|
||||
</DrawerItem>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{ type === "emptyDir" && (
|
||||
<div>
|
||||
{ volume.emptyDir.medium && (
|
||||
<DrawerItem name="Medium">
|
||||
{volume.emptyDir.medium}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{ volume.emptyDir.sizeLimit && (
|
||||
<DrawerItem name="Size Limit">
|
||||
{volume.emptyDir.sizeLimit}
|
||||
</DrawerItem>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{claimName && (
|
||||
<DrawerItem name="Claim Name">
|
||||
<Link
|
||||
to={getDetailsUrl(pvcApi.getUrl({
|
||||
name: claimName,
|
||||
namespace: pod.getNs(),
|
||||
}))}
|
||||
>{claimName}
|
||||
</Link>
|
||||
</DrawerItem>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DrawerTitle>Containers</DrawerTitle>
|
||||
{containers.map(container => (
|
||||
<PodDetailsContainer
|
||||
key={container.name}
|
||||
pod={pod}
|
||||
container={container}
|
||||
metrics={getItemMetrics(toJS(this.containerMetrics), container.name)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<PodVolumes pod={pod} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import "~flex.box"; // todo: replace with tailwind's flexbox classes
|
||||
@import "./fonts";
|
||||
@import "~flex.box";
|
||||
@import "../themes/theme-vars";
|
||||
@import "./fonts";
|
||||
|
||||
:root {
|
||||
--unit: 8px;
|
||||
|
||||
@ -59,6 +59,10 @@ export function Avatar(props: AvatarProps) {
|
||||
const { title, variant = "rounded", size = 32, colorHash, children, background, imgProps, src, className, disabled, ...rest } = props;
|
||||
|
||||
const getBackgroundColor = () => {
|
||||
if (src) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
return background || randomColor({ seed: colorHash, luminosity: "dark" });
|
||||
};
|
||||
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
width: 5ch;
|
||||
width: 4ch;
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
background: linear-gradient(90deg, transparent 0%, var(--color-active) 25%);
|
||||
|
||||
@ -46,7 +46,7 @@ class NonInjectedEditResource extends React.Component<EditResourceProps & Depend
|
||||
const obj = this.resource;
|
||||
|
||||
if (!obj) {
|
||||
if (store.isLoaded) {
|
||||
if (store?.isLoaded) {
|
||||
// auto-close tab when resource removed from store
|
||||
this.props.closeTab(this.props.tab.id);
|
||||
} else if (!store.isLoading) {
|
||||
|
||||
@ -57,8 +57,9 @@ const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStore
|
||||
setTimeout(() => {
|
||||
const overlay = document.querySelector(".PodLogs .list span.active");
|
||||
|
||||
if (!overlay) return;
|
||||
overlay.scrollIntoViewIfNeeded();
|
||||
if (typeof overlay?.scrollIntoViewIfNeeded === "function") {
|
||||
overlay.scrollIntoViewIfNeeded();
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
|
||||
@ -4,7 +4,4 @@
|
||||
*/
|
||||
|
||||
.TerminalTab {
|
||||
@include font-preload {
|
||||
font-family: "RobotoMono", monospace;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user