From 7f65f1ea0682cebb23a6cbdecb947ee90ffc2376 Mon Sep 17 00:00:00 2001
From: Lauri Nevala
Date: Fri, 31 Jul 2020 16:50:57 +0300
Subject: [PATCH 01/14] Fix CRD api parsing (#622)
Signed-off-by: Lauri Nevala
---
src/renderer/api/kube-api-parse.ts | 3 +++
src/renderer/api/kube-api-parse_test.ts | 13 +++++++++++++
2 files changed, 16 insertions(+)
diff --git a/src/renderer/api/kube-api-parse.ts b/src/renderer/api/kube-api-parse.ts
index 0745dc71eb..97a7875322 100644
--- a/src/renderer/api/kube-api-parse.ts
+++ b/src/renderer/api/kube-api-parse.ts
@@ -40,6 +40,9 @@ export function parseApi(path: string): IKubeApiLinkBase {
apiGroup = left.join("/");
} else {
switch (left.length) {
+ case 4:
+ [apiGroup, apiVersion, resource, name] = left
+ break;
case 2:
resource = left.pop();
// fallthrough
diff --git a/src/renderer/api/kube-api-parse_test.ts b/src/renderer/api/kube-api-parse_test.ts
index 03f53ae34d..1fda8c3e53 100644
--- a/src/renderer/api/kube-api-parse_test.ts
+++ b/src/renderer/api/kube-api-parse_test.ts
@@ -6,6 +6,19 @@ interface KubeApi_Parse_Test {
}
const tests: KubeApi_Parse_Test[] = [
+ {
+ url: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
+ expected: {
+ apiBase: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions",
+ apiPrefix: "/apis",
+ apiGroup: "apiextensions.k8s.io",
+ apiVersion: "v1beta1",
+ apiVersionWithGroup: "apiextensions.k8s.io/v1beta1",
+ namespace: undefined,
+ resource: "customresourcedefinitions",
+ name: "prometheuses.monitoring.coreos.com"
+ },
+ },
{
url: "/api/v1/namespaces/kube-system/pods/coredns-6955765f44-v8p27",
expected: {
From 0c3be9bbaea6d18eb8296b1e52949081180abed3 Mon Sep 17 00:00:00 2001
From: Sebastian Malton
Date: Tue, 4 Aug 2020 13:14:02 -0400
Subject: [PATCH 02/14] Fix Resource Quota Rendering (#624)
* add parsing of quota values which are non-numeric in the general case
* add unit tests
Signed-off-by: Sebastian Malton
Co-authored-by: Sebastian Malton
---
.../resource-quota-details.tsx | 48 +++++++++++--------
src/renderer/utils/convertCpu.ts | 13 +++--
src/renderer/utils/convertMemory.ts | 10 ++--
src/renderer/utils/index.ts | 1 +
src/renderer/utils/metricUnitsToNumber.ts | 10 ++++
.../utils/metricUnitsToNumber_test.ts | 15 ++++++
6 files changed, 68 insertions(+), 29 deletions(-)
create mode 100644 src/renderer/utils/metricUnitsToNumber.ts
create mode 100644 src/renderer/utils/metricUnitsToNumber_test.ts
diff --git a/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx b/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx
index 6a2665e741..3da8c7112a 100644
--- a/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx
+++ b/src/renderer/components/+config-resource-quotas/resource-quota-details.tsx
@@ -4,7 +4,7 @@ import kebabCase from "lodash/kebabCase";
import { observer } from "mobx-react";
import { Trans } from "@lingui/macro";
import { DrawerItem, DrawerTitle } from "../drawer";
-import { cpuUnitsToNumber, cssNames, unitsToBytes } from "../../utils";
+import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber } from "../../utils";
import { KubeObjectDetailsProps } from "../kube-object";
import { ResourceQuota, resourceQuotaApi } from "../../api/endpoints/resource-quota.api";
import { LineProgress } from "../line-progress";
@@ -15,24 +15,30 @@ import { KubeObjectMeta } from "../kube-object/kube-object-meta";
interface Props extends KubeObjectDetailsProps {
}
-@observer
-export class ResourceQuotaDetails extends React.Component {
- renderQuotas = (quota: ResourceQuota) => {
- const { hard, used } = quota.status
- if (!hard || !used) return null
- const transformUnit = (name: string, value: string) => {
- if (name.includes("memory") || name.includes("storage")) {
- return unitsToBytes(value)
- }
- if (name.includes("cpu")) {
- return cpuUnitsToNumber(value)
- }
- return parseInt(value)
- }
- return Object.entries(hard).map(([name, value]) => {
- if (!used[name]) return null
+const onlyNumbers = /$[0-9]*^/g;
+
+function transformUnit(name: string, value: string): number {
+ if (name.includes("memory") || name.includes("storage")) {
+ return unitsToBytes(value)
+ }
+
+ if (name.includes("cpu")) {
+ return cpuUnitsToNumber(value)
+ }
+
+ return metricUnitsToNumber(value);
+}
+
+function renderQuotas(quota: ResourceQuota): JSX.Element[] {
+ const { hard = {}, used = {} } = quota.status
+
+ return Object.entries(hard)
+ .filter(([name]) => used[name])
+ .map(([name, value]) => {
const current = transformUnit(name, used[name])
const max = transformUnit(name, value)
+ const usage = max === 0 ? 100 : Math.ceil(current / max * 100); // special case 0 max as always 100% usage
+
return (
{name}
@@ -41,14 +47,16 @@ export class ResourceQuotaDetails extends React.Component
{
max={max}
value={current}
tooltip={
- Set : {value}. Used : {Math.ceil(current / max * 100) + "%"}
+ Set : {value}. Usage : {usage + "%"}
}
/>
)
})
- }
+}
+@observer
+export class ResourceQuotaDetails extends React.Component {
render() {
const { object: quota } = this.props;
if (!quota) return null;
@@ -57,7 +65,7 @@ export class ResourceQuotaDetails extends React.Component {
Quotas} className="quota-list">
- {this.renderQuotas(quota)}
+ {renderQuotas(quota)}
{quota.getScopeSelector().length > 0 && (
diff --git a/src/renderer/utils/convertCpu.ts b/src/renderer/utils/convertCpu.ts
index 2e7c6b85c3..7b81a30cc3 100644
--- a/src/renderer/utils/convertCpu.ts
+++ b/src/renderer/utils/convertCpu.ts
@@ -1,10 +1,13 @@
// Helper to convert CPU K8S units to numbers
+const thousand = 1000;
+const million = thousand * thousand;
+const shortBillion = thousand * million;
+
export function cpuUnitsToNumber(cpu: string) {
const cpuNum = parseInt(cpu)
- const billion = 1000000 * 1000
- if (cpu.includes("m")) return cpuNum / 1000
- if (cpu.includes("u")) return cpuNum / 1000000
- if (cpu.includes("n")) return cpuNum / billion
+ if (cpu.includes("m")) return cpuNum / thousand
+ if (cpu.includes("u")) return cpuNum / million
+ if (cpu.includes("n")) return cpuNum / shortBillion
return parseFloat(cpu)
-}
\ No newline at end of file
+}
diff --git a/src/renderer/utils/convertMemory.ts b/src/renderer/utils/convertMemory.ts
index faa89dd990..d0d7e1fc52 100644
--- a/src/renderer/utils/convertMemory.ts
+++ b/src/renderer/utils/convertMemory.ts
@@ -7,9 +7,9 @@ export function unitsToBytes(value: string) {
if (!suffixes.some(suffix => value.includes(suffix))) {
return parseFloat(value)
}
- const index = suffixes.findIndex(suffix =>
- suffix == value.replace(/[0-9]|i|\./g, '')
- )
+
+ const suffix = value.replace(/[0-9]|i|\./g, '');
+ const index = suffixes.indexOf(suffix);
return parseInt(
(parseFloat(value) * Math.pow(base, index + 1)).toFixed(1)
)
@@ -21,8 +21,10 @@ export function bytesToUnits(bytes: number, precision = 1) {
if (!bytes) {
return "N/A"
}
+
if (index === 0) {
return `${bytes}${sizes[index]}`
}
+
return `${(bytes / (1024 ** index)).toFixed(precision)}${sizes[index]}i`
-}
\ No newline at end of file
+}
diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts
index 8a3a263077..578ec5c355 100755
--- a/src/renderer/utils/index.ts
+++ b/src/renderer/utils/index.ts
@@ -20,3 +20,4 @@ export * from './formatDuration'
export * from './isReactNode'
export * from './convertMemory'
export * from './convertCpu'
+export * from './metricUnitsToNumber'
diff --git a/src/renderer/utils/metricUnitsToNumber.ts b/src/renderer/utils/metricUnitsToNumber.ts
new file mode 100644
index 0000000000..9390c35b24
--- /dev/null
+++ b/src/renderer/utils/metricUnitsToNumber.ts
@@ -0,0 +1,10 @@
+const base = 1000;
+const suffixes = ["k", "m", "g", "t", "q"];
+
+export function metricUnitsToNumber(value: string): number {
+ const suffix = value.toLowerCase().slice(-1);
+ const index = suffixes.indexOf(suffix);
+ return parseInt(
+ (parseFloat(value) * Math.pow(base, index + 1)).toFixed(1)
+ )
+}
diff --git a/src/renderer/utils/metricUnitsToNumber_test.ts b/src/renderer/utils/metricUnitsToNumber_test.ts
new file mode 100644
index 0000000000..cbb0669122
--- /dev/null
+++ b/src/renderer/utils/metricUnitsToNumber_test.ts
@@ -0,0 +1,15 @@
+import { metricUnitsToNumber } from "./metricUnitsToNumber";
+
+describe("metricUnitsToNumber tests", () => {
+ test("plain number", () => {
+ expect(metricUnitsToNumber("124")).toStrictEqual(124);
+ });
+
+ test("with k suffix", () => {
+ expect(metricUnitsToNumber("124k")).toStrictEqual(124000);
+ });
+
+ test("with m suffix", () => {
+ expect(metricUnitsToNumber("124m")).toStrictEqual(124000000);
+ });
+});
\ No newline at end of file
From 0f4248de689daddaf0b71cd00d88776b0c6fc74a Mon Sep 17 00:00:00 2001
From: Alex Andreev
Date: Fri, 7 Aug 2020 15:57:16 +0300
Subject: [PATCH 03/14] 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 (
-