diff --git a/frontend/kubecloud/src/components/dashboard/ManageClusterView.vue b/frontend/kubecloud/src/components/dashboard/ManageClusterView.vue index f356e986..5c58869f 100644 --- a/frontend/kubecloud/src/components/dashboard/ManageClusterView.vue +++ b/frontend/kubecloud/src/components/dashboard/ManageClusterView.vue @@ -105,6 +105,20 @@ {{ node.contract_id || '-' }} + + + + Repair Node + + + + + + + Repair {{ nodeToRepair }} node + + + +

Pick a node in order to replace the old one

+ +
+ + + + + Cancel + + + Repair + + +
+
+ @@ -185,13 +233,21 @@ import { useClusterStore } from '../../stores/clusters' import { useNotificationStore } from '../../stores/notifications' import { useKubeconfig } from '../../composables/useKubeconfig' import { api } from '../../utils/api' +import NodeSelect from '../ui/NodeSelect.vue'; +import { useNodes } from '../../composables/useNodes'; +import { getAvailableCPU, getAvailableRAM, getAvailableStorage, getTotalCPU } from '../../utils/nodeNormalizer'; import { formatDate } from '../../utils/dateUtils' import { userService } from '@/utils/userService' import { useUserStore } from '@/stores/user' +import useNodeStoragePool from '@/composables/useNodeStoragePool' const userStore = useUserStore() +const addFormNodeId = ref(null); +const { nodes, loading: nodesLoading, fetchNodes } = useNodes() +onMounted(fetchNodes) + const haveEnoughBalance = computed(() => { return userStore.netBalance >= 5 }) @@ -210,6 +266,64 @@ const loading = ref(true) const notFound = ref(false) const deleteConfirmDialog = ref(false); const nodeToDelete = ref(''); +const nodeToRepair = ref(''); + +const repairing = ref(false) +async function repairNode(oldNodeName: string, newNode: number) { + const name = cluster.value?.cluster.name + const node = filteredNodes.value.find(n => n.original_name === oldNodeName) + if (!name || !node) { + console.warn('cluster or it\'s name not found', cluster.value) + return + } + + repairing.value = true + + const { data } = await userService.removeNodeFromDeployment(name, oldNodeName) + await new Promise(res => setTimeout(res, 5000)) + if (await userService.waitTaskTocomplete((data as any).task_id)) { + const {data: d } = await userService.addNodeToDeployment(name, { + name: name, + nodes: [ + { + name: oldNodeName, + type: node.type, + node_id: newNode, + cpu: node.cpu, + memory: node.memory, + root_size: node.root_size, + disk_size: node.disk_size, + env_vars: node.env_vars, + } + ] + }) + await new Promise(res => setTimeout(res, 5000)) + await userService.waitTaskTocomplete((d as any).task_id) + } + repairing.value = false +} + +const { validateNodeStoragePool, createStoragePoolError, failedToCheckStoragePoolError } = useNodeStoragePool() +const nodeValidationError = ref('') +const validatingNode = ref(false) + +async function validateNode(nodeId: number | null) { +try { + nodeValidationError.value = '' + validatingNode.value = true + if (!nodeId || !nodes.value.find((node) => node.nodeId === nodeId)) return + const isValid = await validateNodeStoragePool(/* addFormStorage.value */ 25, nodeId) + if (!isValid) { + nodeValidationError.value = createStoragePoolError(nodeId) + return + } +} catch (error) { + console.error(error) + nodeValidationError.value = failedToCheckStoragePoolError().message +} finally { + validatingNode.value = false +} +} const projectName = computed(() => route.params.id?.toString() || '') const cluster = computed(() => @@ -222,6 +336,10 @@ const filteredNodes = computed(() => { } return [] }) +const filteredNodesMap = computed(() => filteredNodes.value.reduce((r, n) => { + r[n.original_name] = n.node_id + return r +}, {} as {[key: string]: number})) const totalCPU = computed(() => { return filteredNodes.value.length diff --git a/frontend/kubecloud/src/components/deploy/Step1DefineVMs.vue b/frontend/kubecloud/src/components/deploy/Step1DefineVMs.vue index 3058d7bd..5a156d74 100644 --- a/frontend/kubecloud/src/components/deploy/Step1DefineVMs.vue +++ b/frontend/kubecloud/src/components/deploy/Step1DefineVMs.vue @@ -61,6 +61,11 @@ Add Master + + + To achieve high availability in your cluster, consider having at least three master nodes. + +

No master nodes configured

diff --git a/frontend/kubecloud/src/utils/userService.ts b/frontend/kubecloud/src/utils/userService.ts index c8dc4229..e5f62b7f 100644 --- a/frontend/kubecloud/src/utils/userService.ts +++ b/frontend/kubecloud/src/utils/userService.ts @@ -311,6 +311,20 @@ export class UserService { return response.data.data } + async waitTaskTocomplete(id: string): Promise { + const res = await api.get>(`/v1/workflow/${id}`) + if (res.data.data === "failed") { + return false + } + + if (res.data.data === "completed") { + return true + } + + await new Promise(res => setTimeout(res, 5000)) + return this.waitTaskTocomplete(id) + } + private async trackNodeStatus(nodeId: number, targetStatus: "rented" | "rentable", maxAttempts: number = 20, interval: number = 5000) { await new Promise((resolve, reject) => { let attempts = 0