diff --git a/src/common/workspace-store.ts b/src/common/workspace-store.ts index 6afee4878e..7d67bd4d35 100644 --- a/src/common/workspace-store.ts +++ b/src/common/workspace-store.ts @@ -41,10 +41,19 @@ export class WorkspaceStore extends BaseStore { return Array.from(this.workspaces.values()); } + isDefault(id: WorkspaceId) { + return id === WorkspaceStore.defaultId; + } + getById(id: WorkspaceId): Workspace { return this.workspaces.get(id); } + @action + setActive(id = WorkspaceStore.defaultId) { + this.currentWorkspaceId = id; + } + @action public saveWorkspace(workspace: Workspace) { const id = workspace.id; @@ -60,11 +69,11 @@ export class WorkspaceStore extends BaseStore { public removeWorkspace(id: WorkspaceId) { const workspace = this.getById(id); if (!workspace) return; - if (id === WorkspaceStore.defaultId) { + if (this.isDefault(id)) { throw new Error("Cannot remove default workspace"); } - if (id === this.currentWorkspaceId) { - this.currentWorkspaceId = WorkspaceStore.defaultId; + if (this.currentWorkspaceId === id) { + this.currentWorkspaceId = WorkspaceStore.defaultId; // reset to default } this.workspaces.delete(id); clusterStore.removeByWorkspaceId(id) diff --git a/src/renderer/components/+add-cluster/add-cluster.scss b/src/renderer/components/+add-cluster/add-cluster.scss index 7b913cd7f2..8dc192d25b 100644 --- a/src/renderer/components/+add-cluster/add-cluster.scss +++ b/src/renderer/components/+add-cluster/add-cluster.scss @@ -1,32 +1,2 @@ .AddCluster { - --flex-gap: #{$padding * 2}; - - position: relative; - padding: $padding * 3; - height: 100%; - display: grid; - grid-template-columns: 1fr 40%; - - > .content { - padding: var(--flex-gap); - margin-right: var(--flex-gap); - background-color: var(--clusters-menu-bgc); - border-radius: $radius; - } - - > .info-panel { - @include hidden-scrollbar; - padding: var(--flex-gap); - border-left: 1px solid #353a3e; - } - - .error { - border-radius: $radius; - padding: $padding; - background-color: pink; - } - - a { - color: $colorInfo; - } } \ No newline at end of file diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 2481275702..821fff2971 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -1,7 +1,7 @@ import "./add-cluster.scss" import path from "path"; import fs from "fs-extra"; -import React from "react"; +import React, { Fragment } from "react"; import { observer } from "mobx-react"; import { computed, observable } from "mobx"; import { Select, SelectOption } from "../select"; @@ -17,6 +17,7 @@ import { clusterStore } from "../../../common/cluster-store"; import { workspaceStore } from "../../../common/workspace-store"; import { v4 as uuid } from "uuid" import { navigation } from "../../navigation"; +import { WizardLayout } from "../layout/wizard-layout"; @observer export class AddCluster extends React.Component { @@ -102,107 +103,110 @@ export class AddCluster extends React.Component { } } + renderInfo() { + return ( + +

Clusters associated with Lens

+

+ Add clusters by clicking the Add Cluster button. + You'll need to obtain a working kubeconfig for the cluster you want to add. +

+

+ Each cluster context is added as a separate item in the + left-side cluster menu + to allow you to operate easily on multiple clusters and/or contexts. +

+

+ For more information on kubeconfig see Kubernetes docs +

+

+ NOTE: Any manually added cluster is not merged into your kubeconfig file. +

+

+ To see your currently enabled config with kubectl, use kubectl config view --minify --raw command in your terminal. +

+

+ When connecting to a cluster, make sure you have a valid and working kubeconfig for the cluster. Following lists known "gotchas" in some authentication types used in kubeconfig with Lens + app. +

+ +

OIDC (OpenID Connect)

+
+
+

+ When connecting Lens to OIDC enabled cluster, there's few things you as a user need to take into account. +

+ Dedicated refresh token +

+ As Lens app utilized kubeconfig is "disconnected" from your main kubeconfig Lens needs to have it's own refresh token it utilizes. + If you share the refresh token with e.g. kubectl who ever uses the token first will invalidate it for the next user. + One way to achieve this is with kubelogin tool by removing the tokens + (both id_token and refresh_token) from + the config and issuing kubelogin command. That'll take you through the login process and will result you having "dedicated" refresh token. +

+
+

Exec auth plugins

+

+ When using exec auth plugins make sure the paths that are used to call + any binaries + are full paths as Lens app might not be able to call binaries with relative paths. Make also sure that you pass all needed information either as arguments or env variables in the config, + Lens app might not have all login shell env variables set automatically. +

+
+ ) + } + render() { return ( -
-
-

Add Cluster

- Select kubeconfig} + value={this.clusterConfig} + options={this.clusterOptions} + onChange={({ value }: SelectOption) => this.clusterConfig = value} + /> +
+ this.showSettings = !this.showSettings}> + Proxy settings + +
+ {this.showSettings && ( +
+ :)`)} + value={this.proxyServer} + onChange={value => this.proxyServer = value} + /> + + HTTP Proxy server. Used for communicating with Kubernetes API. +
- {this.showSettings && ( -
- :)`)} - value={this.proxyServer} - onChange={value => this.proxyServer = value} - /> - - HTTP Proxy server. Used for communicating with Kubernetes API. - -
- )} - {this.isCustom && ( -
-

Kubeconfig:

- this.customConfig = value} - /> -
- )} - {this.error && ( -
{this.error}
- )} -
-
-
-

