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:
|
The custom Lens UI elements that you can add include:
|
||||||
|
|
||||||
* [Cluster pages](#clusterpages)
|
- [Cluster pages](#clusterpages)
|
||||||
* [Cluster page menus](#clusterpagemenus)
|
- [Cluster page menus](#clusterpagemenus)
|
||||||
* [Global pages](#globalpages)
|
- [Global pages](#globalpages)
|
||||||
* [Welcome menus](#welcomemenus)
|
- [Welcome menus](#welcomemenus)
|
||||||
* [App preferences](#apppreferences)
|
- [App preferences](#apppreferences)
|
||||||
* [Top bar items](#topbaritems)
|
- [Top bar items](#topbaritems)
|
||||||
* [Status bar items](#statusbaritems)
|
- [Status bar items](#statusbaritems)
|
||||||
* [KubeObject menu items](#kubeobjectmenuitems)
|
- [KubeObject menu items](#kubeobjectmenuitems)
|
||||||
* [KubeObject detail items](#kubeobjectdetailitems)
|
- [KubeObject detail items](#kubeobjectdetailitems)
|
||||||
* [KubeObject status texts](#kubeobjectstatustexts)
|
- [KubeObject status texts](#kubeobjectstatustexts)
|
||||||
* [Kube workloads overview items](#kubeworkloadsoverviewitems)
|
- [Kube workloads overview items](#kubeworkloadsoverviewitems)
|
||||||
|
|
||||||
as well as catalog-related UI elements:
|
as well as catalog-related UI elements:
|
||||||
|
|
||||||
* [Entity settings](#entitysettings)
|
- [Entity settings](#entitysettings)
|
||||||
* [Catalog entity detail items](#catalogentitydetailitems)
|
- [Catalog entity detail items](#catalogentitydetailitems)
|
||||||
|
|
||||||
All UI elements are based on React components.
|
All UI elements are based on React components.
|
||||||
|
|
||||||
Finally, you can also add commands and protocol handlers:
|
Finally, you can also add commands and protocol handlers:
|
||||||
|
|
||||||
* [Command palette commands](#commandpalettecommands)
|
- [Command palette commands](#commandpalettecommands)
|
||||||
* [protocol handlers](protocol-handlers.md)
|
- [protocol handlers](protocol-handlers.md)
|
||||||
|
|
||||||
## `Renderer.LensExtension` Class
|
## `Renderer.LensExtension` Class
|
||||||
|
|
||||||
@ -41,11 +41,11 @@ import { Renderer } from "@k8slens/extensions";
|
|||||||
|
|
||||||
export default class ExampleExtensionMain extends Renderer.LensExtension {
|
export default class ExampleExtensionMain extends Renderer.LensExtension {
|
||||||
onActivate() {
|
onActivate() {
|
||||||
console.log('custom renderer process extension code started');
|
console.log("custom renderer process extension code started");
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeactivate() {
|
onDeactivate() {
|
||||||
console.log('custom renderer process extension de-activated');
|
console.log("custom renderer process extension de-activated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -75,8 +75,8 @@ Add a cluster page definition to a `Renderer.LensExtension` subclass with the fo
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExampleIcon, ExamplePage } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleExtension extends Renderer.LensExtension {
|
export default class ExampleExtension extends Renderer.LensExtension {
|
||||||
clusterPages = [
|
clusterPages = [
|
||||||
@ -84,8 +84,8 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
id: "hello",
|
id: "hello",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExamplePage extension={this} />,
|
Page: () => <ExamplePage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -93,24 +93,26 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
`clusterPages` is an array of objects that satisfy the `PageRegistration` interface.
|
`clusterPages` is an array of objects that satisfy the `PageRegistration` interface.
|
||||||
The properties of the `clusterPages` array objects are defined as follows:
|
The properties of the `clusterPages` array objects are defined as follows:
|
||||||
|
|
||||||
* `id` is a string that identifies the page.
|
- `id` is a string that identifies the page.
|
||||||
* `components` matches the `PageComponents` interface for which there is one field, `Page`.
|
- `components` matches the `PageComponents` interface for which there is one field, `Page`.
|
||||||
* `Page` is of type ` React.ComponentType<any>`.
|
- `Page` is of type ` React.ComponentType<any>`.
|
||||||
It offers flexibility in defining the appearance and behavior of your page.
|
It offers flexibility in defining the appearance and behavior of your page.
|
||||||
|
|
||||||
`ExamplePage` in the example above can be defined in `page.tsx`:
|
`ExamplePage` in the example above can be defined in `page.tsx`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> {
|
export class ExamplePage extends React.Component<{
|
||||||
|
extension: LensRendererExtension;
|
||||||
|
}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>Hello world!</p>
|
<p>Hello world!</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -130,8 +132,8 @@ By expanding on the above example, you can add a cluster page menu item to the `
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExampleIcon, ExamplePage } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleExtension extends Renderer.LensExtension {
|
export default class ExampleExtension extends Renderer.LensExtension {
|
||||||
clusterPages = [
|
clusterPages = [
|
||||||
@ -139,8 +141,8 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
id: "hello",
|
id: "hello",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExamplePage extension={this} />,
|
Page: () => <ExamplePage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
clusterPageMenus = [
|
clusterPageMenus = [
|
||||||
@ -149,7 +151,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
title: "Hello World",
|
title: "Hello World",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExampleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -159,10 +161,10 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
This element defines how the cluster page menu item will appear and what it will do when you click it.
|
This element defines how the cluster page menu item will appear and what it will do when you click it.
|
||||||
The properties of the `clusterPageMenus` array objects are defined as follows:
|
The properties of the `clusterPageMenus` array objects are defined as follows:
|
||||||
|
|
||||||
* `target` links to the relevant cluster page using `pageId`.
|
- `target` links to the relevant cluster page using `pageId`.
|
||||||
* `pageId` takes the value of the relevant cluster page's `id` property.
|
- `pageId` takes the value of the relevant cluster page's `id` property.
|
||||||
* `title` sets the name of the cluster page menu item that will appear in the left side menu.
|
- `title` sets the name of the cluster page menu item that will appear in the left side menu.
|
||||||
* `components` is used to set an icon that appears to the left of the `title` text in the left side menu.
|
- `components` is used to set an icon that appears to the left of the `title` text in the left side menu.
|
||||||
|
|
||||||
The above example creates a menu item that reads **Hello World**.
|
The above example creates a menu item that reads **Hello World**.
|
||||||
When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`.
|
When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`.
|
||||||
@ -171,7 +173,7 @@ This example requires the definition of another React-based component, `ExampleI
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
type IconProps = Renderer.Component.IconProps;
|
type IconProps = Renderer.Component.IconProps;
|
||||||
|
|
||||||
@ -180,16 +182,18 @@ const {
|
|||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
export function ExampleIcon(props: IconProps) {
|
export function ExampleIcon(props: IconProps) {
|
||||||
return <Icon {...props} material="pages" tooltip={"Hi!"}/>
|
return <Icon {...props} material="pages" tooltip={"Hi!"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExamplePage extends React.Component<{ extension: Renderer.LensExtension }> {
|
export class ExamplePage extends React.Component<{
|
||||||
|
extension: Renderer.LensExtension;
|
||||||
|
}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>Hello world!</p>
|
<p>Hello world!</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -198,17 +202,16 @@ Lens includes various built-in components available for extension developers to
|
|||||||
One of these is the `Renderer.Component.Icon`, introduced in `ExampleIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io).
|
One of these is the `Renderer.Component.Icon`, introduced in `ExampleIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io).
|
||||||
The properties that `Renderer.Component.Icon` uses are defined as follows:
|
The properties that `Renderer.Component.Icon` uses are defined as follows:
|
||||||
|
|
||||||
* `material` takes the name of the icon you want to use.
|
- `material` takes the name of the icon you want to use.
|
||||||
* `tooltip` sets the text you want to appear when a user hovers over the icon.
|
- `tooltip` sets the text you want to appear when a user hovers over the icon.
|
||||||
|
|
||||||
`clusterPageMenus` can also be used to define sub menu items, so that you can create groups of cluster pages.
|
`clusterPageMenus` can also be used to define sub menu items, so that you can create groups of cluster pages.
|
||||||
The following example groups two sub menu items under one parent menu item:
|
The following example groups two sub menu items under one parent menu item:
|
||||||
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExampleIcon, ExamplePage } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleExtension extends Renderer.LensExtension {
|
export default class ExampleExtension extends Renderer.LensExtension {
|
||||||
clusterPages = [
|
clusterPages = [
|
||||||
@ -216,14 +219,14 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
id: "hello",
|
id: "hello",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExamplePage extension={this} />,
|
Page: () => <ExamplePage extension={this} />,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "bonjour",
|
id: "bonjour",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExemplePage extension={this} />,
|
Page: () => <ExemplePage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
clusterPageMenus = [
|
clusterPageMenus = [
|
||||||
@ -232,7 +235,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
title: "Greetings",
|
title: "Greetings",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExampleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parentId: "example",
|
parentId: "example",
|
||||||
@ -240,7 +243,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
title: "Hello World",
|
title: "Hello World",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExampleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parentId: "example",
|
parentId: "example",
|
||||||
@ -248,8 +251,8 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
title: "Bonjour le monde",
|
title: "Bonjour le monde",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExempleIcon,
|
Icon: ExempleIcon,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -280,9 +283,9 @@ Unlike cluster pages, users can trigger global pages even when there is no activ
|
|||||||
The following example defines a `Renderer.LensExtension` subclass with a single global page definition:
|
The following example defines a `Renderer.LensExtension` subclass with a single global page definition:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from '@k8slens/extensions';
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { HelpPage } from './page';
|
import { HelpPage } from "./page";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
export default class HelpExtension extends Renderer.LensExtension {
|
export default class HelpExtension extends Renderer.LensExtension {
|
||||||
globalPages = [
|
globalPages = [
|
||||||
@ -290,8 +293,8 @@ export default class HelpExtension extends Renderer.LensExtension {
|
|||||||
id: "help",
|
id: "help",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <HelpPage extension={this} />,
|
Page: () => <HelpPage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -299,24 +302,26 @@ export default class HelpExtension extends Renderer.LensExtension {
|
|||||||
`globalPages` is an array of objects that satisfy the `PageRegistration` interface.
|
`globalPages` is an array of objects that satisfy the `PageRegistration` interface.
|
||||||
The properties of the `globalPages` array objects are defined as follows:
|
The properties of the `globalPages` array objects are defined as follows:
|
||||||
|
|
||||||
* `id` is a string that identifies the page.
|
- `id` is a string that identifies the page.
|
||||||
* `components` matches the `PageComponents` interface for which there is one field, `Page`.
|
- `components` matches the `PageComponents` interface for which there is one field, `Page`.
|
||||||
* `Page` is of type `React.ComponentType<any>`.
|
- `Page` is of type `React.ComponentType<any>`.
|
||||||
It offers flexibility in defining the appearance and behavior of your page.
|
It offers flexibility in defining the appearance and behavior of your page.
|
||||||
|
|
||||||
`HelpPage` in the example above can be defined in `page.tsx`:
|
`HelpPage` in the example above can be defined in `page.tsx`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export class HelpPage extends React.Component<{ extension: LensRendererExtension }> {
|
export class HelpPage extends React.Component<{
|
||||||
|
extension: LensRendererExtension;
|
||||||
|
}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>Help yourself</p>
|
<p>Help yourself</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -328,11 +333,12 @@ This way, `HelpPage` can access all `HelpExtension` subclass data.
|
|||||||
This example code shows how to create a global page, but not how to make that page available to the Lens user.
|
This example code shows how to create a global page, but not how to make that page available to the Lens user.
|
||||||
Global pages are typically made available in the following ways:
|
Global pages are typically made available in the following ways:
|
||||||
|
|
||||||
* To add global pages to the top menu bar, see [`appMenus`](../main-extension#appmenus) in the Main Extension guide.
|
- To add global pages to the top menu bar, see [`appMenus`](../main-extension#appmenus) in the Main Extension guide.
|
||||||
* To add global pages as an interactive element in the blue status bar along the bottom of the Lens UI, see [`statusBarItems`](#statusbaritems).
|
- To add global pages as an interactive element in the blue status bar along the bottom of the Lens UI, see [`statusBarItems`](#statusbaritems).
|
||||||
* To add global pages to the Welcome Page, see [`welcomeMenus`](#welcomemenus).
|
- To add global pages to the Welcome Page, see [`welcomeMenus`](#welcomemenus).
|
||||||
|
|
||||||
### `welcomeMenus`
|
### `welcomeMenus`
|
||||||
|
|
||||||
### `appPreferences`
|
### `appPreferences`
|
||||||
|
|
||||||
The Lens **Preferences** page is a built-in global page.
|
The Lens **Preferences** page is a built-in global page.
|
||||||
@ -342,12 +348,14 @@ The following example demonstrates adding a custom preference:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
|
import {
|
||||||
|
ExamplePreferenceHint,
|
||||||
|
ExamplePreferenceInput,
|
||||||
|
} from "./src/example-preference";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleRendererExtension extends Renderer.LensExtension {
|
export default class ExampleRendererExtension extends Renderer.LensExtension {
|
||||||
|
|
||||||
@observable preference = { enabled: false };
|
@observable preference = { enabled: false };
|
||||||
|
|
||||||
appPreferences = [
|
appPreferences = [
|
||||||
@ -355,9 +363,9 @@ export default class ExampleRendererExtension extends Renderer.LensExtension {
|
|||||||
title: "Example Preferences",
|
title: "Example Preferences",
|
||||||
components: {
|
components: {
|
||||||
Input: () => <ExamplePreferenceInput preference={this.preference} />,
|
Input: () => <ExamplePreferenceInput preference={this.preference} />,
|
||||||
Hint: () => <ExamplePreferenceHint/>
|
Hint: () => <ExamplePreferenceHint />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -365,10 +373,10 @@ export default class ExampleRendererExtension extends Renderer.LensExtension {
|
|||||||
`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface.
|
`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface.
|
||||||
The properties of the `appPreferences` array objects are defined as follows:
|
The properties of the `appPreferences` array objects are defined as follows:
|
||||||
|
|
||||||
* `title` sets the heading text displayed on the Preferences page.
|
- `title` sets the heading text displayed on the Preferences page.
|
||||||
* `components` specifies two `React.Component` objects that define the interface for the preference.
|
- `components` specifies two `React.Component` objects that define the interface for the preference.
|
||||||
* `Input` specifies an interactive input element for the preference.
|
- `Input` specifies an interactive input element for the preference.
|
||||||
* `Hint` provides descriptive information for the preference, shown below the `Input` element.
|
- `Hint` provides descriptive information for the preference, shown below the `Input` element.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Note that the input and the hint can be comprised of more sophisticated elements, according to the needs of the extension.
|
Note that the input and the hint can be comprised of more sophisticated elements, according to the needs of the extension.
|
||||||
@ -386,20 +394,17 @@ import { observer } from "mobx-react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Component: {
|
Component: { Checkbox },
|
||||||
Checkbox,
|
|
||||||
},
|
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
export class ExamplePreferenceProps {
|
export class ExamplePreferenceProps {
|
||||||
preference: {
|
preference: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
|
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super({ preference: { enabled: false } });
|
super({ preference: { enabled: false } });
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
@ -411,7 +416,9 @@ export class ExamplePreferenceInput extends React.Component<ExamplePreferencePro
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
label="I understand appPreferences"
|
label="I understand appPreferences"
|
||||||
value={preference.enabled}
|
value={preference.enabled}
|
||||||
onChange={v => { preference.enabled = v; }}
|
onChange={(v) => {
|
||||||
|
preference.enabled = v;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -419,18 +426,16 @@ export class ExamplePreferenceInput extends React.Component<ExamplePreferencePro
|
|||||||
|
|
||||||
export class ExamplePreferenceHint extends React.Component {
|
export class ExamplePreferenceHint extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return <span>This is an example of an appPreference for extensions.</span>;
|
||||||
<span>This is an example of an appPreference for extensions.</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`ExamplePreferenceInput` implements a simple checkbox using Lens's `Renderer.Component.Checkbox` using the following properties:
|
`ExamplePreferenceInput` implements a simple checkbox using Lens's `Renderer.Component.Checkbox` using the following properties:
|
||||||
|
|
||||||
* `label` sets the text that displays next to the checkbox.
|
- `label` sets the text that displays next to the checkbox.
|
||||||
* `value` is initially set to `preference.enabled`.
|
- `value` is initially set to `preference.enabled`.
|
||||||
* `onChange` is a function that responds when the state of the checkbox changes.
|
- `onChange` is a function that responds when the state of the checkbox changes.
|
||||||
|
|
||||||
`ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props.
|
`ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props.
|
||||||
This is an object with the single `enabled` property.
|
This is an object with the single `enabled` property.
|
||||||
@ -461,9 +466,9 @@ The following example adds a `statusBarItems` definition and a `globalPages` def
|
|||||||
It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
|
It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from '@k8slens/extensions';
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { HelpIcon, HelpPage } from "./page"
|
import { HelpIcon, HelpPage } from "./page";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
export default class HelpExtension extends Renderer.LensExtension {
|
export default class HelpExtension extends Renderer.LensExtension {
|
||||||
globalPages = [
|
globalPages = [
|
||||||
@ -471,8 +476,8 @@ export default class HelpExtension extends Renderer.LensExtension {
|
|||||||
id: "help",
|
id: "help",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <HelpPage extension={this} />,
|
Page: () => <HelpPage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
statusBarItems = [
|
statusBarItems = [
|
||||||
@ -486,7 +491,7 @@ export default class HelpExtension extends Renderer.LensExtension {
|
|||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
My Status Bar Item
|
My Status Bar Item
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -495,11 +500,11 @@ export default class HelpExtension extends Renderer.LensExtension {
|
|||||||
|
|
||||||
The properties of the `statusBarItems` array objects are defined as follows:
|
The properties of the `statusBarItems` array objects are defined as follows:
|
||||||
|
|
||||||
* `Item` specifies the `React.Component` that will be shown on the status bar.
|
- `Item` specifies the `React.Component` that will be shown on the status bar.
|
||||||
By default, items are added starting from the right side of the status bar.
|
By default, items are added starting from the right side of the status bar.
|
||||||
Due to limited space in the status bar, `Item` will typically specify only an icon or a short string of text.
|
Due to limited space in the status bar, `Item` will typically specify only an icon or a short string of text.
|
||||||
The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus).
|
The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus).
|
||||||
* `onClick` determines what the `statusBarItem` does when it is clicked.
|
- `onClick` determines what the `statusBarItem` does when it is clicked.
|
||||||
In the example, `onClick` is set to a function that calls the `LensRendererExtension` `navigate()` method.
|
In the example, `onClick` is set to a function that calls the `LensRendererExtension` `navigate()` method.
|
||||||
`navigate` takes the `id` of the associated global page as a parameter.
|
`navigate` takes the `id` of the associated global page as a parameter.
|
||||||
Thus, clicking the status bar item activates the associated global pages.
|
Thus, clicking the status bar item activates the associated global pages.
|
||||||
@ -518,9 +523,9 @@ They also appear on the title bar of the details page for specific resources:
|
|||||||
The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action:
|
The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import React from "react"
|
import React from "react";
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { NamespaceMenuItem } from "./src/namespace-menu-item"
|
import { NamespaceMenuItem } from "./src/namespace-menu-item";
|
||||||
|
|
||||||
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
|
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
|
||||||
type Namespace = Renderer.K8sApi.Namespace;
|
type Namespace = Renderer.K8sApi.Namespace;
|
||||||
@ -531,22 +536,23 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
kind: "Namespace",
|
kind: "Namespace",
|
||||||
apiVersions: ["v1"],
|
apiVersions: ["v1"],
|
||||||
components: {
|
components: {
|
||||||
MenuItem: (props: KubeObjectMenuProps<Namespace>) => <NamespaceMenuItem {...props} />
|
MenuItem: (props: KubeObjectMenuProps<Namespace>) => (
|
||||||
}
|
<NamespaceMenuItem {...props} />
|
||||||
}
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface.
|
`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface.
|
||||||
The example above adds a menu item for namespaces in the cluster dashboard.
|
The example above adds a menu item for namespaces in the cluster dashboard.
|
||||||
The properties of the `kubeObjectMenuItems` array objects are defined as follows:
|
The properties of the `kubeObjectMenuItems` array objects are defined as follows:
|
||||||
|
|
||||||
* `kind` specifies the Kubernetes resource type the menu item will apply to.
|
- `kind` specifies the Kubernetes resource type the menu item will apply to.
|
||||||
* `apiVersion` specifies the Kubernetes API version number to use with the resource type.
|
- `apiVersion` specifies the Kubernetes API version number to use with the resource type.
|
||||||
* `components` defines the menu item's appearance and behavior.
|
- `components` defines the menu item's appearance and behavior.
|
||||||
* `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties.
|
- `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties.
|
||||||
In this example a `NamespaceMenuItem` object is returned.
|
In this example a `NamespaceMenuItem` object is returned.
|
||||||
|
|
||||||
`NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`:
|
`NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`:
|
||||||
@ -556,11 +562,7 @@ import React from "react";
|
|||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Component: {
|
Component: { terminalStore, MenuItem, Icon },
|
||||||
terminalStore,
|
|
||||||
MenuItem,
|
|
||||||
Icon,
|
|
||||||
},
|
|
||||||
Navigation,
|
Navigation,
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
@ -587,12 +589,15 @@ export function NamespaceMenuItem(props: KubeObjectMenuProps<Namespace>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={getPods}>
|
<MenuItem onClick={getPods}>
|
||||||
<Icon material="speaker_group" interactive={toolbar} title="Get pods in terminal"/>
|
<Icon
|
||||||
|
material="speaker_group"
|
||||||
|
interactive={toolbar}
|
||||||
|
title="Get pods in terminal"
|
||||||
|
/>
|
||||||
<span className="title">Get Pods</span>
|
<span className="title">Get Pods</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`NamespaceMenuItem` returns a `Renderer.Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property.
|
`NamespaceMenuItem` returns a `Renderer.Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property.
|
||||||
@ -629,9 +634,11 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
apiVersions: ["v1"],
|
apiVersions: ["v1"],
|
||||||
priority: 10,
|
priority: 10,
|
||||||
components: {
|
components: {
|
||||||
Details: (props: KubeObjectDetailsProps<Namespace>) => <NamespaceDetailsItem {...props} />
|
Details: (props: KubeObjectDetailsProps<Namespace>) => (
|
||||||
}
|
<NamespaceDetailsItem {...props} />
|
||||||
}
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -640,10 +647,10 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
This example above adds a detail item for namespaces in the cluster dashboard.
|
This example above adds a detail item for namespaces in the cluster dashboard.
|
||||||
The properties of the `kubeObjectDetailItems` array objects are defined as follows:
|
The properties of the `kubeObjectDetailItems` array objects are defined as follows:
|
||||||
|
|
||||||
* `kind` specifies the Kubernetes resource type the detail item will apply to.
|
- `kind` specifies the Kubernetes resource type the detail item will apply to.
|
||||||
* `apiVersion` specifies the Kubernetes API version number to use with the resource type.
|
- `apiVersion` specifies the Kubernetes API version number to use with the resource type.
|
||||||
* `components` defines the detail item's appearance and behavior.
|
- `components` defines the detail item's appearance and behavior.
|
||||||
* `Details` provides a function that returns a `React.Component` given a set of detail item properties.
|
- `Details` provides a function that returns a `React.Component` given a set of detail item properties.
|
||||||
In this example a `NamespaceDetailsItem` object is returned.
|
In this example a `NamespaceDetailsItem` object is returned.
|
||||||
|
|
||||||
`NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`:
|
`NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`:
|
||||||
@ -656,12 +663,8 @@ import { observable } from "mobx";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
K8sApi: {
|
K8sApi: { podsApi },
|
||||||
podsApi,
|
Component: { DrawerTitle },
|
||||||
},
|
|
||||||
Component: {
|
|
||||||
DrawerTitle,
|
|
||||||
},
|
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
|
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
|
||||||
@ -669,7 +672,9 @@ type Namespace = Renderer.K8sApi.Namespace;
|
|||||||
type Pod = Renderer.K8sApi.Pod;
|
type Pod = Renderer.K8sApi.Pod;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class NamespaceDetailsItem extends React.Component<KubeObjectDetailsProps<Namespace>> {
|
export class NamespaceDetailsItem extends React.Component<
|
||||||
|
KubeObjectDetailsProps<Namespace>
|
||||||
|
> {
|
||||||
@observable private pods: Pod[];
|
@observable private pods: Pod[];
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
@ -681,10 +686,10 @@ export class NamespaceDetailsItem extends React.Component<KubeObjectDetailsProps
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<DrawerTitle title="Pods" />
|
<DrawerTitle>Pods</DrawerTitle>
|
||||||
<PodsDetailsList pods={this.pods} />
|
<PodsDetailsList pods={this.pods} />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -714,12 +719,7 @@ import React from "react";
|
|||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Component: {
|
Component: { TableHead, TableRow, TableCell, Table },
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
Table,
|
|
||||||
},
|
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
type Pod = Renderer.K8sApi.Pod;
|
type Pod = Renderer.K8sApi.Pod;
|
||||||
@ -736,11 +736,11 @@ export class PodsDetailsList extends React.Component<PodsDetailsListProps> {
|
|||||||
<TableCell className="podAge">{pods[index].getAge()}</TableCell>
|
<TableCell className="podAge">{pods[index].getAge()}</TableCell>
|
||||||
<TableCell className="podStatus">{pods[index].getStatus()}</TableCell>
|
<TableCell className="podStatus">{pods[index].getStatus()}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { pods } = this.props
|
const { pods } = this.props;
|
||||||
|
|
||||||
if (!pods?.length) {
|
if (!pods?.length) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
46
package.json
46
package.json
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "5.4.0",
|
"version": "5.5.0-alpha.0",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021 OpenLens Authors",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -28,6 +28,8 @@
|
|||||||
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
||||||
"build:win": "yarn run compile && electron-builder --win --dir",
|
"build:win": "yarn run compile && electron-builder --win --dir",
|
||||||
"integration": "jest --runInBand --detectOpenHandles --forceExit integration",
|
"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": "yarn run compile && electron-builder --publish onTag",
|
||||||
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
|
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
|
||||||
"download:binaries": "yarn run ts-node build/download_binaries.ts",
|
"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"
|
"postversion": "git push --set-upstream ${GIT_REMOTE:-origin} release/v$npm_package_version"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"k8sProxyVersion": "0.1.5",
|
"k8sProxyVersion": "0.2.1",
|
||||||
"bundledKubectlVersion": "1.23.3",
|
"bundledKubectlVersion": "1.23.3",
|
||||||
"bundledHelmVersion": "3.7.2",
|
"bundledHelmVersion": "3.7.2",
|
||||||
"sentryDsn": ""
|
"sentryDsn": ""
|
||||||
@ -203,7 +205,7 @@
|
|||||||
"@ogre-tools/injectable": "5.1.2",
|
"@ogre-tools/injectable": "5.1.2",
|
||||||
"@ogre-tools/injectable-react": "5.1.2",
|
"@ogre-tools/injectable-react": "5.1.2",
|
||||||
"@sentry/electron": "^2.5.4",
|
"@sentry/electron": "^2.5.4",
|
||||||
"@sentry/integrations": "^6.15.0",
|
"@sentry/integrations": "^6.19.2",
|
||||||
"@types/circular-dependency-plugin": "5.0.5",
|
"@types/circular-dependency-plugin": "5.0.5",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
"auto-bind": "^4.0.0",
|
"auto-bind": "^4.0.0",
|
||||||
@ -214,7 +216,7 @@
|
|||||||
"conf": "^7.1.2",
|
"conf": "^7.1.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-updater": "^4.6.1",
|
"electron-updater": "^4.6.5",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"filehound": "^1.17.5",
|
"filehound": "^1.17.5",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
@ -223,7 +225,7 @@
|
|||||||
"grapheme-splitter": "^1.0.4",
|
"grapheme-splitter": "^1.0.4",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"immer": "^9.0.6",
|
"immer": "^9.0.12",
|
||||||
"joi": "^17.6.0",
|
"joi": "^17.6.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsdom": "^16.7.0",
|
"jsdom": "^16.7.0",
|
||||||
@ -243,7 +245,7 @@
|
|||||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||||
"node-fetch": "lensapp/node-fetch#2.x",
|
"node-fetch": "lensapp/node-fetch#2.x",
|
||||||
"node-pty": "^0.10.1",
|
"node-pty": "^0.10.1",
|
||||||
"npm": "^6.14.15",
|
"npm": "^6.14.16",
|
||||||
"p-limit": "^3.1.0",
|
"p-limit": "^3.1.0",
|
||||||
"path-to-regexp": "^6.2.0",
|
"path-to-regexp": "^6.2.0",
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
@ -265,7 +267,7 @@
|
|||||||
"tempy": "1.0.1",
|
"tempy": "1.0.1",
|
||||||
"url-parse": "^1.5.10",
|
"url-parse": "^1.5.10",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"win-ca": "^3.4.5",
|
"win-ca": "^3.5.0",
|
||||||
"winston": "^3.6.0",
|
"winston": "^3.6.0",
|
||||||
"winston-console-format": "^1.0.8",
|
"winston-console-format": "^1.0.8",
|
||||||
"winston-transport-browserconsole": "^1.0.5",
|
"winston-transport-browserconsole": "^1.0.5",
|
||||||
@ -277,8 +279,8 @@
|
|||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||||
"@sentry/types": "^6.18.2",
|
"@sentry/types": "^6.19.2",
|
||||||
"@testing-library/jest-dom": "^5.16.2",
|
"@testing-library/jest-dom": "^5.16.3",
|
||||||
"@testing-library/react": "^11.2.7",
|
"@testing-library/react": "^11.2.7",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/byline": "^4.2.33",
|
"@types/byline": "^4.2.33",
|
||||||
@ -293,18 +295,18 @@
|
|||||||
"@types/gunzip-maybe": "^1.4.0",
|
"@types/gunzip-maybe": "^1.4.0",
|
||||||
"@types/hoist-non-react-statics": "^3.3.1",
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
"@types/html-webpack-plugin": "^3.2.6",
|
"@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/jest": "^26.0.24",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsdom": "^16.2.13",
|
"@types/jsdom": "^16.2.14",
|
||||||
"@types/jsonpath": "^0.2.0",
|
"@types/jsonpath": "^0.2.0",
|
||||||
"@types/lodash": "^4.14.180",
|
"@types/lodash": "^4.14.180",
|
||||||
"@types/marked": "^4.0.3",
|
"@types/marked": "^4.0.3",
|
||||||
"@types/md5-file": "^4.0.2",
|
"@types/md5-file": "^4.0.2",
|
||||||
"@types/mini-css-extract-plugin": "^2.4.0",
|
"@types/mini-css-extract-plugin": "^2.4.0",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "14.17.33",
|
"@types/node": "14.18.12",
|
||||||
"@types/node-fetch": "^2.5.12",
|
"@types/node-fetch": "^2.6.1",
|
||||||
"@types/npm": "^2.0.32",
|
"@types/npm": "^2.0.32",
|
||||||
"@types/proper-lockfile": "^4.1.2",
|
"@types/proper-lockfile": "^4.1.2",
|
||||||
"@types/randomcolor": "^0.5.6",
|
"@types/randomcolor": "^0.5.6",
|
||||||
@ -320,7 +322,7 @@
|
|||||||
"@types/request": "^2.48.7",
|
"@types/request": "^2.48.7",
|
||||||
"@types/request-promise-native": "^1.0.18",
|
"@types/request-promise-native": "^1.0.18",
|
||||||
"@types/semver": "^7.3.9",
|
"@types/semver": "^7.3.9",
|
||||||
"@types/sharp": "^0.29.4",
|
"@types/sharp": "^0.30.0",
|
||||||
"@types/spdy": "^3.4.5",
|
"@types/spdy": "^3.4.5",
|
||||||
"@types/tar": "^4.0.5",
|
"@types/tar": "^4.0.5",
|
||||||
"@types/tar-stream": "^2.2.2",
|
"@types/tar-stream": "^2.2.2",
|
||||||
@ -348,7 +350,7 @@
|
|||||||
"electron": "^14.2.7",
|
"electron": "^14.2.7",
|
||||||
"electron-builder": "^22.14.13",
|
"electron-builder": "^22.14.13",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"esbuild": "^0.13.15",
|
"esbuild": "^0.14.27",
|
||||||
"esbuild-loader": "^2.18.0",
|
"esbuild-loader": "^2.18.0",
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.11.0",
|
||||||
"eslint-plugin-header": "^3.1.1",
|
"eslint-plugin-header": "^3.1.1",
|
||||||
@ -372,7 +374,7 @@
|
|||||||
"node-gyp": "7.1.2",
|
"node-gyp": "7.1.2",
|
||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"playwright": "^1.17.1",
|
"playwright": "^1.20.1",
|
||||||
"postcss": "^8.4.12",
|
"postcss": "^8.4.12",
|
||||||
"postcss-loader": "^6.2.1",
|
"postcss-loader": "^6.2.1",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
@ -384,27 +386,27 @@
|
|||||||
"react-select-event": "^5.3.0",
|
"react-select-event": "^5.3.0",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
"sass": "^1.45.1",
|
"sass": "^1.49.9",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"sharp": "^0.29.3",
|
"sharp": "^0.29.3",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"tailwindcss": "^3.0.7",
|
"tailwindcss": "^3.0.23",
|
||||||
"tar-stream": "^2.2.0",
|
"tar-stream": "^2.2.0",
|
||||||
"ts-jest": "26.5.6",
|
"ts-jest": "26.5.6",
|
||||||
"ts-loader": "^9.2.6",
|
"ts-loader": "^9.2.8",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.7.0",
|
||||||
"type-fest": "^1.4.0",
|
"type-fest": "^2.12.0",
|
||||||
"typed-emitter": "^1.4.0",
|
"typed-emitter": "^1.4.0",
|
||||||
"typedoc": "0.22.10",
|
"typedoc": "0.22.10",
|
||||||
"typedoc-plugin-markdown": "^3.11.12",
|
"typedoc-plugin-markdown": "^3.11.12",
|
||||||
"typeface-roboto": "^1.1.13",
|
"typeface-roboto": "^1.1.13",
|
||||||
"typescript": "^4.5.2",
|
"typescript": "^4.5.5",
|
||||||
"typescript-plugin-css-modules": "^3.4.0",
|
"typescript-plugin-css-modules": "^3.4.0",
|
||||||
"webpack": "^5.69.0",
|
"webpack": "^5.69.0",
|
||||||
"webpack-cli": "^4.9.2",
|
"webpack-cli": "^4.9.2",
|
||||||
"webpack-dev-server": "^4.7.4",
|
"webpack-dev-server": "^4.7.4",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"xterm": "^4.15.0",
|
"xterm": "^4.18.0",
|
||||||
"xterm-addon-fit": "^0.5.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
|
import directoryForUserDataInjectable
|
||||||
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
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);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -89,8 +87,6 @@ describe("cluster-store", () => {
|
|||||||
mainDi = dis.mainDi;
|
mainDi = dis.mainDi;
|
||||||
|
|
||||||
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
mainDi.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
|
|
||||||
mainDi.override(kubeAuthProxyCaInjectable, () => ({} as any));
|
|
||||||
|
|
||||||
await dis.runSetups();
|
await dis.runSetups();
|
||||||
|
|
||||||
|
|||||||
@ -173,6 +173,13 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm
|
|||||||
return `${this.spec.group}/${this.spec.names.kind}`;
|
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
|
* 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
|
* @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);
|
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | void {
|
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | undefined {
|
||||||
if (!metrics) return;
|
if (!metrics) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const itemMetrics = { ...metrics };
|
const itemMetrics = { ...metrics };
|
||||||
|
|
||||||
for (const metric in metrics) {
|
for (const metric in metrics) {
|
||||||
|
|||||||
@ -31,17 +31,25 @@ export interface IPvcMetrics<T = IMetrics> {
|
|||||||
diskCapacity: T;
|
diskCapacity: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PersistentVolumeClaim {
|
export interface PersistentVolumeClaimSpec {
|
||||||
spec: {
|
|
||||||
accessModes: string[];
|
accessModes: string[];
|
||||||
storageClassName: string;
|
|
||||||
selector: LabelSelector;
|
selector: LabelSelector;
|
||||||
resources: {
|
resources: {
|
||||||
requests: {
|
requests?: Record<string, string>;
|
||||||
storage: string; // 8Gi
|
limits?: Record<string, string>;
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
volumeName?: string;
|
||||||
|
storageClassName?: string;
|
||||||
|
volumeMode?: string;
|
||||||
|
dataSource?: {
|
||||||
|
apiGroup: string;
|
||||||
|
kind: string;
|
||||||
|
name: string;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersistentVolumeClaim {
|
||||||
|
spec: PersistentVolumeClaimSpec;
|
||||||
status: {
|
status: {
|
||||||
phase: string; // Pending
|
phase: string; // Pending
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,10 @@ import { IMetrics, metricsApi } from "./metrics.api";
|
|||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
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> {
|
export class PodsApi extends KubeApi<Pod> {
|
||||||
getLogs = async (params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> => {
|
getLogs = async (params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> => {
|
||||||
@ -112,12 +116,8 @@ export interface IPodContainer extends Partial<Record<PodContainerProbe, IContai
|
|||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
envFrom?: {
|
envFrom?: {
|
||||||
configMapRef?: {
|
configMapRef?: LocalObjectReference;
|
||||||
name: string;
|
secretRef?: LocalObjectReference;
|
||||||
};
|
|
||||||
secretRef?: {
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
}[];
|
}[];
|
||||||
volumeMounts?: {
|
volumeMounts?: {
|
||||||
name: string;
|
name: string;
|
||||||
@ -195,6 +195,444 @@ export interface IPodContainerStatus {
|
|||||||
started?: boolean;
|
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 {
|
export class Pod extends WorkloadKubeObject {
|
||||||
static kind = "Pod";
|
static kind = "Pod";
|
||||||
static namespaced = true;
|
static namespaced = true;
|
||||||
@ -206,23 +644,7 @@ export class Pod extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare spec?: {
|
declare spec?: {
|
||||||
volumes?: {
|
volumes?: PodVolume[];
|
||||||
name: string;
|
|
||||||
persistentVolumeClaim: {
|
|
||||||
claimName: string;
|
|
||||||
};
|
|
||||||
emptyDir: {
|
|
||||||
medium?: string;
|
|
||||||
sizeLimit?: string;
|
|
||||||
};
|
|
||||||
configMap: {
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
secret: {
|
|
||||||
secretName: string;
|
|
||||||
defaultMode: number;
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
initContainers: IPodContainer[];
|
initContainers: IPodContainer[];
|
||||||
containers: IPodContainer[];
|
containers: IPodContainer[];
|
||||||
restartPolicy?: string;
|
restartPolicy?: string;
|
||||||
@ -239,9 +661,7 @@ export class Pod extends WorkloadKubeObject {
|
|||||||
[selector: string]: string;
|
[selector: string]: string;
|
||||||
};
|
};
|
||||||
securityContext?: {};
|
securityContext?: {};
|
||||||
imagePullSecrets?: {
|
imagePullSecrets?: LocalObjectReference[];
|
||||||
name: string;
|
|
||||||
}[];
|
|
||||||
hostNetwork?: boolean;
|
hostNetwork?: boolean;
|
||||||
hostPID?: boolean;
|
hostPID?: boolean;
|
||||||
hostIPC?: boolean;
|
hostIPC?: boolean;
|
||||||
|
|||||||
@ -25,6 +25,11 @@ export interface ISecretRef {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SecretReference {
|
||||||
|
name: string;
|
||||||
|
namespace?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SecretData extends KubeJsonApiData {
|
export interface SecretData extends KubeJsonApiData {
|
||||||
type: SecretType;
|
type: SecretType;
|
||||||
data?: Record<string, string>;
|
data?: Record<string, string>;
|
||||||
|
|||||||
@ -3,58 +3,5 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JsonApi } from "./json-api";
|
export { apiBase } from "./api-base";
|
||||||
import { KubeJsonApi } from "./kube-json-api";
|
export { apiKube } from "./api-kube";
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
import { JsonApi, JsonApiData, JsonApiError } from "./json-api";
|
import { JsonApi, JsonApiData, JsonApiError } from "./json-api";
|
||||||
import type { Response } from "node-fetch";
|
import type { Response } from "node-fetch";
|
||||||
import { LensProxy } from "../../main/lens-proxy";
|
|
||||||
import { apiKubePrefix, isDebugging } from "../vars";
|
import { apiKubePrefix, isDebugging } from "../vars";
|
||||||
|
import { apiBase } from "./api-base";
|
||||||
|
|
||||||
export interface KubeJsonApiListMetadata {
|
export interface KubeJsonApiListMetadata {
|
||||||
resourceVersion: string;
|
resourceVersion: string;
|
||||||
@ -57,15 +57,15 @@ export interface KubeJsonApiError extends JsonApiError {
|
|||||||
|
|
||||||
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
|
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
|
||||||
static forCluster(clusterId: string): KubeJsonApi {
|
static forCluster(clusterId: string): KubeJsonApi {
|
||||||
const port = LensProxy.getInstance().port;
|
const url = new URL(apiBase.config.serverAddress);
|
||||||
|
|
||||||
return new this({
|
return new this({
|
||||||
serverAddress: `http://127.0.0.1:${port}`,
|
serverAddress: apiBase.config.serverAddress,
|
||||||
apiBase: apiKubePrefix,
|
apiBase: apiKubePrefix,
|
||||||
debug: isDebugging,
|
debug: isDebugging,
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
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;
|
apiBase?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to an object in the same namespace
|
||||||
|
*/
|
||||||
|
export interface LocalObjectReference {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface KubeObjectMetadata {
|
export interface KubeObjectMetadata {
|
||||||
uid: string;
|
uid: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -65,6 +65,9 @@ if (ipcMain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default winston.createLogger({
|
export default winston.createLogger({
|
||||||
format: format.simple(),
|
format: format.combine(
|
||||||
|
format.splat(),
|
||||||
|
format.simple(),
|
||||||
|
),
|
||||||
transports,
|
transports,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -72,11 +72,34 @@ const resourcesDir = lazyInitialized(() => (
|
|||||||
: path.join(process.cwd(), "binaries", "client", normalizedPlatform)
|
: path.join(process.cwd(), "binaries", "client", normalizedPlatform)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated for being explicit side effect.
|
||||||
|
*/
|
||||||
export const baseBinariesDir = lazyInitialized(() => path.join(resourcesDir.get(), normalizedArch));
|
export const baseBinariesDir = lazyInitialized(() => path.join(resourcesDir.get(), normalizedArch));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated for being explicit side effect.
|
||||||
|
*/
|
||||||
export const kubeAuthProxyBinaryName = getBinaryName("lens-k8s-proxy");
|
export const kubeAuthProxyBinaryName = getBinaryName("lens-k8s-proxy");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated for being explicit side effect.
|
||||||
|
*/
|
||||||
export const helmBinaryName = getBinaryName("helm");
|
export const helmBinaryName = getBinaryName("helm");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated for being explicit side effect.
|
||||||
|
*/
|
||||||
export const helmBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), helmBinaryName));
|
export const helmBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), helmBinaryName));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated for being explicit side effect.
|
||||||
|
*/
|
||||||
export const kubectlBinaryName = getBinaryName("kubectl");
|
export const kubectlBinaryName = getBinaryName("kubectl");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated for being explicit side effect.
|
||||||
|
*/
|
||||||
export const kubectlBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), kubectlBinaryName));
|
export const kubectlBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), kubectlBinaryName));
|
||||||
|
|
||||||
// Webpack build paths
|
// Webpack build paths
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
import { asLegacyGlobalForExtensionApi } from "./as-legacy-global-object-for-extension-api";
|
import { asLegacyGlobalForExtensionApi } from "./as-legacy-global-object-for-extension-api";
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import type { Injectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use asLegacyGlobalForExtensionApi instead, and use proper implementations instead of "modifications".
|
||||||
|
*/
|
||||||
export const asLegacyGlobalObjectForExtensionApiWithModifications = <
|
export const asLegacyGlobalObjectForExtensionApiWithModifications = <
|
||||||
InjectableInstance extends InjectionTokenInstance,
|
InjectableInstance extends InjectionTokenInstance,
|
||||||
InjectionTokenInstance,
|
InjectionTokenInstance,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ jest.mock("winston", () => ({
|
|||||||
label: jest.fn(),
|
label: jest.fn(),
|
||||||
timestamp: jest.fn(),
|
timestamp: jest.fn(),
|
||||||
printf: jest.fn(),
|
printf: jest.fn(),
|
||||||
|
splat: jest.fn(),
|
||||||
},
|
},
|
||||||
createLogger: jest.fn().mockReturnValue(logger),
|
createLogger: jest.fn().mockReturnValue(logger),
|
||||||
transports: {
|
transports: {
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import mockFs from "mock-fs";
|
|||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
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";
|
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
@ -83,7 +82,6 @@ describe("ContextHandler", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
di.override(createKubeAuthProxyInjectable, () => ({} as any));
|
di.override(createKubeAuthProxyInjectable, () => ({} as any));
|
||||||
di.override(kubeAuthProxyCaInjectable, () => ({} as any));
|
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ jest.mock("winston", () => ({
|
|||||||
printf: jest.fn(),
|
printf: jest.fn(),
|
||||||
padLevels: jest.fn(),
|
padLevels: jest.fn(),
|
||||||
ms: jest.fn(),
|
ms: jest.fn(),
|
||||||
|
splat: jest.fn(),
|
||||||
},
|
},
|
||||||
createLogger: jest.fn().mockReturnValue({
|
createLogger: jest.fn().mockReturnValue({
|
||||||
silly: jest.fn(),
|
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 { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import spawnInjectable from "../child-process/spawn.injectable";
|
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);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -96,8 +95,6 @@ describe("kube auth proxy tests", () => {
|
|||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(spawnInjectable, () => mockSpawn);
|
di.override(spawnInjectable, () => mockSpawn);
|
||||||
di.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
|
|
||||||
di.override(kubeAuthProxyCaInjectable, () => ({} as any));
|
|
||||||
|
|
||||||
mockFs(mockMinikubeConfig);
|
mockFs(mockMinikubeConfig);
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ jest.mock("winston", () => ({
|
|||||||
padLevels: jest.fn(),
|
padLevels: jest.fn(),
|
||||||
ms: jest.fn(),
|
ms: jest.fn(),
|
||||||
printf: jest.fn(),
|
printf: jest.fn(),
|
||||||
|
splat: jest.fn(),
|
||||||
},
|
},
|
||||||
createLogger: jest.fn().mockReturnValue(logger),
|
createLogger: jest.fn().mockReturnValue(logger),
|
||||||
transports: {
|
transports: {
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
import { autoUpdater, UpdateInfo } from "electron-updater";
|
import { autoUpdater, UpdateInfo } from "electron-updater";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { isLinux, isMac, isPublishConfigured, isTestEnv } from "../common/vars";
|
import { isPublishConfigured, isTestEnv } from "../common/vars";
|
||||||
import { delay } from "../common/utils";
|
import { delay } from "../common/utils";
|
||||||
import { areArgsUpdateAvailableToBackchannel, AutoUpdateChecking, AutoUpdateLogPrefix, AutoUpdateNoUpdateAvailable, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
import { areArgsUpdateAvailableToBackchannel, AutoUpdateChecking, AutoUpdateLogPrefix, AutoUpdateNoUpdateAvailable, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
import { ipcMain, autoUpdater as electronAutoUpdater } from "electron";
|
import { ipcMain } from "electron";
|
||||||
import { nextUpdateChannel } from "./utils/update-channel";
|
import { nextUpdateChannel } from "./utils/update-channel";
|
||||||
import { UserStore } from "../common/user-store";
|
import { UserStore } from "../common/user-store";
|
||||||
|
|
||||||
@ -23,25 +23,7 @@ function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: Upd
|
|||||||
if (arg.doUpdate) {
|
if (arg.doUpdate) {
|
||||||
if (arg.now) {
|
if (arg.now) {
|
||||||
logger.info(`${AutoUpdateLogPrefix}: User chose to update 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 {
|
} else {
|
||||||
logger.info(`${AutoUpdateLogPrefix}: User chose to update on quit`);
|
logger.info(`${AutoUpdateLogPrefix}: User chose to update on quit`);
|
||||||
autoUpdater.autoInstallOnAppQuit = true;
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
@ -52,10 +34,10 @@ function handleAutoUpdateBackChannel(event: Electron.IpcMainEvent, ...[arg]: Upd
|
|||||||
}
|
}
|
||||||
|
|
||||||
autoUpdater.logger = {
|
autoUpdater.logger = {
|
||||||
info: message => logger.info(`[AUTO-UPDATE]: electron-updater:`, message),
|
info: message => logger.info(`[AUTO-UPDATE]: electron-updater: %s`, message),
|
||||||
warn: message => logger.warn(`[AUTO-UPDATE]: electron-updater:`, message),
|
warn: message => logger.warn(`[AUTO-UPDATE]: electron-updater: %s`, message),
|
||||||
error: message => logger.error(`[AUTO-UPDATE]: electron-updater:`, message),
|
error: message => logger.error(`[AUTO-UPDATE]: electron-updater: %s`, message),
|
||||||
debug: message => logger.debug(`[AUTO-UPDATE]: electron-updater:`, 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 clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable";
|
||||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
||||||
import directoryForKubeConfigsInjectable
|
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
|
||||||
import kubeAuthProxyCaInjectable from "../../kube-auth-proxy/kube-auth-proxy-ca.injectable";
|
|
||||||
import createKubeAuthProxyCertFilesInjectable from "../../kube-auth-proxy/create-kube-auth-proxy-cert-files.injectable";
|
|
||||||
|
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
@ -42,9 +39,6 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(kubeAuthProxyCaInjectable, () => Promise.resolve(Buffer.from("ca")));
|
|
||||||
di.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
|
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
await di.runSetups();
|
await di.runSetups();
|
||||||
@ -62,6 +56,7 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
ClusterManager.resetInstance();
|
ClusterManager.resetInstance();
|
||||||
|
ClusterStore.resetInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("configsToModels", () => {
|
describe("configsToModels", () => {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ interface PrometheusServicePreferences {
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
createKubeAuthProxy: CreateKubeAuthProxy;
|
createKubeAuthProxy: CreateKubeAuthProxy;
|
||||||
authProxyCa: Promise<Buffer>;
|
authProxyCa: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContextHandler {
|
export class ContextHandler {
|
||||||
@ -123,7 +123,7 @@ export class ContextHandler {
|
|||||||
return `https://127.0.0.1:${this.kubeAuthProxy.port}${this.kubeAuthProxy.apiPrefix}${path}`;
|
return `https://127.0.0.1:${this.kubeAuthProxy.port}${this.kubeAuthProxy.apiPrefix}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveAuthProxyCa() {
|
resolveAuthProxyCa() {
|
||||||
return this.dependencies.authProxyCa;
|
return this.dependencies.authProxyCa;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ export class ContextHandler {
|
|||||||
protected async newApiTarget(timeout: number): Promise<httpProxy.ServerOptions> {
|
protected async newApiTarget(timeout: number): Promise<httpProxy.ServerOptions> {
|
||||||
await this.ensureServer();
|
await this.ensureServer();
|
||||||
|
|
||||||
const caFileContents = await this.resolveAuthProxyCa();
|
const ca = this.dependencies.authProxyCa;
|
||||||
const clusterPath = this.clusterUrl.path !== "/" ? this.clusterUrl.path : "";
|
const clusterPath = this.clusterUrl.path !== "/" ? this.clusterUrl.path : "";
|
||||||
const apiPrefix = `${this.kubeAuthProxy.apiPrefix}${clusterPath}`;
|
const apiPrefix = `${this.kubeAuthProxy.apiPrefix}${clusterPath}`;
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ export class ContextHandler {
|
|||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
port: this.kubeAuthProxy.port,
|
port: this.kubeAuthProxy.port,
|
||||||
path: apiPrefix,
|
path: apiPrefix,
|
||||||
ca: caFileContents.toString(),
|
ca,
|
||||||
},
|
},
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
timeout,
|
timeout,
|
||||||
|
|||||||
@ -3,23 +3,27 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import selfsigned from "selfsigned";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import { ContextHandler } from "./context-handler";
|
import { ContextHandler } from "./context-handler";
|
||||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
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({
|
const createContextHandlerInjectable = getInjectable({
|
||||||
id: "create-context-handler",
|
id: "create-context-handler",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const authProxyCa = di.inject(kubeAuthProxyCaInjectable);
|
return (cluster: Cluster) => {
|
||||||
|
const clusterUrl = new URLParse(cluster.apiUrl);
|
||||||
|
|
||||||
const dependencies = {
|
const dependencies = {
|
||||||
createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable),
|
createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable),
|
||||||
authProxyCa,
|
authProxyCa: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate).cert,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (cluster: Cluster) => new ContextHandler(dependencies, cluster);
|
return 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 { KubeAuthProxy, KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import selfsigned from "selfsigned";
|
||||||
import { getBinaryName } from "../../common/vars";
|
import { getBinaryName } from "../../common/vars";
|
||||||
import directoryForBundledBinariesInjectable from "../../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable";
|
import directoryForBundledBinariesInjectable from "../../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable";
|
||||||
import spawnInjectable from "../child-process/spawn.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;
|
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||||
|
|
||||||
@ -18,15 +19,17 @@ const createKubeAuthProxyInjectable = getInjectable({
|
|||||||
|
|
||||||
instantiate: (di): CreateKubeAuthProxy => {
|
instantiate: (di): CreateKubeAuthProxy => {
|
||||||
const binaryName = getBinaryName("lens-k8s-proxy");
|
const binaryName = getBinaryName("lens-k8s-proxy");
|
||||||
|
|
||||||
|
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => {
|
||||||
|
const clusterUrl = new URL(cluster.apiUrl);
|
||||||
const dependencies: KubeAuthProxyDependencies = {
|
const dependencies: KubeAuthProxyDependencies = {
|
||||||
proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), binaryName),
|
proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), binaryName),
|
||||||
proxyCertPath: di.inject(createKubeAuthProxyCertFilesInjectable),
|
proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate),
|
||||||
spawn: di.inject(spawnInjectable),
|
spawn: di.inject(spawnInjectable),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => (
|
return new KubeAuthProxy(dependencies, cluster, environmentVariables);
|
||||||
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 logger from "../logger";
|
||||||
import { getPortFrom } from "../utils/get-port";
|
import { getPortFrom } from "../utils/get-port";
|
||||||
import { makeObservable, observable, when } from "mobx";
|
import { makeObservable, observable, when } from "mobx";
|
||||||
|
import type { SelfSignedCert } from "selfsigned";
|
||||||
|
|
||||||
const startingServeRegex = /starting to serve on (?<address>.+)/i;
|
const startingServeRegex = /starting to serve on (?<address>.+)/i;
|
||||||
|
|
||||||
export interface KubeAuthProxyDependencies {
|
export interface KubeAuthProxyDependencies {
|
||||||
proxyBinPath: string;
|
proxyBinPath: string;
|
||||||
proxyCertPath: Promise<string>;
|
proxyCert: SelfSignedCert;
|
||||||
spawn: typeof spawn;
|
spawn: typeof spawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ export class KubeAuthProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const proxyBin = this.dependencies.proxyBinPath;
|
const proxyBin = this.dependencies.proxyBinPath;
|
||||||
const certPath = await this.dependencies.proxyCertPath;
|
const cert = this.dependencies.proxyCert;
|
||||||
|
|
||||||
this.proxyProcess = this.dependencies.spawn(proxyBin, [], {
|
this.proxyProcess = this.dependencies.spawn(proxyBin, [], {
|
||||||
env: {
|
env: {
|
||||||
@ -52,7 +53,8 @@ export class KubeAuthProxy {
|
|||||||
KUBECONFIG: this.cluster.kubeConfigPath,
|
KUBECONFIG: this.cluster.kubeConfigPath,
|
||||||
KUBECONFIG_CONTEXT: this.cluster.contextName,
|
KUBECONFIG_CONTEXT: this.cluster.contextName,
|
||||||
API_PREFIX: this.apiPrefix,
|
API_PREFIX: this.apiPrefix,
|
||||||
CERT_PATH: certPath,
|
PROXY_KEY: cert.private,
|
||||||
|
PROXY_CERT: cert.cert,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.proxyProcess.on("error", (error) => {
|
this.proxyProcess.on("error", (error) => {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type net from "net";
|
import net from "net";
|
||||||
import type http from "http";
|
import type http from "http";
|
||||||
import spdy from "spdy";
|
import spdy from "spdy";
|
||||||
import type httpProxy from "http-proxy";
|
import type httpProxy from "http-proxy";
|
||||||
@ -79,19 +79,18 @@ export class LensProxy extends Singleton {
|
|||||||
|
|
||||||
this.proxyServer
|
this.proxyServer
|
||||||
.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
|
.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
|
||||||
const isInternal = req.url.startsWith(`${apiPrefix}?`);
|
|
||||||
const cluster = getClusterForRequest(req);
|
const cluster = getClusterForRequest(req);
|
||||||
|
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
|
logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
|
||||||
|
socket.destroy();
|
||||||
return socket.destroy();
|
} else {
|
||||||
}
|
const isInternal = req.url.startsWith(`${apiPrefix}?`);
|
||||||
|
|
||||||
const reqHandler = isInternal ? shellApiRequest : kubeApiUpgradeRequest;
|
const reqHandler = isInternal ? shellApiRequest : kubeApiUpgradeRequest;
|
||||||
|
|
||||||
(async () => reqHandler({ req, socket, head, cluster }))()
|
(async () => reqHandler({ req, socket, head, cluster }))()
|
||||||
.catch(error => logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error));
|
.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) => {
|
proxy.on("error", (error, req, res, target) => {
|
||||||
if (this.closed) {
|
if (this.closed || res instanceof net.Socket) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@ import { UserStore } from "../../../common/user-store";
|
|||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import directoryForUserDataInjectable
|
import directoryForUserDataInjectable
|
||||||
from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
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();
|
mockWindow();
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
@ -102,6 +104,7 @@ describe("<Catalog />", () => {
|
|||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
let catalogEntityStore: CatalogEntityStore;
|
let catalogEntityStore: CatalogEntityStore;
|
||||||
let catalogEntityRegistry: CatalogEntityRegistry;
|
let catalogEntityRegistry: CatalogEntityRegistry;
|
||||||
|
let emitEvent: (event: AppEvent) => void;
|
||||||
let render: DiRender;
|
let render: DiRender;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -125,6 +128,12 @@ describe("<Catalog />", () => {
|
|||||||
|
|
||||||
di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry);
|
di.override(catalogEntityRegistryInjectable, () => catalogEntityRegistry);
|
||||||
|
|
||||||
|
emitEvent = jest.fn();
|
||||||
|
|
||||||
|
di.override(appEventBusInjectable, () => ({
|
||||||
|
emit: emitEvent,
|
||||||
|
}));
|
||||||
|
|
||||||
catalogEntityStore = di.inject(catalogEntityStoreInjectable);
|
catalogEntityStore = di.inject(catalogEntityStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -320,4 +329,39 @@ describe("<Catalog />", () => {
|
|||||||
|
|
||||||
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
|
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 type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
|
||||||
import customCategoryViewsInjectable from "./custom-views.injectable";
|
import customCategoryViewsInjectable from "./custom-views.injectable";
|
||||||
import type { CustomCategoryViewComponents } from "./custom-views";
|
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> {}
|
export interface CatalogProps extends RouteComponentProps<CatalogViewRouteParam> {}
|
||||||
|
|
||||||
@ -41,6 +43,7 @@ interface Dependencies {
|
|||||||
catalogEntityStore: CatalogEntityStore;
|
catalogEntityStore: CatalogEntityStore;
|
||||||
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
|
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
|
||||||
customCategoryViews: IComputedValue<Map<string, Map<string, RegisteredCustomCategoryViewDecl>>>;
|
customCategoryViews: IComputedValue<Map<string, Map<string, RegisteredCustomCategoryViewDecl>>>;
|
||||||
|
emitEvent: (event: AppEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -101,6 +104,11 @@ class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.props.emitEvent({
|
||||||
|
name: "catalog",
|
||||||
|
action: "open",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addToHotbar(entity: CatalogEntity): void {
|
addToHotbar(entity: CatalogEntity): void {
|
||||||
@ -143,6 +151,14 @@ class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
|
|||||||
onTabChange = action((tabId: string | null) => {
|
onTabChange = action((tabId: string | null) => {
|
||||||
const activeCategory = this.categories.find(category => category.getId() === tabId);
|
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) {
|
if (activeCategory) {
|
||||||
navigate(catalogURL({ params: { group: activeCategory.spec.group, kind: activeCategory.spec.names.kind }}));
|
navigate(catalogURL({ params: { group: activeCategory.spec.group, kind: activeCategory.spec.names.kind }}));
|
||||||
} else {
|
} else {
|
||||||
@ -280,6 +296,7 @@ export const Catalog = withInjectables<Dependencies, CatalogProps>( NonInjectedC
|
|||||||
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
|
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
|
||||||
getCategoryColumns: di.inject(getCategoryColumnsInjectable),
|
getCategoryColumns: di.inject(getCategoryColumnsInjectable),
|
||||||
customCategoryViews: di.inject(customCategoryViewsInjectable),
|
customCategoryViews: di.inject(customCategoryViewsInjectable),
|
||||||
|
emitEvent: di.inject(appEventBusInjectable).emit,
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -134,7 +134,7 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
|
|||||||
})}
|
})}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|
||||||
<DrawerTitle title="Metrics"/>
|
<DrawerTitle>Metrics</DrawerTitle>
|
||||||
<div className="metrics">
|
<div className="metrics">
|
||||||
{this.renderMetrics()}
|
{this.renderMetrics()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export class ConfigMapDetails extends React.Component<ConfigMapDetailsProps> {
|
|||||||
{
|
{
|
||||||
data.length > 0 && (
|
data.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Data"/>
|
<DrawerTitle>Data</DrawerTitle>
|
||||||
{
|
{
|
||||||
data.map(([name, value]) => (
|
data.map(([name, value]) => (
|
||||||
<div key={name} className="data">
|
<div key={name} className="data">
|
||||||
|
|||||||
@ -82,7 +82,7 @@ export class ResourceQuotaDetails extends React.Component<ResourceQuotaDetailsPr
|
|||||||
|
|
||||||
{quota.getScopeSelector().length > 0 && (
|
{quota.getScopeSelector().length > 0 && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Scope Selector"/>
|
<DrawerTitle>Scope Selector</DrawerTitle>
|
||||||
<Table className="paths">
|
<Table className="paths">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableCell>Operator</TableCell>
|
<TableCell>Operator</TableCell>
|
||||||
|
|||||||
@ -116,7 +116,7 @@ export class SecretDetails extends React.Component<SecretDetailsProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Data" />
|
<DrawerTitle>Data</DrawerTitle>
|
||||||
{secrets.map(this.renderSecret)}
|
{secrets.map(this.renderSecret)}
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
|
|||||||
@ -92,7 +92,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerTitle title="Names"/>
|
<DrawerTitle>Names</DrawerTitle>
|
||||||
<Table selectable className="names box grow">
|
<Table selectable className="names box grow">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableCell>plural</TableCell>
|
<TableCell>plural</TableCell>
|
||||||
@ -109,7 +109,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
|
|||||||
</Table>
|
</Table>
|
||||||
{printerColumns.length > 0 &&
|
{printerColumns.length > 0 &&
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Additional Printer Columns"/>
|
<DrawerTitle>Additional Printer Columns</DrawerTitle>
|
||||||
<Table selectable className="printer-columns box grow">
|
<Table selectable className="printer-columns box grow">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableCell className="name">Name</TableCell>
|
<TableCell className="name">Name</TableCell>
|
||||||
@ -136,7 +136,7 @@ export class CRDDetails extends React.Component<CRDDetailsProps> {
|
|||||||
}
|
}
|
||||||
{validation &&
|
{validation &&
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Validation"/>
|
<DrawerTitle>Validation</DrawerTitle>
|
||||||
<MonacoEditor
|
<MonacoEditor
|
||||||
readOnly
|
readOnly
|
||||||
value={validation}
|
value={validation}
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export class EventDetails extends React.Component<EventDetailsProps> {
|
|||||||
<span className={kebabCase(type)}>{type}</span>
|
<span className={kebabCase(type)}>{type}</span>
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|
||||||
<DrawerTitle title="Involved object"/>
|
<DrawerTitle>Involved object</DrawerTitle>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableCell>Name</TableCell>
|
<TableCell>Name</TableCell>
|
||||||
|
|||||||
@ -113,7 +113,7 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="values">
|
<div className="values">
|
||||||
<DrawerTitle title="Values" />
|
<DrawerTitle>Values</DrawerTitle>
|
||||||
<div className="flex column gaps">
|
<div className="flex column gaps">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="User-supplied values only"
|
label="User-supplied values only"
|
||||||
@ -238,9 +238,9 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
|||||||
/>
|
/>
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{this.renderValues()}
|
{this.renderValues()}
|
||||||
<DrawerTitle title="Notes"/>
|
<DrawerTitle>Notes</DrawerTitle>
|
||||||
{this.renderNotes()}
|
{this.renderNotes()}
|
||||||
<DrawerTitle title="Resources"/>
|
<DrawerTitle>Resources</DrawerTitle>
|
||||||
{resources && this.renderResources(resources)}
|
{resources && this.renderResources(resources)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export class EndpointDetails extends React.Component<EndpointDetailsProps> {
|
|||||||
return (
|
return (
|
||||||
<div className="EndpointDetails">
|
<div className="EndpointDetails">
|
||||||
<KubeObjectMeta object={endpoint}/>
|
<KubeObjectMeta object={endpoint}/>
|
||||||
<DrawerTitle title="Subsets"/>
|
<DrawerTitle>Subsets</DrawerTitle>
|
||||||
{
|
{
|
||||||
endpoint.getEndpointSubsets().map((subset) => (
|
endpoint.getEndpointSubsets().map((subset) => (
|
||||||
<EndpointSubsetList key={subset.toString()} subset={subset} endpoint={endpoint} />
|
<EndpointSubsetList key={subset.toString()} subset={subset} endpoint={endpoint} />
|
||||||
|
|||||||
@ -160,10 +160,10 @@ export class IngressDetails extends React.Component<IngressDetailsProps> {
|
|||||||
{serviceName}:{servicePort}
|
{serviceName}:{servicePort}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
}
|
}
|
||||||
<DrawerTitle title="Rules"/>
|
<DrawerTitle>Rules</DrawerTitle>
|
||||||
{this.renderPaths(ingress)}
|
{this.renderPaths(ingress)}
|
||||||
|
|
||||||
<DrawerTitle title="Load-Balancer Ingress Points"/>
|
<DrawerTitle>Load-Balancer Ingress Points</DrawerTitle>
|
||||||
{this.renderIngressPoints(ingressPoints)}
|
{this.renderIngressPoints(ingressPoints)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -170,7 +170,7 @@ export class NetworkPolicyDetails extends React.Component<NetworkPolicyDetailsPr
|
|||||||
|
|
||||||
{ingress && (
|
{ingress && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Ingress"/>
|
<DrawerTitle>Ingress</DrawerTitle>
|
||||||
{ingress.map((ingress, i) => (
|
{ingress.map((ingress, i) => (
|
||||||
<div key={i} data-testid={`ingress-${i}`}>
|
<div key={i} data-testid={`ingress-${i}`}>
|
||||||
{this.renderNetworkPolicyPorts(ingress.ports)}
|
{this.renderNetworkPolicyPorts(ingress.ports)}
|
||||||
@ -182,7 +182,7 @@ export class NetworkPolicyDetails extends React.Component<NetworkPolicyDetailsPr
|
|||||||
|
|
||||||
{egress && (
|
{egress && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Egress"/>
|
<DrawerTitle>Egress</DrawerTitle>
|
||||||
{egress.map((egress, i) => (
|
{egress.map((egress, i) => (
|
||||||
<div key={i} data-testid={`egress-${i}`}>
|
<div key={i} data-testid={`egress-${i}`}>
|
||||||
{this.renderNetworkPolicyPorts(egress.ports)}
|
{this.renderNetworkPolicyPorts(egress.ports)}
|
||||||
|
|||||||
@ -86,7 +86,7 @@ class NonInjectedServiceDetails extends React.Component<ServiceDetailsProps & De
|
|||||||
{spec.sessionAffinity}
|
{spec.sessionAffinity}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|
||||||
<DrawerTitle title="Connection"/>
|
<DrawerTitle>Connection</DrawerTitle>
|
||||||
|
|
||||||
<DrawerItem name="Cluster IP">
|
<DrawerItem name="Cluster IP">
|
||||||
{spec.clusterIP}
|
{spec.clusterIP}
|
||||||
@ -129,7 +129,7 @@ class NonInjectedServiceDetails extends React.Component<ServiceDetailsProps & De
|
|||||||
{spec.loadBalancerIP}
|
{spec.loadBalancerIP}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
)}
|
)}
|
||||||
<DrawerTitle title="Endpoint"/>
|
<DrawerTitle>Endpoint</DrawerTitle>
|
||||||
|
|
||||||
<ServiceDetailsEndpoint endpoint={endpoint}/>
|
<ServiceDetailsEndpoint endpoint={endpoint}/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -169,9 +169,9 @@ class NonInjectedNodeDetails extends React.Component<NodeDetailsProps & Dependen
|
|||||||
}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
}
|
}
|
||||||
<DrawerTitle title="Capacity"/>
|
<DrawerTitle>Capacity</DrawerTitle>
|
||||||
<NodeDetailsResources node={node} type={"capacity"}/>
|
<NodeDetailsResources node={node} type={"capacity"}/>
|
||||||
<DrawerTitle title="Allocatable"/>
|
<DrawerTitle>Allocatable</DrawerTitle>
|
||||||
<NodeDetailsResources node={node} type={"allocatable"}/>
|
<NodeDetailsResources node={node} type={"allocatable"}/>
|
||||||
<PodDetailsList
|
<PodDetailsList
|
||||||
pods={childPods}
|
pods={childPods}
|
||||||
|
|||||||
@ -28,26 +28,23 @@ interface RuleGroup {
|
|||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyDetailsProps> {
|
export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyDetailsProps> {
|
||||||
renderRuleGroup( title: React.ReactNode, group: RuleGroup) {
|
renderRuleGroup = (title: React.ReactNode, { rule, ranges }: RuleGroup) => (
|
||||||
if (!group) return null;
|
|
||||||
const { rule, ranges } = group;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title={title}/>
|
<DrawerTitle>{title}</DrawerTitle>
|
||||||
<DrawerItem name="Rule">
|
<DrawerItem name="Rule">
|
||||||
{rule}
|
{rule}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{ranges && (
|
{ranges && (
|
||||||
<DrawerItem name="Ranges (Min-Max)" labelsOnly>
|
<DrawerItem name="Ranges (Min-Max)" labelsOnly>
|
||||||
{ranges.map(({ min, max }, index) => {
|
{ranges.map(({ min, max }, index) => (
|
||||||
return <Badge key={index} label={`${min} - ${max}`}/>;
|
<Badge
|
||||||
})}
|
key={index}
|
||||||
|
label={`${min} - ${max}`} />
|
||||||
|
))}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object: psp } = this.props;
|
const { object: psp } = this.props;
|
||||||
@ -161,7 +158,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
|
|||||||
|
|
||||||
{allowedHostPaths && (
|
{allowedHostPaths && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Allowed Host Paths"/>
|
<DrawerTitle>Allowed Host Paths</DrawerTitle>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableCell>Path Prefix</TableCell>
|
<TableCell>Path Prefix</TableCell>
|
||||||
@ -179,14 +176,14 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.renderRuleGroup("Fs Group", fsGroup)}
|
{fsGroup && this.renderRuleGroup("Fs Group", fsGroup)}
|
||||||
{this.renderRuleGroup("Run As Group", runAsGroup)}
|
{runAsGroup && this.renderRuleGroup("Run As Group", runAsGroup)}
|
||||||
{this.renderRuleGroup("Run As User", runAsUser)}
|
{runAsUser && this.renderRuleGroup("Run As User", runAsUser)}
|
||||||
{this.renderRuleGroup("Supplemental Groups", supplementalGroups)}
|
{supplementalGroups && this.renderRuleGroup("Supplemental Groups", supplementalGroups)}
|
||||||
|
|
||||||
{runtimeClass && (
|
{runtimeClass && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Runtime Class"/>
|
<DrawerTitle>Runtime Class</DrawerTitle>
|
||||||
<DrawerItem name="Allowed Runtime Class Names">
|
<DrawerItem name="Allowed Runtime Class Names">
|
||||||
{runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"}
|
{runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
@ -198,7 +195,7 @@ export class PodSecurityPolicyDetails extends React.Component<PodSecurityPolicyD
|
|||||||
|
|
||||||
{seLinux && (
|
{seLinux && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Se Linux"/>
|
<DrawerTitle>Se Linux</DrawerTitle>
|
||||||
<DrawerItem name="Rule">
|
<DrawerItem name="Rule">
|
||||||
{seLinux.rule}
|
{seLinux.rule}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|||||||
@ -80,7 +80,7 @@ class NonInjectedStorageClassDetails extends React.Component<StorageClassDetails
|
|||||||
)}
|
)}
|
||||||
{parameters && (
|
{parameters && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Parameters"/>
|
<DrawerTitle>Parameters</DrawerTitle>
|
||||||
{
|
{
|
||||||
Object.entries(parameters).map(([name, value]) => (
|
Object.entries(parameters).map(([name, value]) => (
|
||||||
<DrawerItem key={name + value} name={startCase(name)}>
|
<DrawerItem key={name + value} name={startCase(name)}>
|
||||||
|
|||||||
@ -102,7 +102,7 @@ export class PersistentVolumeClaimDetails extends React.Component<PersistentVolu
|
|||||||
{volumeClaim.getStatus()}
|
{volumeClaim.getStatus()}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|
||||||
<DrawerTitle title="Selector"/>
|
<DrawerTitle>Selector</DrawerTitle>
|
||||||
|
|
||||||
<DrawerItem name="Match Labels" labelsOnly>
|
<DrawerItem name="Match Labels" labelsOnly>
|
||||||
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)}
|
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)}
|
||||||
|
|||||||
@ -67,7 +67,7 @@ export class VolumeDetailsList extends React.Component<VolumeDetailsListProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="VolumeDetailsList flex column">
|
<div className="VolumeDetailsList flex column">
|
||||||
<DrawerTitle title="Persistent Volumes"/>
|
<DrawerTitle>Persistent Volumes</DrawerTitle>
|
||||||
<Table
|
<Table
|
||||||
tableId="storage_volume_details_list"
|
tableId="storage_volume_details_list"
|
||||||
items={persistentVolumes}
|
items={persistentVolumes}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
|
|||||||
|
|
||||||
{nfs && (
|
{nfs && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Network File System"/>
|
<DrawerTitle>Network File System</DrawerTitle>
|
||||||
{
|
{
|
||||||
Object.entries(nfs).map(([name, value]) => (
|
Object.entries(nfs).map(([name, value]) => (
|
||||||
<DrawerItem key={name} name={startCase(name)}>
|
<DrawerItem key={name} name={startCase(name)}>
|
||||||
@ -78,7 +78,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
|
|||||||
|
|
||||||
{flexVolume && (
|
{flexVolume && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="FlexVolume"/>
|
<DrawerTitle>FlexVolume</DrawerTitle>
|
||||||
<DrawerItem name="Driver">
|
<DrawerItem name="Driver">
|
||||||
{flexVolume.driver}
|
{flexVolume.driver}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
@ -94,7 +94,7 @@ export class PersistentVolumeDetails extends React.Component<PersistentVolumeDet
|
|||||||
|
|
||||||
{claimRef && (
|
{claimRef && (
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Claim"/>
|
<DrawerTitle>Claim</DrawerTitle>
|
||||||
<DrawerItem name="Type">
|
<DrawerItem name="Type">
|
||||||
{claimRef.kind}
|
{claimRef.kind}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
|
|||||||
@ -68,7 +68,7 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
|
|||||||
<div className="ClusterRoleBindingDetails">
|
<div className="ClusterRoleBindingDetails">
|
||||||
<KubeObjectMeta object={clusterRoleBinding} />
|
<KubeObjectMeta object={clusterRoleBinding} />
|
||||||
|
|
||||||
<DrawerTitle title="Reference" />
|
<DrawerTitle>Reference</DrawerTitle>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableCell>Kind</TableCell>
|
<TableCell>Kind</TableCell>
|
||||||
@ -82,7 +82,7 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<DrawerTitle title="Bindings" />
|
<DrawerTitle>Bindings</DrawerTitle>
|
||||||
{subjects.length > 0 && (
|
{subjects.length > 0 && (
|
||||||
<Table selectable className="bindings box grow">
|
<Table selectable className="bindings box grow">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export class ClusterRoleDetails extends React.Component<ClusterRoleDetailsProps>
|
|||||||
<div className="ClusterRoleDetails">
|
<div className="ClusterRoleDetails">
|
||||||
<KubeObjectMeta object={clusterRole}/>
|
<KubeObjectMeta object={clusterRole}/>
|
||||||
|
|
||||||
<DrawerTitle title="Rules"/>
|
<DrawerTitle>Rules</DrawerTitle>
|
||||||
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
|
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
|
||||||
return (
|
return (
|
||||||
<div className="rule" key={index}>
|
<div className="rule" key={index}>
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
|
|||||||
<div className="RoleBindingDetails">
|
<div className="RoleBindingDetails">
|
||||||
<KubeObjectMeta object={roleBinding} />
|
<KubeObjectMeta object={roleBinding} />
|
||||||
|
|
||||||
<DrawerTitle title="Reference" />
|
<DrawerTitle>Reference</DrawerTitle>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableCell>Kind</TableCell>
|
<TableCell>Kind</TableCell>
|
||||||
@ -78,7 +78,7 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<DrawerTitle title="Bindings" />
|
<DrawerTitle>Bindings</DrawerTitle>
|
||||||
{subjects.length > 0 && (
|
{subjects.length > 0 && (
|
||||||
<Table selectable className="bindings box grow">
|
<Table selectable className="bindings box grow">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export class RoleDetails extends React.Component<RoleDetailsProps> {
|
|||||||
return (
|
return (
|
||||||
<div className="RoleDetails">
|
<div className="RoleDetails">
|
||||||
<KubeObjectMeta object={role}/>
|
<KubeObjectMeta object={role}/>
|
||||||
<DrawerTitle title="Rules"/>
|
<DrawerTitle>Rules</DrawerTitle>
|
||||||
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
|
{rules.map(({ resourceNames, apiGroups, resources, verbs }, index) => {
|
||||||
return (
|
return (
|
||||||
<div className="rule" key={index}>
|
<div className="rule" key={index}>
|
||||||
|
|||||||
@ -143,7 +143,7 @@ export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetai
|
|||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
}
|
}
|
||||||
|
|
||||||
<DrawerTitle title="Mountable secrets"/>
|
<DrawerTitle>Mountable secrets</DrawerTitle>
|
||||||
<div className="secrets">
|
<div className="secrets">
|
||||||
{this.renderSecrets()}
|
{this.renderSecrets()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -78,7 +78,7 @@ class NonInjectedCronJobDetails extends React.Component<CronJobDetailsProps & De
|
|||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{childJobs.length > 0 &&
|
{childJobs.length > 0 &&
|
||||||
<>
|
<>
|
||||||
<DrawerTitle title="Jobs"/>
|
<DrawerTitle>Jobs</DrawerTitle>
|
||||||
{childJobs.map((job: Job) => {
|
{childJobs.map((job: Job) => {
|
||||||
const selectors = job.getSelectors();
|
const selectors = job.getSelectors();
|
||||||
const condition = job.getCondition();
|
const condition = job.getCondition();
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export class DeploymentReplicaSets extends React.Component<DeploymentReplicaSets
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ReplicaSets flex column">
|
<div className="ReplicaSets flex column">
|
||||||
<DrawerTitle title="Deploy Revisions"/>
|
<DrawerTitle>Deploy Revisions</DrawerTitle>
|
||||||
<Table
|
<Table
|
||||||
selectable
|
selectable
|
||||||
tableId="deployment_replica_sets_view"
|
tableId="deployment_replica_sets_view"
|
||||||
|
|||||||
@ -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 (
|
return (
|
||||||
<div className="PodDetailsList flex column">
|
<div className="PodDetailsList flex column">
|
||||||
<DrawerTitle title="Pods" />
|
<DrawerTitle>Pods</DrawerTitle>
|
||||||
<Table
|
<Table
|
||||||
tableId="workloads_pod_details_list"
|
tableId="workloads_pod_details_list"
|
||||||
items={pods}
|
items={pods}
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
import "./pod-details-secrets.scss";
|
import "./pod-details-secrets.scss";
|
||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { autorun, observable, makeObservable } from "mobx";
|
import { reaction } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Pod, Secret, secretsApi } from "../../../common/k8s-api/endpoints";
|
import { Pod, Secret, secretsApi } from "../../../common/k8s-api/endpoints";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
|
||||||
@ -16,59 +16,50 @@ export interface PodDetailsSecretsProps {
|
|||||||
pod: Pod;
|
pod: Pod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
export const PodDetailsSecrets = observer(({ pod }: PodDetailsSecretsProps) => {
|
||||||
export class PodDetailsSecrets extends Component<PodDetailsSecretsProps> {
|
const [secrets, setSecrets] = useState(new Map<string, Secret>());
|
||||||
@observable secrets: Map<string, Secret> = observable.map<string, Secret>();
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
useEffect(() => (
|
||||||
disposeOnUnmount(this, [
|
reaction(
|
||||||
autorun(async () => {
|
() => pod.getSecrets(),
|
||||||
const { pod } = this.props;
|
async (secretNames) => {
|
||||||
|
const results = await Promise.allSettled(
|
||||||
const secrets = await Promise.all(
|
secretNames.map(secretName => secretsApi.get({
|
||||||
pod.getSecrets().map(secretName => secretsApi.get({
|
|
||||||
name: secretName,
|
name: secretName,
|
||||||
namespace: pod.getNs(),
|
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]),
|
||||||
constructor(props: PodDetailsSecretsProps) {
|
));
|
||||||
super(props);
|
},
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { pod } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="PodDetailsSecrets">
|
|
||||||
{
|
{
|
||||||
pod.getSecrets().map(secretName => {
|
fireImmediately: true,
|
||||||
const secret = this.secrets.get(secretName);
|
|
||||||
|
|
||||||
if (secret) {
|
|
||||||
return this.renderSecretLink(secret);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<span key={secretName}>{secretName}</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
), []);
|
||||||
</div>
|
|
||||||
);
|
const renderSecret = (name: string) => {
|
||||||
|
const secret = secrets.get(name);
|
||||||
|
|
||||||
|
if (!secret) {
|
||||||
|
return <span key={name}>{name}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderSecretLink(secret: Secret) {
|
|
||||||
return (
|
return (
|
||||||
<Link key={secret.getId()} to={getDetailsUrl(secret.selfLink)}>
|
<Link key={secret.getId()} to={getDetailsUrl(secret.selfLink)}>
|
||||||
{secret.getName()}
|
{secret.getName()}
|
||||||
</Link>
|
</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 { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { observable, reaction, makeObservable } from "mobx";
|
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 { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { boundMethod, cssNames, toJS } from "../../utils";
|
import { boundMethod, cssNames, toJS } from "../../utils";
|
||||||
import { PodDetailsContainer } from "./pod-details-container";
|
import { PodDetailsContainer } from "./pod-details-container";
|
||||||
import { PodDetailsAffinities } from "./pod-details-affinities";
|
import { PodDetailsAffinities } from "./pod-details-affinities";
|
||||||
import { PodDetailsTolerations } from "./pod-details-tolerations";
|
import { PodDetailsTolerations } from "./pod-details-tolerations";
|
||||||
import { Icon } from "../icon";
|
|
||||||
import { PodDetailsSecrets } from "./pod-details-secrets";
|
import { PodDetailsSecrets } from "./pod-details-secrets";
|
||||||
import { ResourceMetrics } from "../resource-metrics";
|
import { ResourceMetrics } from "../resource-metrics";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
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 { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { PodVolumes } from "./details/volumes/view";
|
||||||
|
|
||||||
export interface PodDetailsProps extends KubeObjectDetailsProps<Pod> {
|
export interface PodDetailsProps extends KubeObjectDetailsProps<Pod> {
|
||||||
}
|
}
|
||||||
@ -73,12 +73,13 @@ export class PodDetails extends React.Component<PodDetailsProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { status, spec } = pod;
|
const { status, spec } = pod;
|
||||||
const { conditions, podIP } = status;
|
const { conditions = [], podIP } = status ?? {};
|
||||||
const podIPs = pod.getIPs();
|
const podIPs = pod.getIPs();
|
||||||
const { nodeName } = spec;
|
const { nodeName } = spec ?? {};
|
||||||
const nodeSelector = pod.getNodeSelectors();
|
const nodeSelector = pod.getNodeSelectors();
|
||||||
const volumes = pod.getVolumes();
|
|
||||||
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Pod);
|
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Pod);
|
||||||
|
const initContainers = pod.getInitContainers();
|
||||||
|
const containers = pod.getContainers();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PodDetails">
|
<div className="PodDetails">
|
||||||
@ -90,26 +91,22 @@ export class PodDetails extends React.Component<PodDetailsProps> {
|
|||||||
<PodCharts/>
|
<PodCharts/>
|
||||||
</ResourceMetrics>
|
</ResourceMetrics>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<KubeObjectMeta object={pod}/>
|
<KubeObjectMeta object={pod}/>
|
||||||
|
|
||||||
<DrawerItem name="Status">
|
<DrawerItem name="Status">
|
||||||
<span className={cssNames("status", kebabCase(pod.getStatusMessage()))}>{pod.getStatusMessage()}</span>
|
<span className={cssNames("status", kebabCase(pod.getStatusMessage()))}>{pod.getStatusMessage()}</span>
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Node">
|
<DrawerItem name="Node" hidden={!nodeName}>
|
||||||
{nodeName && (
|
|
||||||
<Link to={getDetailsUrl(nodesApi.getUrl({ name: nodeName }))}>
|
<Link to={getDetailsUrl(nodesApi.getUrl({ name: nodeName }))}>
|
||||||
{nodeName}
|
{nodeName}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Pod IP">
|
<DrawerItem name="Pod IP">
|
||||||
{podIP}
|
{podIP}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Pod IPs" hidden={!podIPs.length} labelsOnly>
|
<DrawerItem name="Pod IPs" hidden={podIPs.length === 0} labelsOnly>
|
||||||
{
|
{podIPs.map(label => <Badge key={label} label={label} />)}
|
||||||
podIPs.map(label => (
|
|
||||||
<Badge key={label} label={label}/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Priority Class">
|
<DrawerItem name="Priority Class">
|
||||||
{pod.getPriorityClassName()}
|
{pod.getPriorityClassName()}
|
||||||
@ -117,129 +114,55 @@ export class PodDetails extends React.Component<PodDetailsProps> {
|
|||||||
<DrawerItem name="QoS Class">
|
<DrawerItem name="QoS Class">
|
||||||
{pod.getQosClass()}
|
{pod.getQosClass()}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{conditions &&
|
|
||||||
<DrawerItem name="Conditions" className="conditions" labelsOnly>
|
|
||||||
{
|
|
||||||
conditions.map(condition => {
|
|
||||||
const { type, status, lastTransitionTime } = condition;
|
|
||||||
|
|
||||||
return (
|
<DrawerItem name="Conditions" className="conditions" hidden={conditions.length === 0} labelsOnly>
|
||||||
|
{
|
||||||
|
conditions.map(({ type, status, lastTransitionTime }) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={type}
|
key={type}
|
||||||
label={type}
|
label={type}
|
||||||
disabled={status === "False"}
|
disabled={status === "False"}
|
||||||
tooltip={`Last transition time: ${lastTransitionTime}`}
|
tooltip={`Last transition time: ${lastTransitionTime}`}
|
||||||
/>
|
/>
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</DrawerItem>
|
|
||||||
}
|
|
||||||
{nodeSelector.length > 0 &&
|
|
||||||
<DrawerItem name="Node Selector">
|
|
||||||
{
|
|
||||||
nodeSelector.map(label => (
|
|
||||||
<Badge key={label} label={label}/>
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
}
|
|
||||||
|
<DrawerItem name="Node Selector" hidden={nodeSelector.length === 0}>
|
||||||
|
{nodeSelector.map(label => <Badge key={label} label={label} />)}
|
||||||
|
</DrawerItem>
|
||||||
|
|
||||||
<PodDetailsTolerations workload={pod}/>
|
<PodDetailsTolerations workload={pod}/>
|
||||||
<PodDetailsAffinities workload={pod}/>
|
<PodDetailsAffinities workload={pod}/>
|
||||||
|
|
||||||
{pod.getSecrets().length > 0 && (
|
<DrawerItem name="Secrets" hidden={pod.getSecrets().length === 0}>
|
||||||
<DrawerItem name="Secrets">
|
|
||||||
<PodDetailsSecrets pod={pod}/>
|
<PodDetailsSecrets pod={pod}/>
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
)}
|
|
||||||
|
|
||||||
{pod.getInitContainers() && pod.getInitContainers().length > 0 &&
|
{initContainers.length > 0 && (
|
||||||
<DrawerTitle title="Init Containers"/>
|
<>
|
||||||
}
|
<DrawerTitle>Init Containers</DrawerTitle>
|
||||||
{
|
{initContainers.map(container => (
|
||||||
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 (
|
|
||||||
<PodDetailsContainer
|
<PodDetailsContainer
|
||||||
key={name}
|
key={container.name}
|
||||||
pod={pod}
|
pod={pod}
|
||||||
container={container}
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,9 +7,9 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@import "~flex.box"; // todo: replace with tailwind's flexbox classes
|
@import "~flex.box";
|
||||||
@import "./fonts";
|
|
||||||
@import "../themes/theme-vars";
|
@import "../themes/theme-vars";
|
||||||
|
@import "./fonts";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--unit: 8px;
|
--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 { title, variant = "rounded", size = 32, colorHash, children, background, imgProps, src, className, disabled, ...rest } = props;
|
||||||
|
|
||||||
const getBackgroundColor = () => {
|
const getBackgroundColor = () => {
|
||||||
|
if (src) {
|
||||||
|
return "transparent";
|
||||||
|
}
|
||||||
|
|
||||||
return background || randomColor({ seed: colorHash, luminosity: "dark" });
|
return background || randomColor({ seed: colorHash, luminosity: "dark" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -81,7 +81,7 @@
|
|||||||
.close {
|
.close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
width: 5ch;
|
width: 4ch;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: linear-gradient(90deg, transparent 0%, var(--color-active) 25%);
|
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;
|
const obj = this.resource;
|
||||||
|
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
if (store.isLoaded) {
|
if (store?.isLoaded) {
|
||||||
// auto-close tab when resource removed from store
|
// auto-close tab when resource removed from store
|
||||||
this.props.closeTab(this.props.tab.id);
|
this.props.closeTab(this.props.tab.id);
|
||||||
} else if (!store.isLoading) {
|
} else if (!store.isLoading) {
|
||||||
|
|||||||
@ -57,8 +57,9 @@ const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStore
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const overlay = document.querySelector(".PodLogs .list span.active");
|
const overlay = document.querySelector(".PodLogs .list span.active");
|
||||||
|
|
||||||
if (!overlay) return;
|
if (typeof overlay?.scrollIntoViewIfNeeded === "function") {
|
||||||
overlay.scrollIntoViewIfNeeded();
|
overlay.scrollIntoViewIfNeeded();
|
||||||
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.TerminalTab {
|
.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