mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
1 parent
a0831b3283
commit
33d3181113
11 files changed
+163
-165
No files matched your search
@@ -20,6 +20,12 @@
|
||||
border-left: 1px solid #353a3e;
|
||||
}
|
||||
|
||||
.error {
|
||||
border-radius: $radius;
|
||||
padding: $padding;
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $colorInfo;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,101 @@
|
||||
import "./add-cluster.scss"
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { computed, observable } from "mobx";
|
||||
import path from "path";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { Input } from "../input";
|
||||
import { _i18n } from "../../i18n";
|
||||
import { AceEditor } from "../ace-editor";
|
||||
import { Button } from "../button";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import { loadConfig, saveConfigToAppFiles, splitConfig, validateConfig } from "../../../common/kube-helpers";
|
||||
import { tracker } from "../../../common/tracker";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
@observer
|
||||
export class AddCluster extends React.Component {
|
||||
readonly customContext = "custom"
|
||||
readonly kubeConfigFile = path.join(process.env.HOME, '.kube', 'config');
|
||||
readonly custom: any = "custom"
|
||||
@observable.ref clusterConfig: KubeConfig;
|
||||
@observable.ref kubeConfig: KubeConfig; // local ~/.kube/config (if available)
|
||||
|
||||
@observable isWaiting = false
|
||||
@observable showSettings = false
|
||||
@observable clusterContext = ""
|
||||
@observable error = ""
|
||||
@observable proxyServerUrl = ""
|
||||
@observable proxyServer = ""
|
||||
@observable customConfig = ""
|
||||
|
||||
// todo: mark new contexts with badge
|
||||
@computed get clusterOptions(): SelectOption[] {
|
||||
return [
|
||||
{
|
||||
label: <Trans>Custom..</Trans>,
|
||||
value: this.customContext,
|
||||
}
|
||||
]
|
||||
async componentDidMount() {
|
||||
const kubeConfig = await this.readLocalKubeConfig();
|
||||
if (kubeConfig) {
|
||||
this.kubeConfig = loadConfig(kubeConfig)
|
||||
this.customConfig = kubeConfig
|
||||
}
|
||||
}
|
||||
|
||||
// todo
|
||||
addCluster = () => {
|
||||
console.log('add new cluster')
|
||||
async readLocalKubeConfig(): Promise<string> {
|
||||
const localPath = path.join(process.env.HOME, '.kube', 'config');
|
||||
return fs.readFile(localPath, "utf8").catch(() => null)
|
||||
}
|
||||
|
||||
@computed get isCustom() {
|
||||
return this.clusterConfig === this.custom;
|
||||
}
|
||||
|
||||
@computed get clusterOptions() {
|
||||
const options: SelectOption<KubeConfig>[] = [];
|
||||
if (this.kubeConfig) {
|
||||
splitConfig(this.kubeConfig).forEach(kubeConfig => {
|
||||
const contextName = kubeConfig.getCurrentContext();
|
||||
const isNew = false; // fixme: detect new context since last visit
|
||||
options.push({
|
||||
value: kubeConfig,
|
||||
label: <>
|
||||
{contextName}
|
||||
{isNew && <span className="new"> <Trans>(new)</Trans></span>}
|
||||
</>,
|
||||
})
|
||||
})
|
||||
}
|
||||
options.push({
|
||||
label: <Trans>Custom..</Trans>,
|
||||
value: this.custom,
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
addCluster = async () => {
|
||||
tracker.event("cluster", "add");
|
||||
const { clusterConfig, customConfig, proxyServer } = this;
|
||||
const clusterId = uuid();
|
||||
try {
|
||||
const config = this.isCustom ? loadConfig(customConfig) : clusterConfig;
|
||||
if (!config) {
|
||||
this.error = "Please select kubeconfig"
|
||||
return;
|
||||
}
|
||||
this.error = ""
|
||||
this.isWaiting = true
|
||||
validateConfig(config);
|
||||
clusterStore.addCluster({
|
||||
id: clusterId,
|
||||
kubeConfigPath: saveConfigToAppFiles(clusterId, config),
|
||||
workspace: workspaceStore.currentWorkspaceId,
|
||||
contextName: config.currentContext,
|
||||
preferences: {
|
||||
clusterName: config.currentContext,
|
||||
httpsProxy: proxyServer || undefined,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
this.error = String(err);
|
||||
} finally {
|
||||
this.isWaiting = false;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -43,9 +105,9 @@ export class AddCluster extends React.Component {
|
||||
<h2><Trans>Add Cluster</Trans></h2>
|
||||
<Select
|
||||
placeholder={<Trans>Select kubeconfig</Trans>}
|
||||
value={this.clusterContext}
|
||||
value={this.clusterConfig}
|
||||
options={this.clusterOptions}
|
||||
onChange={({ value }: SelectOption) => this.clusterContext = value}
|
||||
onChange={({ value }: SelectOption) => this.clusterConfig = value}
|
||||
/>
|
||||
<div className="cluster-settings">
|
||||
<a href="#" onClick={() => this.showSettings = !this.showSettings}>
|
||||
@@ -57,15 +119,15 @@ export class AddCluster extends React.Component {
|
||||
<Input
|
||||
autoFocus
|
||||
placeholder={_i18n._(t`A HTTP proxy server URL (format: http://<address>:<port>)`)}
|
||||
value={this.proxyServerUrl}
|
||||
onChange={value => this.proxyServerUrl = value}
|
||||
value={this.proxyServer}
|
||||
onChange={value => this.proxyServer = value}
|
||||
/>
|
||||
<small className="hint">
|
||||
<Trans>HTTP Proxy server. Used for communicating with Kubernetes API.</Trans>
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
{this.clusterContext === this.customContext && (
|
||||
{this.isCustom && (
|
||||
<div className="custom-kubeconfig flex column gaps box grow">
|
||||
<p>Kubeconfig:</p>
|
||||
<AceEditor
|
||||
@@ -76,11 +138,15 @@ export class AddCluster extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{this.error && (
|
||||
<div className="error">{this.error}</div>
|
||||
)}
|
||||
<div className="actions-panel">
|
||||
<Button
|
||||
primary
|
||||
label={<Trans>Add cluster</Trans>}
|
||||
onClick={this.addCluster}
|
||||
waiting={this.isWaiting}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.ClusterIcon {
|
||||
--size: 40px;
|
||||
--size: 37px;
|
||||
|
||||
position: relative;
|
||||
opacity: .75;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
.ClustersMenu {
|
||||
--flex-gap: #{$padding * 2};
|
||||
|
||||
position: relative;
|
||||
padding: var(--flex-gap);
|
||||
text-align: center;
|
||||
padding: $padding * 2 $padding;
|
||||
background: var(--clusters-menu-bgc);
|
||||
|
||||
> .startup-tooltip {
|
||||
@@ -35,14 +34,17 @@
|
||||
|
||||
> .clusters {
|
||||
@include hidden-scrollbar;
|
||||
--flex-gap: #{$padding * 2};
|
||||
padding: $padding;
|
||||
|
||||
.is-mac & {
|
||||
margin-top: $padding;
|
||||
margin-top: $padding * 2;
|
||||
}
|
||||
}
|
||||
|
||||
> .add-cluster {
|
||||
position: relative;
|
||||
margin-top: $padding;
|
||||
|
||||
.Icon {
|
||||
border-radius: $radius;
|
||||
|
||||
@@ -74,7 +74,7 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
const showStartupHint = this.showHint && isLanding && noClusters;
|
||||
return (
|
||||
<div
|
||||
className={cssNames("ClustersMenu flex gaps column", className)}
|
||||
className={cssNames("ClustersMenu flex column", className)}
|
||||
onMouseEnter={() => this.showHint = false}
|
||||
>
|
||||
{showStartupHint && (
|
||||
@@ -87,7 +87,7 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="clusters">
|
||||
<div className="clusters flex column gaps">
|
||||
{clusters.map(cluster => {
|
||||
return (
|
||||
<ClusterIcon
|
||||
|
||||
@@ -92,11 +92,9 @@
|
||||
|
||||
@mixin set-draggable($is-draggable: true) {
|
||||
@if ($is-draggable) {
|
||||
cursor: move;
|
||||
-webkit-user-drag: auto;
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
} @else {
|
||||
-webkit-user-drag: none;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user