Clusters associated with Lens

-

- Add clusters by clicking the Add Cluster button. - You'll need to obtain a working kubeconfig for the cluster you want to add. -

-

- Each cluster context is added as a separate item in the - left-side cluster menu - to allow you to operate easily on multiple clusters and/or contexts. -

-

- For more information on kubeconfig see Kubernetes docs -

-

- NOTE: Any manually added cluster is not merged into your kubeconfig file. -

-

- To see your currently enabled config with kubectl, use kubectl config view --minify --raw command in your terminal. -

-

- When connecting to a cluster, make sure you have a valid and working kubeconfig for the cluster. Following lists known "gotchas" in some authentication types used in kubeconfig with Lens - app. -

- -

OIDC (OpenID Connect)

-
-
-

- When connecting Lens to OIDC enabled cluster, there's few things you as a user need to take into account. -

- Dedicated refresh token -

- As Lens app utilized kubeconfig is "disconnected" from your main kubeconfig Lens needs to have it's own refresh token it utilizes. - If you share the refresh token with e.g. kubectl who ever uses the token first will invalidate it for the next user. - One way to achieve this is with kubelogin tool by removing the tokens - (both id_token and refresh_token) from - the config and issuing kubelogin command. That'll take you through the login process and will result you having "dedicated" refresh token. -

-
-

Exec auth plugins

-

- When using exec auth plugins make sure the paths that are used to call - any binaries - are full paths as Lens app might not be able to call binaries with relative paths. Make also sure that you pass all needed information either as arguments or env variables in the config, - Lens app might not have all login shell env variables set automatically. -

