mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
allow to remove cluster from icon's context-menu, random fixes
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
33d3181113
commit
e11d8582f1
@ -65,7 +65,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.on("cluster:state", (event, clusterState: ClusterState) => {
|
ipcRenderer.on("cluster:state", (event, clusterState: ClusterState) => {
|
||||||
this.applyWithoutSync(() => {
|
this.applyWithoutSync(() => {
|
||||||
logger.info(`[CLUSTER-STORE]: received cluster(${clusterState.id}) update`, clusterState);
|
logger.debug(`[CLUSTER-STORE]: received state update for cluster=${clusterState.id}`, clusterState);
|
||||||
const cluster = this.getById(clusterState.id);
|
const cluster = this.getById(clusterState.id);
|
||||||
if (cluster) cluster.updateModel(clusterState)
|
if (cluster) cluster.updateModel(clusterState)
|
||||||
})
|
})
|
||||||
@ -85,6 +85,10 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
return Array.from(this.clusters.values());
|
return Array.from(this.clusters.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasContext(name: string) {
|
||||||
|
return this.clustersList.some(cluster => cluster.contextName === name);
|
||||||
|
}
|
||||||
|
|
||||||
getById(id: ClusterId): Cluster {
|
getById(id: ClusterId): Cluster {
|
||||||
return this.clusters.get(id);
|
return this.clusters.get(id);
|
||||||
}
|
}
|
||||||
@ -94,18 +98,23 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addCluster(model: ClusterModel): Cluster {
|
async addCluster(model: ClusterModel, activate = true): Promise<Cluster> {
|
||||||
const cluster = new Cluster(model);
|
const cluster = new Cluster(model);
|
||||||
this.clusters.set(model.id, cluster);
|
this.clusters.set(model.id, cluster);
|
||||||
|
if (activate) this.activeClusterId = model.id;
|
||||||
return cluster;
|
return cluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
removeById(clusterId: ClusterId): void {
|
async removeById(clusterId: ClusterId) {
|
||||||
if (this.activeClusterId === clusterId) {
|
const cluster = this.getById(clusterId);
|
||||||
this.activeClusterId = null;
|
if (cluster) {
|
||||||
|
this.clusters.delete(clusterId);
|
||||||
|
if (this.activeClusterId === clusterId) {
|
||||||
|
this.activeClusterId = null;
|
||||||
|
}
|
||||||
|
unlink(cluster.kubeConfigPath).catch(() => null);
|
||||||
}
|
}
|
||||||
this.clusters.delete(clusterId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -116,7 +125,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected async uploadClusterIcon({ clusterId, ...upload }: ClusterIconUpload): Promise<string> {
|
protected async uploadIcon({ clusterId, ...upload }: ClusterIconUpload): Promise<string> {
|
||||||
const cluster = this.getById(clusterId);
|
const cluster = this.getById(clusterId);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
tracker.event("cluster", "upload-icon");
|
tracker.event("cluster", "upload-icon");
|
||||||
@ -129,7 +138,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected resetClusterIcon(clusterId: ClusterId) {
|
protected resetIcon(clusterId: ClusterId) {
|
||||||
const cluster = this.getById(clusterId);
|
const cluster = this.getById(clusterId);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
tracker.event("cluster", "reset-icon")
|
tracker.event("cluster", "reset-icon")
|
||||||
@ -167,7 +176,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
this.clusters.replace(newClusters);
|
this.clusters.replace(newClusters);
|
||||||
this.removedClusters.replace(removedClusters);
|
this.removedClusters.replace(removedClusters);
|
||||||
|
|
||||||
// "auto-select" first cluster if not available or invalid from config file
|
// "auto-select" first cluster if available
|
||||||
if (!this.activeClusterId && newClusters.size) {
|
if (!this.activeClusterId && newClusters.size) {
|
||||||
this.activeClusterId = Array.from(newClusters.values())[0].id;
|
this.activeClusterId = Array.from(newClusters.values())[0].id;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,14 +31,14 @@ export function sendMessage({ channel, webContentId, filter, args = [] }: IpcMes
|
|||||||
}
|
}
|
||||||
views.forEach(webContent => {
|
views.forEach(webContent => {
|
||||||
const type = webContent.getType();
|
const type = webContent.getType();
|
||||||
logger.info(`[IPC]: sending message "${channel}" to ${type}=${webContent.id}`);
|
logger.debug(`[IPC]: sending message "${channel}" to ${type}=${webContent.id}`, { args });
|
||||||
webContent.send(channel, ...[args].flat());
|
webContent.send(channel, ...[args].flat());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: support timeout + merge with sendMessage?
|
// todo: support timeout + merge with sendMessage?
|
||||||
export async function invokeMessage<T extends any[], R = any>(channel: IpcChannel, ...args: T): Promise<R> {
|
export async function invokeMessage<T extends any[], R = any>(channel: IpcChannel, ...args: T): Promise<R> {
|
||||||
logger.debug(`[IPC]: invoke channel "${channel}"`, args);
|
logger.debug(`[IPC]: invoke channel "${channel}"`, { args });
|
||||||
return ipcRenderer.invoke(channel, ...args);
|
return ipcRenderer.invoke(channel, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export async function invokeMessage<T extends any[], R = any>(channel: IpcChanne
|
|||||||
export function handleMessage<T extends any[]>(channel: IpcChannel, handler: IpcMessageHandler<T>, options: IpcHandleOpts = {}) {
|
export function handleMessage<T extends any[]>(channel: IpcChannel, handler: IpcMessageHandler<T>, options: IpcHandleOpts = {}) {
|
||||||
const { timeout = 0 } = options;
|
const { timeout = 0 } = options;
|
||||||
ipcMain.handle(channel, async (event, ...args: T) => {
|
ipcMain.handle(channel, async (event, ...args: T) => {
|
||||||
logger.info(`[IPC]: handle "${channel}"`, { event, args });
|
logger.debug(`[IPC]: handle "${channel}"`, { args });
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
let timerId;
|
let timerId;
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
@ -60,7 +60,7 @@ export function handleMessage<T extends any[]>(channel: IpcChannel, handler: Ipc
|
|||||||
clearTimeout(timerId);
|
clearTimeout(timerId);
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.debug(`[IPC]: handling "${channel}" error`, err);
|
logger.debug(`[IPC]: handling "${channel}" error`, { err });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,23 +5,31 @@ import { ClusterId, clusterStore } from "../common/cluster-store"
|
|||||||
import { handleMessage } from "../common/ipc";
|
import { handleMessage } from "../common/ipc";
|
||||||
import { tracker } from "../common/tracker";
|
import { tracker } from "../common/tracker";
|
||||||
import { Cluster, ClusterIpcEvent } from "./cluster"
|
import { Cluster, ClusterIpcEvent } from "./cluster"
|
||||||
|
import logger from "./logger";
|
||||||
|
|
||||||
export class ClusterManager {
|
export class ClusterManager {
|
||||||
constructor(public readonly port: number) {
|
constructor(public readonly port: number) {
|
||||||
// auto-init clusters
|
// auto-init clusters
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
clusterStore.clustersList
|
clusterStore.clusters.forEach(cluster => {
|
||||||
.filter(cluster => !cluster.initialized)
|
if (cluster.initialized) return;
|
||||||
.forEach(cluster => cluster.init(port));
|
cluster.init(port);
|
||||||
|
logger.info(`[CLUSTER-MANAGER]: initializing cluster`, cluster.getMeta());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// auto-stop removed clusters
|
// auto-stop removed clusters
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
clusterStore.removedClusters.forEach(cluster => cluster.stop());
|
const { removedClusters } = clusterStore;
|
||||||
clusterStore.removedClusters.clear();
|
const meta = Array.from(removedClusters.values()).map(cluster => cluster.getMeta());
|
||||||
|
logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta);
|
||||||
|
removedClusters.forEach(cluster => cluster.destroy());
|
||||||
|
removedClusters.clear();
|
||||||
|
}, {
|
||||||
|
delay: 250
|
||||||
});
|
});
|
||||||
|
|
||||||
// listen ipc-events which could be handled *only* in main-process (nodeIntegration=true)
|
// listen for ipc-events that must be handled *only* in main-process (nodeIntegration=true)
|
||||||
handleMessage(ClusterIpcEvent.STOP, this.stopCluster.bind(this));
|
handleMessage(ClusterIpcEvent.STOP, this.stopCluster.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +45,7 @@ export class ClusterManager {
|
|||||||
|
|
||||||
protected stopCluster(clusterId: ClusterId) {
|
protected stopCluster(clusterId: ClusterId) {
|
||||||
tracker.event("cluster", "stop");
|
tracker.event("cluster", "stop");
|
||||||
this.getCluster(clusterId)?.stop();
|
this.getCluster(clusterId)?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
getClusterForRequest(req: http.IncomingMessage): Cluster {
|
getClusterForRequest(req: http.IncomingMessage): Cluster {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export enum ClusterStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ClusterState extends ClusterModel {
|
export interface ClusterState extends ClusterModel {
|
||||||
|
initialized?: boolean;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
online?: boolean;
|
online?: boolean;
|
||||||
accessible?: boolean;
|
accessible?: boolean;
|
||||||
@ -98,12 +99,13 @@ export class Cluster implements ClusterModel {
|
|||||||
|
|
||||||
bindEvents(viewId: number) {
|
bindEvents(viewId: number) {
|
||||||
if (!this.initialized) return;
|
if (!this.initialized) return;
|
||||||
|
logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
||||||
const refreshStatusTimer = setInterval(() => this.refreshStatus(), 30000); // every 30s
|
const refreshStatusTimer = setInterval(() => this.refreshStatus(), 30000); // every 30s
|
||||||
const refreshEventsTimer = setInterval(() => this.refreshEvents(), 3000); // every 3s
|
const refreshEventsTimer = setInterval(() => this.refreshEvents(), 3000); // every 3s
|
||||||
|
|
||||||
this.disposers.push(
|
this.disposers.push(
|
||||||
() => clearTimeout(refreshStatusTimer),
|
() => clearInterval(refreshStatusTimer),
|
||||||
() => clearTimeout(refreshEventsTimer),
|
() => clearInterval(refreshEventsTimer),
|
||||||
|
|
||||||
reaction(() => this.getState(), clusterState => {
|
reaction(() => this.getState(), clusterState => {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
@ -118,15 +120,24 @@ export class Cluster implements ClusterModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unbindEvents() {
|
unbindEvents() {
|
||||||
|
if (!this.initialized) return;
|
||||||
|
logger.info(`[CLUSTER]: unbind events`, this.getMeta());
|
||||||
this.disposers.forEach(dispose => dispose());
|
this.disposers.forEach(dispose => dispose());
|
||||||
this.disposers.length = 0;
|
this.disposers.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if (!this.initialized) return;
|
|
||||||
this.contextHandler.stopServer();
|
this.contextHandler.stopServer();
|
||||||
this.kubeconfigManager.unlink();
|
}
|
||||||
this.unbindEvents();
|
|
||||||
|
destroy() {
|
||||||
|
try {
|
||||||
|
this.stop();
|
||||||
|
this.unbindEvents();
|
||||||
|
this.kubeconfigManager.unlink();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[CLUSTER]: destroy() throws: ${err}`, this.getMeta());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -318,6 +329,7 @@ export class Cluster implements ClusterModel {
|
|||||||
getState(): ClusterState {
|
getState(): ClusterState {
|
||||||
const state: ClusterState = {
|
const state: ClusterState = {
|
||||||
...this.toJSON(),
|
...this.toJSON(),
|
||||||
|
initialized: this.initialized,
|
||||||
apiUrl: this.apiUrl,
|
apiUrl: this.apiUrl,
|
||||||
online: this.online,
|
online: this.online,
|
||||||
accessible: this.accessible,
|
accessible: this.accessible,
|
||||||
@ -333,4 +345,12 @@ export class Cluster implements ClusterModel {
|
|||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get cluster system meta, e.g. use in "logger"
|
||||||
|
getMeta() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.contextName,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,7 @@ export class KubeconfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unlink() {
|
unlink() {
|
||||||
logger.debug('Deleting temporary kubeconfig: ' + this.tempFile)
|
logger.info('Deleting temporary kubeconfig: ' + this.tempFile)
|
||||||
fs.unlinkSync(this.tempFile)
|
fs.unlinkSync(this.tempFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,6 @@ export class WindowManager {
|
|||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
delay: 250,
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// auto-destroy views for removed clusters
|
// auto-destroy views for removed clusters
|
||||||
|
|||||||
@ -1,261 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="content">
|
|
||||||
<b-container fluid class="h-100">
|
|
||||||
<b-row align-h="around">
|
|
||||||
<b-col lg="7">
|
|
||||||
<div class="card">
|
|
||||||
<h2>Add Cluster</h2>
|
|
||||||
<div class="add-cluster">
|
|
||||||
<b-form @submit.prevent="doAddCluster">
|
|
||||||
<b-form-group
|
|
||||||
label="Choose config:"
|
|
||||||
>
|
|
||||||
<b-form-file
|
|
||||||
v-model="file"
|
|
||||||
:state="Boolean(file)"
|
|
||||||
placeholder="Choose a file or drop it here..."
|
|
||||||
drop-placeholder="Drop file here..."
|
|
||||||
@input="reloadKubeContexts()"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="mt-3">
|
|
||||||
Selected file: {{ file ? file.name : '' }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<b-form-select
|
|
||||||
id="kubecontext-select"
|
|
||||||
v-model="kubecontext"
|
|
||||||
:options="contextNames"
|
|
||||||
@change="onSelect($event)"
|
|
||||||
/>
|
|
||||||
<b-button v-b-toggle.collapse-advanced variant="link">
|
|
||||||
Proxy settings
|
|
||||||
</b-button>
|
|
||||||
</b-form-group>
|
|
||||||
<b-collapse id="collapse-advanced">
|
|
||||||
<b-form-group
|
|
||||||
label="HTTP Proxy server. Used for communicating with Kubernetes API."
|
|
||||||
description="A HTTP proxy server URL (format: http://<address>:<port>)."
|
|
||||||
>
|
|
||||||
<b-form-input
|
|
||||||
v-model="httpsProxy"
|
|
||||||
/>
|
|
||||||
</b-form-group>
|
|
||||||
</b-collapse>
|
|
||||||
<b-form-group
|
|
||||||
label="Kubeconfig:"
|
|
||||||
v-if="status === 'ERROR' || kubecontext === 'custom'"
|
|
||||||
>
|
|
||||||
<div class="editor">
|
|
||||||
<prism-editor v-model="clusterconfig" language="yaml" />
|
|
||||||
</div>
|
|
||||||
</b-form-group>
|
|
||||||
<b-alert variant="danger" show v-if="status === 'ERROR'">
|
|
||||||
{{ errorMsg }}
|
|
||||||
<div v-if="errorDetails !== ''">
|
|
||||||
<b-button v-b-toggle.collapse-error variant="link" size="sm">
|
|
||||||
Show details
|
|
||||||
</b-button>
|
|
||||||
<b-collapse id="collapse-error">
|
|
||||||
<code>
|
|
||||||
{{ errorDetails }}
|
|
||||||
</code>
|
|
||||||
</b-collapse>
|
|
||||||
</div>
|
|
||||||
</b-alert>
|
|
||||||
<b-form-row>
|
|
||||||
<b-col>
|
|
||||||
<b-button variant="primary" type="submit" :disabled="clusterconfig === ''">
|
|
||||||
<b-spinner small v-if="isProcessing" label="Small Spinner" />
|
|
||||||
{{ addButtonText }}
|
|
||||||
</b-button>
|
|
||||||
</b-col>
|
|
||||||
</b-form-row>
|
|
||||||
</b-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-col>
|
|
||||||
<!--info-panel-->
|
|
||||||
</b-row>
|
|
||||||
</b-container>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import * as PrismEditor from 'vue-prism-editor'
|
|
||||||
import * as k8s from "@kubernetes/client-node"
|
|
||||||
import { dumpConfigYaml } from "../../../common/kube-helpers"
|
|
||||||
import ClustersMixin from "@/_vue/mixins/ClustersMixin";
|
|
||||||
import * as path from "path"
|
|
||||||
import fs from 'fs'
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
class ClusterAccessError extends Error {}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'AddClusterPage',
|
|
||||||
mixins: [ClustersMixin],
|
|
||||||
props: { },
|
|
||||||
components: {
|
|
||||||
PrismEditor,
|
|
||||||
},
|
|
||||||
data(){
|
|
||||||
return {
|
|
||||||
file: null,
|
|
||||||
filepath: null,
|
|
||||||
clusterconfig: "",
|
|
||||||
httpsProxy: "",
|
|
||||||
kubecontext: "",
|
|
||||||
status: "",
|
|
||||||
errorMsg: "",
|
|
||||||
errorCluster: "",
|
|
||||||
errorDetails: "",
|
|
||||||
seenContexts: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted: function() {
|
|
||||||
this.filepath = path.join(process.env.HOME, '.kube', 'config')
|
|
||||||
this.file = new File(fs.readFileSync(this.filepath), this.filepath)
|
|
||||||
this.$store.dispatch("reloadAvailableKubeContexts", this.filepath);
|
|
||||||
this.seenContexts = JSON.parse(JSON.stringify(this.$store.getters.seenContexts)) // clone seenContexts from store
|
|
||||||
this.storeSeenContexts()
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isProcessing: function() {
|
|
||||||
return this.status === "PROCESSING";
|
|
||||||
},
|
|
||||||
addButtonText: function() {
|
|
||||||
if (this.kubecontext === "custom") {
|
|
||||||
return "Add Cluster(s)"
|
|
||||||
} else {
|
|
||||||
return "Add Cluster"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contextNames: function() {
|
|
||||||
const configs = this.availableContexts
|
|
||||||
const names = configs.map((kc) => {
|
|
||||||
return { text: kc.currentContext + (this.isNewContext(kc.currentContext) ? " (new)": ""), value: dumpConfigYaml(kc) }
|
|
||||||
})
|
|
||||||
names.unshift({text: "Select kubeconfig", value: ""})
|
|
||||||
names.push({text: "Custom ...", value: "custom"})
|
|
||||||
|
|
||||||
return names;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
reloadKubeContexts() {
|
|
||||||
this.filepath = this.file.path
|
|
||||||
this.$store.dispatch("reloadAvailableKubeContexts", this.file.path);
|
|
||||||
},
|
|
||||||
isNewContext(context) {
|
|
||||||
return this.newContexts.indexOf(context) > -1
|
|
||||||
},
|
|
||||||
storeSeenContexts() {
|
|
||||||
const configs = this.$store.getters.availableKubeContexts
|
|
||||||
const contexts = configs.map((kc) => {
|
|
||||||
return kc.currentContext
|
|
||||||
})
|
|
||||||
this.$store.dispatch("addSeenContexts", contexts)
|
|
||||||
},
|
|
||||||
onSelect: function() {
|
|
||||||
this.status = "";
|
|
||||||
if (this.kubecontext === "custom") {
|
|
||||||
this.clusterconfig = "";
|
|
||||||
} else {
|
|
||||||
this.clusterconfig = this.kubecontext;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doAddCluster: async function() {
|
|
||||||
// Clear previous error details
|
|
||||||
this.errorMsg = ""
|
|
||||||
this.errorCluster = ""
|
|
||||||
this.errorDetails = ""
|
|
||||||
this.status = "PROCESSING"
|
|
||||||
try {
|
|
||||||
const kc = new k8s.KubeConfig();
|
|
||||||
kc.loadFromString(this.clusterconfig); // throws TypeError if we cannot parse kubeconfig
|
|
||||||
const clusterId = uuidv4();
|
|
||||||
// We need to store the kubeconfig to "app-home"/
|
|
||||||
if (this.kubecontext === "custom") {
|
|
||||||
this.filepath = saveConfigToAppFiles(clusterId, this.clusterconfig)
|
|
||||||
}
|
|
||||||
const clusterInfo = {
|
|
||||||
id: clusterId,
|
|
||||||
kubeConfigPath: this.filepath,
|
|
||||||
contextName: kc.currentContext,
|
|
||||||
preferences: {
|
|
||||||
clusterName: kc.currentContext
|
|
||||||
},
|
|
||||||
workspace: this.$store.getters.currentWorkspace.id
|
|
||||||
}
|
|
||||||
if (this.httpsProxy) {
|
|
||||||
clusterInfo.preferences.httpsProxy = this.httpsProxy
|
|
||||||
}
|
|
||||||
console.log("sending clusterInfo:", clusterInfo)
|
|
||||||
let res = await this.$store.dispatch('addCluster', clusterInfo)
|
|
||||||
console.log("addCluster result:", res)
|
|
||||||
if(!res){
|
|
||||||
this.status = "ERROR";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.status = "SUCCESS"
|
|
||||||
this.$router.push({
|
|
||||||
name: "cluster-page",
|
|
||||||
params: {
|
|
||||||
id: res.id
|
|
||||||
},
|
|
||||||
}).catch((err) => {})
|
|
||||||
} catch (error) {
|
|
||||||
console.log("addCluster raised:", error)
|
|
||||||
if(typeof error === 'string') {
|
|
||||||
this.errorMsg = error;
|
|
||||||
} else if(error instanceof TypeError) {
|
|
||||||
this.errorMsg = "cannot parse kubeconfig";
|
|
||||||
} else if(error.response && error.response.statusCode === 401) {
|
|
||||||
this.errorMsg = "invalid kubeconfig (access denied)"
|
|
||||||
} else if(error.message) {
|
|
||||||
this.errorMsg = error.message
|
|
||||||
} else if(error instanceof ClusterAccessError) {
|
|
||||||
this.errorMsg = `Invalid kubeconfig context ${error.context}`
|
|
||||||
this.errorCluster = error.cluster
|
|
||||||
this.errorDetails = error.details
|
|
||||||
}
|
|
||||||
this.status = "ERROR";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.help{
|
|
||||||
border-left: 1px solid #353a3e;
|
|
||||||
padding-top: 20px;
|
|
||||||
&:first-child{
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
h3{
|
|
||||||
padding: 0.75rem 0 0.75rem 0;
|
|
||||||
}
|
|
||||||
height: 100vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2{
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-cluster {
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-link {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -16,16 +16,17 @@ import { tracker } from "../../../common/tracker";
|
|||||||
import { clusterStore } from "../../../common/cluster-store";
|
import { clusterStore } from "../../../common/cluster-store";
|
||||||
import { workspaceStore } from "../../../common/workspace-store";
|
import { workspaceStore } from "../../../common/workspace-store";
|
||||||
import { v4 as uuid } from "uuid"
|
import { v4 as uuid } from "uuid"
|
||||||
|
import { navigate } from "../../navigation";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class AddCluster extends React.Component {
|
export class AddCluster extends React.Component {
|
||||||
readonly custom: any = "custom"
|
readonly custom: any = "custom"
|
||||||
@observable.ref clusterConfig: KubeConfig;
|
@observable.ref clusterConfig: KubeConfig;
|
||||||
@observable.ref kubeConfig: KubeConfig; // local ~/.kube/config (if available)
|
@observable.ref kubeConfig: KubeConfig; // local ~/.kube/config (if available)
|
||||||
|
@observable.ref error: React.ReactNode;
|
||||||
|
|
||||||
@observable isWaiting = false
|
@observable isWaiting = false
|
||||||
@observable showSettings = false
|
@observable showSettings = false
|
||||||
@observable error = ""
|
|
||||||
@observable proxyServer = ""
|
@observable proxyServer = ""
|
||||||
@observable customConfig = ""
|
@observable customConfig = ""
|
||||||
|
|
||||||
@ -49,13 +50,15 @@ export class AddCluster extends React.Component {
|
|||||||
@computed get clusterOptions() {
|
@computed get clusterOptions() {
|
||||||
const options: SelectOption<KubeConfig>[] = [];
|
const options: SelectOption<KubeConfig>[] = [];
|
||||||
if (this.kubeConfig) {
|
if (this.kubeConfig) {
|
||||||
splitConfig(this.kubeConfig).forEach(kubeConfig => {
|
const contexts = splitConfig(this.kubeConfig)
|
||||||
const contextName = kubeConfig.getCurrentContext();
|
.filter(kc => !clusterStore.hasContext(kc.currentContext));
|
||||||
|
|
||||||
|
contexts.forEach(kubeConfig => {
|
||||||
const isNew = false; // fixme: detect new context since last visit
|
const isNew = false; // fixme: detect new context since last visit
|
||||||
options.push({
|
options.push({
|
||||||
value: kubeConfig,
|
value: kubeConfig,
|
||||||
label: <>
|
label: <>
|
||||||
{contextName}
|
{kubeConfig.currentContext}
|
||||||
{isNew && <span className="new"> <Trans>(new)</Trans></span>}
|
{isNew && <span className="new"> <Trans>(new)</Trans></span>}
|
||||||
</>,
|
</>,
|
||||||
})
|
})
|
||||||
@ -72,16 +75,16 @@ export class AddCluster extends React.Component {
|
|||||||
tracker.event("cluster", "add");
|
tracker.event("cluster", "add");
|
||||||
const { clusterConfig, customConfig, proxyServer } = this;
|
const { clusterConfig, customConfig, proxyServer } = this;
|
||||||
const clusterId = uuid();
|
const clusterId = uuid();
|
||||||
|
this.isWaiting = true
|
||||||
|
this.error = ""
|
||||||
try {
|
try {
|
||||||
const config = this.isCustom ? loadConfig(customConfig) : clusterConfig;
|
const config = this.isCustom ? loadConfig(customConfig) : clusterConfig;
|
||||||
if (!config) {
|
if (!config) {
|
||||||
this.error = "Please select kubeconfig"
|
this.error = <Trans>Please select kubeconfig</Trans>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.error = ""
|
|
||||||
this.isWaiting = true
|
|
||||||
validateConfig(config);
|
validateConfig(config);
|
||||||
clusterStore.addCluster({
|
await clusterStore.addCluster({
|
||||||
id: clusterId,
|
id: clusterId,
|
||||||
kubeConfigPath: saveConfigToAppFiles(clusterId, config),
|
kubeConfigPath: saveConfigToAppFiles(clusterId, config),
|
||||||
workspace: workspaceStore.currentWorkspaceId,
|
workspace: workspaceStore.currentWorkspaceId,
|
||||||
@ -90,7 +93,8 @@ export class AddCluster extends React.Component {
|
|||||||
clusterName: config.currentContext,
|
clusterName: config.currentContext,
|
||||||
httpsProxy: proxyServer || undefined,
|
httpsProxy: proxyServer || undefined,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
navigate("/");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.error = String(err);
|
this.error = String(err);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .clusters {
|
> .clusters {
|
||||||
@include hidden-scrollbar;
|
//@include hidden-scrollbar; // fixme: uncomment after refactoring tooltip.tsx
|
||||||
--flex-gap: #{$padding * 2};
|
--flex-gap: #{$padding * 2};
|
||||||
padding: $padding;
|
padding: $padding;
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { addClusterURL } from "../+add-cluster";
|
|||||||
import { clusterSettingsURL } from "../+cluster-settings";
|
import { clusterSettingsURL } from "../+cluster-settings";
|
||||||
import { landingURL } from "../+landing-page";
|
import { landingURL } from "../+landing-page";
|
||||||
import { Tooltip, TooltipContent } from "../tooltip";
|
import { Tooltip, TooltipContent } from "../tooltip";
|
||||||
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
|
|
||||||
// fixme: allow to rearrange clusters with drag&drop
|
// fixme: allow to rearrange clusters with drag&drop
|
||||||
// fixme: disconnect cluster from context-menu
|
// fixme: disconnect cluster from context-menu
|
||||||
@ -50,7 +51,6 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
label: _i18n._(t`Settings`),
|
label: _i18n._(t`Settings`),
|
||||||
click: () => navigate(clusterSettingsURL())
|
click: () => navigate(clusterSettingsURL())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (cluster.initialized) {
|
if (cluster.initialized) {
|
||||||
menu.append(new MenuItem({
|
menu.append(new MenuItem({
|
||||||
label: _i18n._(t`Disconnect`),
|
label: _i18n._(t`Disconnect`),
|
||||||
@ -59,6 +59,16 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: _i18n._(t`Remove`),
|
||||||
|
click: () => {
|
||||||
|
ConfirmDialog.open({
|
||||||
|
ok: () => clusterStore.removeById(cluster.id),
|
||||||
|
labelOk: _i18n._(t`Remove`),
|
||||||
|
message: <p>Are you sure want to remove cluster <b title={cluster.id}>{cluster.contextName}</b>?</p>,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
menu.popup({
|
menu.popup({
|
||||||
window: remote.getCurrentWindow()
|
window: remote.getCurrentWindow()
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user