From 0f4248de689daddaf0b71cd00d88776b0c6fc74a Mon Sep 17 00:00:00 2001
From: Alex Andreev
Date: Fri, 7 Aug 2020 15:57:16 +0300
Subject: [PATCH] Fixing Cluster Settings layout (#651)
* A bit of cleaning in Add Cluster page
Signed-off-by: alexfront
* Adding head-col to WizardLayout
Signed-off-by: alexfront
* Fixing Cluster Settings general layout bugs
Signed-off-by: alexfront
* Cluster Status view refactoring
Signed-off-by: alexfront
* Install Metrics component refactoring
Using notifications for error, removed picking
button icon method, simplified button generation.
Signed-off-by: alexfront
* Remove icons / checks from RemoveClusterButton
Signed-off-by: alexfront
* Fixing colorError in Input styles
Signed-off-by: alexfront
* Preventing Input's spellchecking
Signed-off-by: alexfront
* ClusterNameSettings refactoring
Signed-off-by: alexfront
* ClusterWorkspaceSettings refactoring/fixing
Signed-off-by: alexfront
* ClusterProxySetting refactoring
Signed-off-by: alexfront
* ClusterPrometheusSetting refactoring
Signed-off-by: alexfront
* Clean up Removal section
Signed-off-by: alexfront
* Glued InstallMetrics & InstallUserMode into 1 component
Signed-off-by: alexfront
* Removing unused styles in Cluster Settings
Signed-off-by: alexfront
* Cluster Settings styling
Signed-off-by: alexfront
* Adding close button to settings header
Signed-off-by: alexfront
* ClusterHomeDirSetting refactoring
Signed-off-by: alexfront
* FilePicker restyling
Signed-off-by: alexfront
* Fixing Prometheus selector
Signed-off-by: alexfront
* Fixing Hashicon
Passing cluster name instead of cluster id to prevent
icon changing while typing new
cluster name
Signed-off-by: alexfront
* Minor ClusterSettings fixes
Signed-off-by: alexfront
* Increasing opacity for non-interactive icons
Signed-off-by: alexfront
* Keep feature install loading state
Waiting for props to change before
disabling loading state (gray button
width spinner)
Signed-off-by: alexfront
* Remove arrays in disposeOnUnmount()
Signed-off-by: alexfront
* Fix Cluster select behavior
Now clicking cluster icon in sidebar
always leads to / dashboard. And
'Settings' submenu switches active
cluster at first and only the showing
Cluster Settings
Signed-off-by: alexfront
* Using structuralComparator in feature installer
Signed-off-by: alexfront
* Saving input fields on blur
Signed-off-by: alexfront
* Setting Select color same as Input color
Signed-off-by: alexfront
---
src/common/cluster-store.ts | 4 +
.../components/+add-cluster/add-cluster.tsx | 30 ++--
.../+cluster-settings/cluster-settings.scss | 137 +++++++++---------
.../+cluster-settings/cluster-settings.tsx | 44 ++++--
.../components/cluster-home-dir-setting.tsx | 101 ++++---------
.../components/cluster-icon-setting.tsx | 64 ++++----
.../components/cluster-name-setting.tsx | 93 +++---------
.../components/cluster-prometheus-setting.tsx | 55 +++----
.../components/cluster-proxy-setting.tsx | 118 ++++-----------
.../components/cluster-workspace-setting.tsx | 50 +++----
.../components/install-feature.tsx | 93 ++++++++++++
.../components/install-metrics.tsx | 109 --------------
.../components/install-user-mode.tsx | 108 --------------
.../components/remove-cluster-button.tsx | 58 ++------
.../+cluster-settings/components/statuses.ts | 24 ---
.../components/+cluster-settings/features.tsx | 36 ++++-
.../components/+cluster-settings/general.tsx | 2 -
.../components/+cluster-settings/removal.tsx | 12 +-
.../components/+cluster-settings/status.tsx | 37 +++--
.../components/cluster-icon/cluster-icon.scss | 7 +-
.../components/cluster-icon/cluster-icon.tsx | 4 +-
.../cluster-manager/clusters-menu.tsx | 12 +-
.../components/file-picker/file-picker.scss | 19 +--
.../components/file-picker/file-picker.tsx | 6 +-
src/renderer/components/input/input.scss | 2 +-
src/renderer/components/input/input.tsx | 1 +
.../components/layout/wizard-layout.scss | 9 ++
.../components/layout/wizard-layout.tsx | 9 +-
src/renderer/components/select/select.scss | 6 +-
29 files changed, 497 insertions(+), 753 deletions(-)
create mode 100644 src/renderer/components/+cluster-settings/components/install-feature.tsx
delete mode 100644 src/renderer/components/+cluster-settings/components/install-metrics.tsx
delete mode 100644 src/renderer/components/+cluster-settings/components/install-user-mode.tsx
delete mode 100644 src/renderer/components/+cluster-settings/components/statuses.ts
diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts
index e4febf532a..efed98d00a 100644
--- a/src/common/cluster-store.ts
+++ b/src/common/cluster-store.ts
@@ -86,6 +86,10 @@ export class ClusterStore extends BaseStore {
return Array.from(this.clusters.values());
}
+ setActive(id: ClusterId) {
+ this.activeClusterId = id;
+ }
+
hasClusters() {
return this.clusters.size > 0;
}
diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx
index 221f695425..93d28d400a 100644
--- a/src/renderer/components/+add-cluster/add-cluster.tsx
+++ b/src/renderer/components/+add-cluster/add-cluster.tsx
@@ -124,7 +124,7 @@ export class AddCluster extends React.Component {
to allow you to operate easily on multiple clusters and/or contexts.
- For more information on kubeconfig see Kubernetes docs
+ For more information on kubeconfig see Kubernetes docs .
NOTE: Any manually added cluster is not merged into your kubeconfig file.
@@ -137,22 +137,20 @@ export class AddCluster extends React.Component {
app.
- OIDC (OpenID Connect)
+ 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 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
diff --git a/src/renderer/components/+cluster-settings/cluster-settings.scss b/src/renderer/components/+cluster-settings/cluster-settings.scss
index 1ea6926eab..e02a4da5f8 100644
--- a/src/renderer/components/+cluster-settings/cluster-settings.scss
+++ b/src/renderer/components/+cluster-settings/cluster-settings.scss
@@ -1,86 +1,85 @@
.ClusterSettings {
- overflow-y: scroll;
- grid-template-columns: unset;
+ grid-template-columns: unset;
+ padding: 0;
- .info-col {
- display: none;
+ .head-col {
+ justify-content: space-between;
+
+ :nth-child(2) {
+ flex: 1 0 0;
}
- .content-col {
- margin-right: unset;
+ a {
+ text-decoration: none;
+ color: $grey-600;
+ }
+ }
+
+ .info-col {
+ display: none;
+ }
+
+ .content-col {
+ margin: 0;
+ padding-top: $padding * 3;
+ background-color: transparent;
+
+ .SubTitle {
+ text-transform: none;
}
- * {
- margin-top: 40px;
+ .settings-wrapper {
+ margin: 0 auto;
+ width: 60%;
+ min-width: 570px;
+ max-width: 1000px;
- &:first-child {
- margin-top: 0px;
- }
- }
+ > div {
+ margin-top: $margin * 5;
+ }
- h4 {
- margin-top: 20px;
- }
-
- .status-table {
- margin-top: 20px;
- display: grid;
- grid-template-columns: 1fr 3fr;
- grid-gap: 10px;
- }
-
- .loading {
- margin-top: 20px;
- text-align: center;
-
- .Spinner {
- display: inline-block;
- }
- }
-
- .Input,.Select {
- margin-top: 10px;
- }
-
- .Icon:not(.updated):not(.clean) {
- color: #ad0000;
- }
-
- .Icon.updated {
- color: #00dd1d;
- }
-
- .updated {
- animation: updated-name 1s 1;
- animation-fill-mode: forwards;
- animation-delay: 3s;
- }
-
- @keyframes updated-name {
- from {opacity :1;}
- to {opacity :0;}
- }
-
- .center {
- text-align: center;
- }
-
- input[type="text"]::placeholder {
+ .admin-note {
font-size: small;
- color: #707070;
+ opacity: 0.5;
+ margin-left: $margin;
+ }
+
+ .button-area {
+ margin-top: $margin * 2;
+ }
}
- input[type="text"] {
- color: white;
+ .file-loader {
+ margin-top: $margin * 2;
}
- button {
- margin-top: 5px;
+ .hint {
+ font-size: smaller;
+ opacity: 0.8;
+ }
+ }
- .Spinner {
- width: 10px;
- height: 10px;
- border-color: transparent black;
+ .status-table {
+ margin: $margin * 3 0;
+
+ .Table {
+ border: 1px solid var(--drawerSubtitleBackground);
+ border-radius: $radius;
+
+ .TableRow {
+ &:not(:last-of-type) {
+ border-bottom: 1px solid var(--drawerSubtitleBackground);
}
+
+ .value {
+ flex-grow: 2;
+ color: var(--textColorSecondary);
+ }
+ }
}
+ }
+
+ .Input,.Select {
+ margin-top: 10px;
+ }
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/cluster-settings.tsx b/src/renderer/components/+cluster-settings/cluster-settings.tsx
index 777562060b..4ce4dcb379 100644
--- a/src/renderer/components/+cluster-settings/cluster-settings.tsx
+++ b/src/renderer/components/+cluster-settings/cluster-settings.tsx
@@ -1,25 +1,43 @@
-import "./cluster-settings.scss"
+import "./cluster-settings.scss";
+
import React from "react";
+import { Link } from "react-router-dom";
import { observer } from "mobx-react";
-import { Features } from "./features"
-import { Removal } from "./removal"
-import { Status } from "./status"
-import { General } from "./general"
-import { getHostedCluster } from "../../../common/cluster-store"
+import { Features } from "./features";
+import { Removal } from "./removal";
+import { Status } from "./status";
+import { General } from "./general";
+import { getHostedCluster } from "../../../common/cluster-store";
import { WizardLayout } from "../layout/wizard-layout";
+import { ClusterIcon } from "../cluster-icon";
+import { Icon } from "../icon";
@observer
export class ClusterSettings extends React.Component {
render() {
const cluster = getHostedCluster();
-
+ const header = (
+ <>
+
+
{cluster.preferences.clusterName}
+
+
+
+ >
+ );
return (
-
-
-
-
-
+
+
+
+
+
+
+
- )
+ );
}
}
diff --git a/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx
index c8779c692a..f998035b44 100644
--- a/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx
+++ b/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx
@@ -1,86 +1,43 @@
import React from "react";
-import { Cluster } from "../../../../main/cluster";
-import { Input } from "../../input";
-import { Spinner } from "../../spinner";
-import { clusterStore } from "../../../../common/cluster-store"
-import { Icon } from "../../icon";
-import { Tooltip, TooltipPosition } from "../../tooltip";
-import { autobind } from "../../../utils";
-import { TextInputStatus } from "./statuses"
import { observable } from "mobx";
import { observer } from "mobx-react";
+import { Cluster } from "../../../../main/cluster";
+import { Input } from "../../input";
+import { SubTitle } from "../../layout/sub-title";
interface Props {
- cluster: Cluster;
+ cluster: Cluster;
}
@observer
export class ClusterHomeDirSetting extends React.Component {
@observable directory = this.props.cluster.preferences.terminalCWD || "";
- @observable status = TextInputStatus.CLEAN;
- @observable errorText?: string;
+
+ save = () => {
+ this.props.cluster.preferences.terminalCWD = this.directory;
+ };
+
+ onChange = (value: string) => {
+ this.directory = value;
+ }
render() {
- return <>
- Working Directory
- Set initial working directory for terminals. When set it will the `pwd` when a new terminal instance is opened for this cluster.
-
- >;
- }
-
- @autobind()
- onWorkingDirectoryChange(directory: string, _e: React.ChangeEvent) {
- if (this.status === TextInputStatus.UPDATING) {
- console.log("prevent changing cluster directory while updating");
- return;
- }
-
- this.status = this.dirDiffers(directory);
- this.directory = directory;
- }
-
- dirDiffers(directory: string): TextInputStatus {
- const { terminalCWD = "" } = this.props.cluster.preferences;
-
- return directory === terminalCWD ? TextInputStatus.CLEAN : TextInputStatus.DIRTY;
- }
-
- getIconRight(): React.ReactNode {
- switch (this.status) {
- case TextInputStatus.CLEAN:
- return null;
- case TextInputStatus.DIRTY:
- return ;
- case TextInputStatus.UPDATED:
- return ;
- case TextInputStatus.UPDATING:
- return ;
- case TextInputStatus.ERROR:
- return
-
- {this.errorText}
-
-
- }
- }
-
- @autobind()
- onWorkingDirectorySubmit(directory: string) {
- if (this.dirDiffers(directory) !== TextInputStatus.DIRTY) {
- return;
- }
-
- this.status = TextInputStatus.UPDATING
- this.props.cluster.preferences.terminalCWD = directory;
- this.directory = directory;
- this.status = TextInputStatus.UPDATED
+ return (
+ <>
+
+ Terminal working directory.
+
+
+ An explicit start path where the terminal will be launched,{" "}
+ this is used as the current working directory (cwd) for the shell process.
+
+ >
+ );
}
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/cluster-icon-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-icon-setting.tsx
index 2a5f0d0b97..20b4e6d6d5 100644
--- a/src/renderer/components/+cluster-settings/components/cluster-icon-setting.tsx
+++ b/src/renderer/components/+cluster-settings/components/cluster-icon-setting.tsx
@@ -1,16 +1,20 @@
import React from "react";
import { Cluster } from "../../../../main/cluster";
-import { clusterStore } from "../../../../common/cluster-store"
-import { Icon } from "../../icon";
import { FilePicker, OverSizeLimitStyle } from "../../file-picker";
import { autobind } from "../../../utils";
import { Button } from "../../button";
-import { GeneralInputStatus } from "./statuses"
import { observable } from "mobx";
import { observer } from "mobx-react";
+import { SubTitle } from "../../layout/sub-title";
+import { ClusterIcon } from "../../cluster-icon";
+
+enum GeneralInputStatus {
+ CLEAN = "clean",
+ ERROR = "error",
+}
interface Props {
- cluster: Cluster;
+ cluster: Cluster;
}
@observer
@@ -21,7 +25,6 @@ export class ClusterIconSetting extends React.Component {
@autobind()
async onIconPick([file]: File[]) {
const { cluster } = this.props;
-
try {
if (file) {
const buf = Buffer.from(await file.arrayBuffer());
@@ -38,35 +41,36 @@ export class ClusterIconSetting extends React.Component {
}
getClearButton() {
- const { cluster } = this.props;
-
- if (cluster.preferences.icon) {
- return this.onIconPick([])}>Clear
+ if (this.props.cluster.preferences.icon) {
+ return this.onIconPick([])}>Clear
}
}
render() {
- return <>
- Cluster Icon
- Set cluster icon. By default it is automatically generated. {this.getIconRight()}
-
-
+
- {this.getClearButton()}
-
- >;
- }
-
- getIconRight(): React.ReactNode {
- switch (this.status) {
- case GeneralInputStatus.CLEAN:
- return null;
- case GeneralInputStatus.ERROR:
- return
- }
+ {"Browse for new icon..."}
+ >
+ );
+ return (
+ <>
+
+ Define cluster icon. By default automatically generated.
+
+
+ {this.getClearButton()}
+
+ >
+ );
}
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx
index e605864030..8e2f8a2afa 100644
--- a/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx
+++ b/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx
@@ -1,85 +1,40 @@
import React from "react";
import { Cluster } from "../../../../main/cluster";
import { Input } from "../../input";
-import { Spinner } from "../../spinner";
-import { clusterStore } from "../../../../common/cluster-store"
-import { Icon } from "../../icon";
-import { Tooltip, TooltipPosition } from "../../tooltip";
-import { autobind } from "../../../utils";
-import { TextInputStatus } from "./statuses"
import { observable } from "mobx";
import { observer } from "mobx-react";
+import { SubTitle } from "../../layout/sub-title";
+import { isRequired } from "../../input/input.validators";
interface Props {
- cluster: Cluster;
+ cluster: Cluster;
}
@observer
export class ClusterNameSetting extends React.Component {
@observable name = this.props.cluster.preferences.clusterName || "";
- @observable status = TextInputStatus.CLEAN;
- @observable errorText?: string;
+
+ save = () => {
+ this.props.cluster.preferences.clusterName = this.name;
+ };
+
+ onChange = (value: string) => {
+ this.name = value;
+ }
render() {
- return <>
- Cluster Name
- Change cluster name:
-
- >;
- }
-
- @autobind()
- onClusterNameChange(name: string, _e: React.ChangeEvent) {
- if (this.status === TextInputStatus.UPDATING) {
- console.log("prevent changing cluster name while updating");
- return;
- }
-
- this.status = this.nameDiffers(name)
- this.name = name;
- }
-
- nameDiffers(name: string): TextInputStatus {
- const { clusterName } = this.props.cluster.preferences;
-
- return name === clusterName ? TextInputStatus.CLEAN : TextInputStatus.DIRTY;
- }
-
- getIconRight(): React.ReactNode {
- switch (this.status) {
- case TextInputStatus.CLEAN:
- return null;
- case TextInputStatus.DIRTY:
- return ;
- case TextInputStatus.UPDATED:
- return ;
- case TextInputStatus.UPDATING:
- return ;
- case TextInputStatus.ERROR:
- return
-
- {this.errorText}
-
-
- }
- }
-
- @autobind()
- onClusterNameSubmit(name: string) {
- if (this.nameDiffers(name) !== TextInputStatus.DIRTY) {
- return;
- }
-
- this.status = TextInputStatus.UPDATING
- this.props.cluster.preferences.clusterName = name;
- this.name = name;
- this.status = TextInputStatus.UPDATED
+ return (
+ <>
+
+ Define cluster name.
+
+ >
+ );
}
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/cluster-prometheus-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-prometheus-setting.tsx
index 7596cc245b..e4f81bb18a 100644
--- a/src/renderer/components/+cluster-settings/components/cluster-prometheus-setting.tsx
+++ b/src/renderer/components/+cluster-settings/components/cluster-prometheus-setting.tsx
@@ -1,41 +1,44 @@
import React from "react";
-import { Cluster } from "../../../../main/cluster";
-import { clusterStore } from "../../../../common/cluster-store"
-import { Select, SelectOption, SelectProps } from "../../select";
-import { prometheusProviders } from "../../../../common/prometheus-providers";
-import { autobind } from "../../../utils";
-import { observable } from "mobx";
+import merge from "lodash/merge";
import { observer } from "mobx-react";
+import { prometheusProviders } from "../../../../common/prometheus-providers";
+import { Cluster } from "../../../../main/cluster";
+import { SubTitle } from "../../layout/sub-title";
+import { Select, SelectOption } from "../../select";
-const prometheusGuide = "https://github.com/lensapp/lens/blob/master/troubleshooting/custom-prometheus.md";
const options: SelectOption[] = [
- { value: "", label: "Auto detect" },
+ { value: "", label: "Auto detect" },
...prometheusProviders.map(pp => ({value: pp.id, label: pp.name}))
];
interface Props {
- cluster: Cluster;
+ cluster: Cluster;
}
@observer
export class ClusterPrometheusSetting extends React.Component {
- @observable prometheusProvider = this.props.cluster.preferences.prometheusProvider?.type || "";
-
render() {
- return <>
- Cluster Prometheus
- Use pre-installed Prometheus service for metrics. Please refer to this guide for possible configuration changes.
-
- >;
- }
-
- @autobind()
- changePrometheusProvider({ value: prometheusProvider }: SelectProps) {
- this.prometheusProvider = prometheusProvider;
- this.props.cluster.preferences.prometheusProvider = { type: prometheusProvider };
+ return (
+ <>
+
+
+ Use pre-installed Prometheus service for metrics. Please refer to the{" "}
+ guide {" "}
+ for possible configuration changes.
+
+ {
+ const provider = {
+ prometheusProvider: {
+ type: value
+ }
+ }
+ merge(this.props.cluster.preferences, provider);
+ }}
+ options={options}
+ />
+ >
+ );
}
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/cluster-proxy-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-proxy-setting.tsx
index 3ddcf25c7f..1b94992e5b 100644
--- a/src/renderer/components/+cluster-settings/components/cluster-proxy-setting.tsx
+++ b/src/renderer/components/+cluster-settings/components/cluster-proxy-setting.tsx
@@ -1,105 +1,41 @@
import React from "react";
-import { Cluster } from "../../../../main/cluster";
-import { Input } from "../../input";
-import { Spinner } from "../../spinner";
-import { clusterStore } from "../../../../common/cluster-store"
-import { Icon } from "../../icon";
-import { Tooltip, TooltipPosition } from "../../tooltip";
-import { autobind } from "../../../utils";
-import { TextInputStatus } from "./statuses"
import { observable } from "mobx";
import { observer } from "mobx-react";
+import { Cluster } from "../../../../main/cluster";
+import { Input } from "../../input";
+import { isUrl } from "../../input/input.validators";
+import { SubTitle } from "../../layout/sub-title";
interface Props {
- cluster: Cluster;
+ cluster: Cluster;
}
@observer
export class ClusterProxySetting extends React.Component {
@observable proxy = this.props.cluster.preferences.httpsProxy || "";
- @observable status = TextInputStatus.CLEAN;
- @observable errorText?: string;
+
+ save = () => {
+ this.props.cluster.preferences.httpsProxy = this.proxy;
+ };
+
+ onChange = (value: string) => {
+ this.proxy = value;
+ }
render() {
- return <>
- HTTPS Proxy
- HTTPS Proxy server. Used for communicating with Kubernetes API.
-
- >;
- }
-
- @autobind()
- changeProxyState(proxy: string, _e: React.ChangeEvent) {
- if (this.status === TextInputStatus.UPDATING) {
- console.log("prevent changing cluster proxy while updating");
- return;
- }
-
- this.status = this.proxyDiffers(proxy);
- this.proxy = proxy;
- }
-
- proxyDiffers(proxy: string): TextInputStatus {
- const { httpsProxy = "" } = this.props.cluster.preferences;
-
- return proxy === httpsProxy ? TextInputStatus.CLEAN : TextInputStatus.DIRTY;
- }
-
- getIconRight(): React.ReactNode {
- switch (this.status) {
- case TextInputStatus.CLEAN:
- return null;
- case TextInputStatus.DIRTY:
- return ;
- case TextInputStatus.UPDATED:
- return ;
- case TextInputStatus.UPDATING:
- return ;
- case TextInputStatus.ERROR:
- return
-
- {this.errorText}
-
-
- }
- }
-
- @autobind()
- updateClusterProxy(proxy: string) {
- if (this.proxyDiffers(proxy) !== TextInputStatus.DIRTY) {
- return;
- }
-
- try {
- const url = new URL(proxy);
-
- if (url.protocol !== "https") {
- this.status = TextInputStatus.ERROR
- this.errorText= `Proxy's protocol should be "https"`
- return
- }
- if (url.port === "") {
- this.status = TextInputStatus.ERROR
- this.errorText= "Proxy should include a port"
- return
- }
- } catch (e) {
- this.status = TextInputStatus.ERROR
- this.errorText= "Invalid URL"
- return
- }
-
- this.status = TextInputStatus.UPDATING
- this.props.cluster.preferences.httpsProxy = proxy;
- this.proxy = proxy;
- this.status = TextInputStatus.UPDATED
+ return (
+ <>
+
+ HTTP Proxy server. Used for communicating with Kubernetes API.
+
+ >
+ );
}
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/cluster-workspace-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-workspace-setting.tsx
index 6337f56aa0..6cd933ca11 100644
--- a/src/renderer/components/+cluster-settings/components/cluster-workspace-setting.tsx
+++ b/src/renderer/components/+cluster-settings/components/cluster-workspace-setting.tsx
@@ -1,36 +1,36 @@
import React from "react";
-import { Cluster } from "../../../../main/cluster";
-import { clusterStore } from "../../../../common/cluster-store"
-import { workspaceStore } from "../../../../common/workspace-store"
-import { Select, SelectOption } from "../../../components/select";
-import { GeneralInputStatus } from "./statuses"
-import { observable } from "mobx";
-import { autobind } from "../../../utils";
import { observer } from "mobx-react";
+import { Link } from "react-router-dom";
+import { workspacesURL } from "../../+workspaces";
+import { workspaceStore } from "../../../../common/workspace-store";
+import { Cluster } from "../../../../main/cluster";
+import { Select } from "../../../components/select";
+import { SubTitle } from "../../layout/sub-title";
interface Props {
- cluster: Cluster;
+ cluster: Cluster;
}
@observer
export class ClusterWorkspaceSetting extends React.Component {
- @observable workspace = this.props.cluster.workspace;
-
render() {
- return <>
- Cluster Workspace
- Change cluster workspace:
- ({value: w.id, label: {w.name} }))}
- onChange={this.changeWorkspace}
- />
- >;
- }
-
- @autobind()
- changeWorkspace({ value: workspace }: SelectOption) {
- this.workspace = workspace;
- this.props.cluster.workspace = workspace;
+ return (
+ <>
+
+
+ Define cluster{" "}
+
+ workspace
+ .
+
+ this.props.cluster.workspace = value}
+ options={workspaceStore.workspacesList.map(w =>
+ ({value: w.id, label: w.name})
+ )}
+ />
+ >
+ );
}
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/install-feature.tsx b/src/renderer/components/+cluster-settings/components/install-feature.tsx
new file mode 100644
index 0000000000..a04b8fed63
--- /dev/null
+++ b/src/renderer/components/+cluster-settings/components/install-feature.tsx
@@ -0,0 +1,93 @@
+import React from "react";
+import { observable, reaction, comparer } from "mobx";
+import { observer, disposeOnUnmount } from "mobx-react";
+import { clusterIpc } from "../../../../common/cluster-ipc";
+import { Cluster } from "../../../../main/cluster";
+import { Button } from "../../button";
+import { Notifications } from "../../notifications";
+import { Spinner } from "../../spinner";
+
+interface Props {
+ cluster: Cluster
+ feature: string
+}
+
+@observer
+export class InstallFeature extends React.Component {
+ @observable loading = false;
+
+ componentDidMount() {
+ disposeOnUnmount(this,
+ reaction(() => this.props.cluster.features[this.props.feature], () => {
+ this.loading = false;
+ }, { equals: comparer.structural })
+ );
+ }
+
+ getActionButtons() {
+ const { cluster, feature } = this.props;
+ const features = cluster.features[feature];
+ const disabled = !cluster.isAdmin || this.loading;
+ const loadingIcon = this.loading ? : null;
+ if (!features) return null;
+ return (
+
+ {features.canUpgrade &&
+
+ clusterIpc.upgradeFeature.invokeFromRenderer(cluster.id, feature))
+ }
+ >
+ Upgrade
+
+ }
+ {features.installed &&
+
+ clusterIpc.uninstallFeature.invokeFromRenderer(cluster.id, feature))
+ }
+ >
+ Uninstall
+
+ }
+ {!features.installed && !features.canUpgrade &&
+
+ clusterIpc.installFeature.invokeFromRenderer(cluster.id, feature))
+ }
+ >
+ Install
+
+ }
+ {loadingIcon}
+ {!cluster.isAdmin && Actions can only be performed by admins. }
+
+ );
+ }
+
+ runAction(action: () => Promise): () => Promise {
+ return async () => {
+ try {
+ this.loading = true;
+ await action();
+ } catch (err) {
+ Notifications.error(err.toString());
+ }
+ };
+ }
+
+ render() {
+ return (
+ <>
+ {this.props.children}
+ {this.getActionButtons()}
+ >
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/install-metrics.tsx b/src/renderer/components/+cluster-settings/components/install-metrics.tsx
deleted file mode 100644
index 171adc6034..0000000000
--- a/src/renderer/components/+cluster-settings/components/install-metrics.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import React from "react";
-import { Cluster } from "../../../../main/cluster";
-import { Button } from "../../button";
-import { autobind } from "../../../utils";
-import { Tooltip, TooltipPosition } from "../../tooltip";
-import { MetricsFeature } from "../../../../features/metrics";
-import { Spinner } from "../../spinner";
-import { Icon } from "../../icon";
-import { clusterIpc } from "../../../../common/cluster-ipc";
-import { observable } from "mobx";
-import { ActionStatus } from "./statuses"
-import { observer } from "mobx-react";
-
-interface Props {
- cluster: Cluster;
-}
-
-@observer
-export class InstallMetrics extends React.Component {
- @observable status = ActionStatus.IDLE;
- @observable errorText?: string;
-
- render() {
- return <>
- Metrics
-
- User Mode feature enables non-admin users to see namespaces they have access to.
- This is achieved by configuring RBAC rules so that every authenticated user is granted to list namespaces.
-
-
- {this.getActionButtons()}
-
- >;
- }
-
- getStatusIcon(): React.ReactNode {
- switch (this.status) {
- case ActionStatus.IDLE:
- return null;
- case ActionStatus.PROCESSING:
- return ;
- case ActionStatus.ERROR:
- return
- }
- }
-
- getDisabledToolTip(id: string, action: string): React.ReactNode {
- const { cluster } = this.props;
- if (cluster.isAdmin) {
- return null;
- }
-
- return (
-
- {action} only allowed by admins
-
- );
- }
-
- getActionButtons(): React.ReactNode[] {
- const { cluster } = this.props
- const buttons = [];
-
- if (cluster.features[MetricsFeature.id]?.canUpgrade) {
- buttons.push(
-
- Upgrade {this.getStatusIcon()} {this.getDisabledToolTip("cluster-feature-metrics-upgrade", "Upgrading")}
-
- );
- }
-
- if (cluster.features[MetricsFeature.id]?.installed) {
- buttons.push(
-
- Uninstall {this.getStatusIcon()} {this.getDisabledToolTip("cluster-feature-metrics-uninstall", "Uninstalling")}
-
- );
- } else {
- buttons.push(
-
- Install {this.getStatusIcon()} {this.getDisabledToolTip("cluster-feature-metrics-install", "Installing")}
-
- );
- }
-
- return buttons;
- }
-
- runAction(action: keyof typeof clusterIpc): () => Promise {
- return async () => {
- const { cluster } = this.props;
- console.log(`running ${action} ${MetricsFeature.id} onto ${cluster.preferences.clusterName}`);
-
- try {
- this.status = ActionStatus.PROCESSING
- await clusterIpc[action].invokeFromRenderer(cluster.id, MetricsFeature.id);
- try {
- await cluster.refresh();
- } catch (err) {
- console.error(err);
- }
- this.status = ActionStatus.IDLE
- } catch (err) {
- this.status = ActionStatus.ERROR
- this.errorText = err.toString()
- }
- };
- }
-}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/install-user-mode.tsx b/src/renderer/components/+cluster-settings/components/install-user-mode.tsx
deleted file mode 100644
index faf7562336..0000000000
--- a/src/renderer/components/+cluster-settings/components/install-user-mode.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import React from "react";
-import { Cluster } from "../../../../main/cluster";
-import { Button } from "../../button";
-import { autobind } from "../../../utils";
-import { Tooltip, TooltipPosition } from "../../tooltip";
-import { Spinner } from "../../spinner";
-import { Icon } from "../../icon";
-import { UserModeFeature } from "../../../../features/user-mode";
-import { clusterIpc } from "../../../../common/cluster-ipc";
-import { observable } from "mobx";
-import { ActionStatus } from "./statuses"
-import { observer } from "mobx-react";
-
-interface Props {
- cluster: Cluster;
-}
-
-@observer
-export class InstallUserMode extends React.Component {
- @observable status = ActionStatus.IDLE;
- @observable errorText?: string;
-
- render() {
- return <>
- User Mode
-
- User Mode feature enables non-admin users to see namespaces they have access to.
- This is achieved by configuring RBAC rules so that every authenticated user is granted to list namespaces.
-
-
- {this.getActionButtons()}
-
- >;
- }
-
-
- getStatusIcon(): React.ReactNode {
- switch (this.status) {
- case ActionStatus.IDLE:
- return null;
- case ActionStatus.PROCESSING:
- return ;
- case ActionStatus.ERROR:
- return
- }
- }
-
- getDisabledToolTip(id: string, action: string): React.ReactNode {
- const { cluster } = this.props;
- if (cluster.isAdmin) {
- return null;
- }
-
- return
- {action} only allowed by admins
- ;
- }
-
- getActionButtons(): React.ReactNode[] {
- const { cluster } = this.props
- const buttons = [];
-
- if (cluster.features[UserModeFeature.id]?.canUpgrade) {
- buttons.push(
-
- Upgrade {this.getStatusIcon()} {this.getDisabledToolTip("cluster-feature-user-mode-upgrade", "Upgrading")}
-
- );
- }
-
- if (cluster.features[UserModeFeature.id]?.installed) {
- buttons.push(
-
- Uninstall {this.getStatusIcon()} {this.getDisabledToolTip("cluster-feature-user-mode-uninstall", "Uninstalling")}
-
- );
- } else {
- buttons.push(
-
- Install {this.getStatusIcon()} {this.getDisabledToolTip("cluster-feature-user-mode-install", "Installing")}
-
- );
- }
-
- return buttons;
- }
-
- runAction(action: keyof typeof clusterIpc): () => Promise {
- return async () => {
- const { cluster } = this.props;
- console.log(`running ${action} ${UserModeFeature.id} onto ${cluster.preferences.clusterName}`);
-
- try {
- this.status = ActionStatus.PROCESSING
- await clusterIpc[action].invokeFromRenderer(cluster.id, UserModeFeature.id);
- try {
- await cluster.refresh();
- } catch (err) {
- console.error(err);
- }
- this.status = ActionStatus.IDLE
- } catch (err) {
- this.status = ActionStatus.ERROR
- this.errorText = err.toString()
- }
- };
- }
-}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/remove-cluster-button.tsx b/src/renderer/components/+cluster-settings/components/remove-cluster-button.tsx
index 0c57196330..fe62ef4899 100644
--- a/src/renderer/components/+cluster-settings/components/remove-cluster-button.tsx
+++ b/src/renderer/components/+cluster-settings/components/remove-cluster-button.tsx
@@ -1,63 +1,37 @@
import React from "react";
-import { Cluster } from "../../../../main/cluster";
-import { Button } from "../../button";
-import { autobind } from "../../../utils";
-import { Spinner } from "../../spinner";
-import { Icon } from "../../icon";
-import { ConfirmDialog } from "../../confirm-dialog";
import { Trans } from "@lingui/macro";
+import { observer } from "mobx-react";
import { clusterIpc } from "../../../../common/cluster-ipc";
import { clusterStore } from "../../../../common/cluster-store";
-import { observable } from "mobx";
-import { observer } from "mobx-react";
-import { RemovalStatus } from "./statuses"
+import { Cluster } from "../../../../main/cluster";
+import { autobind } from "../../../utils";
+import { Button } from "../../button";
+import { ConfirmDialog } from "../../confirm-dialog";
interface Props {
- cluster: Cluster;
+ cluster: Cluster;
}
@observer
export class RemoveClusterButton extends React.Component {
- @observable status = RemovalStatus.PRESENT;
- @observable errorText?: string;
-
- render() {
- return (
-
- Remove Cluster {this.getStatusIcon()}
-
- );
- }
-
- getStatusIcon(): React.ReactNode {
- switch (this.status) {
- case RemovalStatus.PRESENT:
- return null;
- case RemovalStatus.PROCESSING:
- return ;
- case RemovalStatus.ERROR:
- return ;
- }
- }
-
- @autobind()
+ @autobind()
confirmRemoveCluster() {
const { cluster } = this.props;
-
ConfirmDialog.open({
message: Are you sure you want to remove {cluster.preferences.clusterName} from Lens?
,
labelOk: Yes ,
labelCancel: No ,
ok: async () => {
- try {
- this.status = RemovalStatus.PROCESSING;
- await clusterIpc.disconnect.invokeFromRenderer(cluster.id);
- await clusterStore.removeById(cluster.id);
- } catch (err) {
- this.status = RemovalStatus.ERROR;
- this.errorText = err.toString();
- }
+ await clusterStore.removeById(cluster.id);
}
})
}
+
+ render() {
+ return (
+
+ Remove Cluster
+
+ );
+ }
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/components/statuses.ts b/src/renderer/components/+cluster-settings/components/statuses.ts
deleted file mode 100644
index d9d897c430..0000000000
--- a/src/renderer/components/+cluster-settings/components/statuses.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export enum TextInputStatus {
- CLEAN = "clean",
- DIRTY = "dirty",
- UPDATING = "updating",
- ERROR = "error",
- UPDATED = "updated",
-}
-
-export enum GeneralInputStatus {
- CLEAN = "clean",
- ERROR = "error",
-}
-
-export enum ActionStatus {
- IDLE = "idle",
- PROCESSING = "processing",
- ERROR = "error"
-}
-
-export enum RemovalStatus {
- PRESENT = "present",
- PROCESSING = "processing",
- ERROR = "error",
-}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/features.tsx b/src/renderer/components/+cluster-settings/features.tsx
index b049fa90ff..f1b3cdada8 100644
--- a/src/renderer/components/+cluster-settings/features.tsx
+++ b/src/renderer/components/+cluster-settings/features.tsx
@@ -1,7 +1,9 @@
import React from "react";
import { Cluster } from "../../../main/cluster";
-import { InstallMetrics } from "./components/install-metrics";
-import { InstallUserMode } from "./components/install-user-mode";
+import { InstallFeature } from "./components/install-feature";
+import { SubTitle } from "../layout/sub-title";
+import { MetricsFeature } from "../../../features/metrics";
+import { UserModeFeature } from "../../../features/user-mode";
interface Props {
cluster: Cluster;
@@ -11,10 +13,30 @@ export class Features extends React.Component {
render() {
const { cluster } = this.props;
- return
-
Features
-
-
- ;
+ return (
+
+
Features
+
+ <>
+
+
+ Enable timeseries data visualization (Prometheus stack) for your cluster.
+ Install this only if you don't have existing Prometheus stack installed.
+ You can see preview of manifests{" "}
+ here .
+
+ >
+
+
+ <>
+
+
+ User Mode feature enables non-admin users to see namespaces they have access to.{" "}
+ This is achieved by configuring RBAC rules so that every authenticated user is granted to list namespaces.
+
+ >
+
+
+ );
}
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/general.tsx b/src/renderer/components/+cluster-settings/general.tsx
index e03c6e2195..5fb6e9b81f 100644
--- a/src/renderer/components/+cluster-settings/general.tsx
+++ b/src/renderer/components/+cluster-settings/general.tsx
@@ -15,8 +15,6 @@ export class General extends React.Component {
render() {
return
General
-
-
diff --git a/src/renderer/components/+cluster-settings/removal.tsx b/src/renderer/components/+cluster-settings/removal.tsx
index f1d613c694..7d97e9c515 100644
--- a/src/renderer/components/+cluster-settings/removal.tsx
+++ b/src/renderer/components/+cluster-settings/removal.tsx
@@ -3,16 +3,18 @@ import { Cluster } from "../../../main/cluster";
import { RemoveClusterButton } from "./components/remove-cluster-button";
interface Props {
- cluster: Cluster;
+ cluster: Cluster;
}
export class Removal extends React.Component
{
render() {
const { cluster } = this.props;
- return
-
Removal
-
- ;
+ return (
+
+
Removal
+
+
+ );
}
}
\ No newline at end of file
diff --git a/src/renderer/components/+cluster-settings/status.tsx b/src/renderer/components/+cluster-settings/status.tsx
index 6e0bf528fc..3136fc5962 100644
--- a/src/renderer/components/+cluster-settings/status.tsx
+++ b/src/renderer/components/+cluster-settings/status.tsx
@@ -1,41 +1,40 @@
import React from "react";
-import { Spinner } from "../spinner";
import { Cluster } from "../../../main/cluster";
+import { SubTitle } from "../layout/sub-title";
+import { Table, TableCell, TableRow } from "../table";
interface Props {
cluster: Cluster;
}
export class Status extends React.Component {
- renderStatusRows(): JSX.Element[] {
+ renderStatusRows() {
const { cluster } = this.props;
-
- const rows: [string, React.ReactNode][] = [
+ const rows = [
["Online Status", cluster.online ? "online" : `offline (${cluster.failureReason || "unknown reason"}`],
["Distribution", cluster.distribution],
["Kerbel Version", cluster.version],
["API Address", cluster.apiUrl],
+ ["Nodes Count", cluster.nodes || "0"]
];
-
- if (cluster.nodes > 0) {
- rows.push(["Nodes Count", cluster.nodes]);
- }
-
- return rows
- .map(([header, value]) => [
- {header} ,
- {value}
- ])
- .flat();
+ return (
+
+ {rows.map(([name, value]) => {
+ return (
+
+ {name}
+ {value}
+
+ );
+ })}
+
+ );
}
render() {
- const { cluster } = this.props;
-
return
Status
-
-
Cluster status
+
Cluster status information including: detected distribution, kernel version, and online status.
diff --git a/src/renderer/components/cluster-icon/cluster-icon.scss b/src/renderer/components/cluster-icon/cluster-icon.scss
index f7e159a7c5..b8a87a07de 100644
--- a/src/renderer/components/cluster-icon/cluster-icon.scss
+++ b/src/renderer/components/cluster-icon/cluster-icon.scss
@@ -7,6 +7,12 @@
user-select: none;
cursor: pointer;
+ &.interactive {
+ img {
+ opacity: .55;
+ }
+ }
+
&.active, &.interactive:hover {
background-color: #fff;
@@ -16,7 +22,6 @@
}
img {
- opacity: .55;
width: var(--size);
height: var(--size);
}
diff --git a/src/renderer/components/cluster-icon/cluster-icon.tsx b/src/renderer/components/cluster-icon/cluster-icon.tsx
index 9d9a359670..ccc65892de 100644
--- a/src/renderer/components/cluster-icon/cluster-icon.tsx
+++ b/src/renderer/components/cluster-icon/cluster-icon.tsx
@@ -42,12 +42,12 @@ export class ClusterIcon extends React.Component
{
active: isActive,
});
return (
-