-
-
+ ) } } diff --git a/src/renderer/components/+workspaces/workspace-menu.scss b/src/renderer/components/+workspaces/workspace-menu.scss new file mode 100644 index 0000000000..e7adf9ae6a --- /dev/null +++ b/src/renderer/components/+workspaces/workspace-menu.scss @@ -0,0 +1,7 @@ +.WorkspaceMenu { + border-radius: $radius; + + .workspaces-title { + padding: $padding; + } +} \ No newline at end of file diff --git a/src/renderer/components/+workspaces/workspace-menu.tsx b/src/renderer/components/+workspaces/workspace-menu.tsx new file mode 100644 index 0000000000..bcf9c3a74d --- /dev/null +++ b/src/renderer/components/+workspaces/workspace-menu.tsx @@ -0,0 +1,51 @@ +import "./workspace-menu.scss" +import React from "react"; +import { observer } from "mobx-react"; +import { Link } from "react-router-dom"; +import { workspacesURL } from "./workspaces.route"; +import { Trans } from "@lingui/macro"; +import { Menu, MenuItem, MenuProps } from "../menu"; +import { Icon } from "../icon"; +import { observable } from "mobx"; +import { workspaceStore } from "../../../common/workspace-store"; +import { cssNames } from "../../utils"; + +interface Props extends Partial { +} + +@observer +export class WorkspaceMenu extends React.Component { + @observable menuVisible = false; + + render() { + const { className, ...menuProps } = this.props; + const { workspacesList, currentWorkspace } = workspaceStore; + return ( + this.menuVisible = true} + close={() => this.menuVisible = false} + > + + Workspaces + + {workspacesList.map(({ id: workspaceId, name, description }) => { + return ( + workspaceStore.setActive(workspaceId)} + > + + {name} + + ) + })} + + ) + } +} diff --git a/src/renderer/components/+workspaces/workspaces.tsx b/src/renderer/components/+workspaces/workspaces.tsx index fa6cf28271..5e7dc3089d 100644 --- a/src/renderer/components/+workspaces/workspaces.tsx +++ b/src/renderer/components/+workspaces/workspaces.tsx @@ -1,14 +1,44 @@ import "./workspaces.scss" -import React from "react"; +import React, { Fragment } from "react"; import { observer } from "mobx-react"; +import { Trans } from "@lingui/macro"; +import { WizardLayout } from "../layout/wizard-layout"; +import { workspaceStore } from "../../../common/workspace-store"; @observer export class Workspaces extends React.Component { - render() { + renderInfo() { return ( -
- Workspaces -
+ +

What is a Workspace?

+

Workspaces are used to organize number of clusters into logical groups.

+

A single workspaces contains a list of clusters and their full configuration.

+
+ ) + } + + addWorkspace = () => { + console.log('add workspace') + } + + render() { + const { workspacesList, currentWorkspace } = workspaceStore; + return ( + +

+ Workspaces +

+
+ {workspacesList.map(({ id: workspaceId, name, description }) => { + return ( +
+ {name} + {description} +
+ ) + })} +
+
); } } diff --git a/src/renderer/components/cluster-manager/bottom-bar.scss b/src/renderer/components/cluster-manager/bottom-bar.scss index 7193084180..974585723a 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.scss +++ b/src/renderer/components/cluster-manager/bottom-bar.scss @@ -11,11 +11,3 @@ cursor: pointer; } } - -#workspace-menu { - border-radius: $radius; - - .workspaces-title { - padding: $padding; - } -} \ No newline at end of file diff --git a/src/renderer/components/cluster-manager/bottom-bar.tsx b/src/renderer/components/cluster-manager/bottom-bar.tsx index fe3153ecad..14809618f5 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.tsx +++ b/src/renderer/components/cluster-manager/bottom-bar.tsx @@ -1,56 +1,21 @@ import "./bottom-bar.scss" import React from "react"; -import { observable } from "mobx"; import { observer } from "mobx-react"; -import { Link } from "react-router-dom"; -import { Trans } from "@lingui/macro"; import { Icon } from "../icon"; -import { Menu, MenuItem } from "../menu"; -import { WorkspaceId, workspaceStore } from "../../../common/workspace-store"; -import { workspacesURL } from "../+workspaces"; +import { WorkspaceMenu } from "../+workspaces/workspace-menu"; +import { workspaceStore } from "../../../common/workspace-store"; @observer export class BottomBar extends React.Component { - @observable menuVisible = false; - - selectWorkspace = (workspaceId: WorkspaceId) => { - workspaceStore.currentWorkspaceId = workspaceId; - } - render() { - const { currentWorkspace, workspacesList } = workspaceStore; - const menuId = "workspaces-menu" + const { currentWorkspace } = workspaceStore; return (
{currentWorkspace.name}
- this.menuVisible = true} - close={() => this.menuVisible = false} - > - - Workspaces - - {workspacesList.map(({ id, name, description }) => { - return ( - this.selectWorkspace(id)} - title={description} - > - - {name} - - ) - })} - +
) } diff --git a/src/renderer/components/layout/wizard-layout.scss b/src/renderer/components/layout/wizard-layout.scss new file mode 100644 index 0000000000..08f5e01580 --- /dev/null +++ b/src/renderer/components/layout/wizard-layout.scss @@ -0,0 +1,32 @@ +.WizardLayout { + --flex-gap: #{$padding * 2}; + + position: relative; + padding: $padding * 3; + height: 100%; + display: grid; + grid-template-columns: 1fr 40%; + + > .content-col { + padding: var(--flex-gap); + margin-right: var(--flex-gap); + background-color: var(--clusters-menu-bgc); + border-radius: $radius; + + > .error { + border-radius: $radius; + padding: $padding; + background-color: pink; + } + } + + > .info-col { + @include hidden-scrollbar; + padding: var(--flex-gap); + border-left: 1px solid #353a3e; + } + + a { + color: $colorInfo; + } +} \ No newline at end of file diff --git a/src/renderer/components/layout/wizard-layout.tsx b/src/renderer/components/layout/wizard-layout.tsx new file mode 100644 index 0000000000..107c8feffe --- /dev/null +++ b/src/renderer/components/layout/wizard-layout.tsx @@ -0,0 +1,28 @@ +import "./wizard-layout.scss" +import React from "react"; +import { observer } from "mobx-react"; +import { cssNames, IClassName } from "../../utils"; + +interface Props { + className?: IClassName; + contentClass?: IClassName; + infoPanelClass?: IClassName; + infoPanel?: React.ReactNode; +} + +@observer +export class WizardLayout extends React.Component { + render() { + const { className, contentClass, infoPanelClass, infoPanel, children: content } = this.props; + return ( +
+
+ {content} +
+
+ {infoPanel} +
+
+ ) + } +}