Skip to content

Commit bf91a24

Browse files
added sse connection
1 parent 4b783b2 commit bf91a24

11 files changed

Lines changed: 201 additions & 62 deletions

File tree

frontend/kubecloud-v2/assets/scss/global.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,13 @@ blockquote > * {
152152
}
153153
}
154154

155+
156+
.iziToast {
157+
.iziToast-body {
158+
.iziToast-title {
159+
float: none !important;
160+
display: block !important;
161+
margin-bottom: 6px !important;
162+
}
163+
}
164+
}

frontend/kubecloud-v2/components/deploy-cluster/define/DefineVMsForm.vue

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,7 @@
2929
</template>
3030

3131
<script setup lang="ts">
32-
defineProps<{ modelValue: ClusterForm }>()
32+
import type { ModelsSSHKey } from "~/generated/api"
3333
34-
const api = useApi()
35-
const { state: sshKeys } = useAsyncState(async () => {
36-
const { data } = await api.users.listSshKeys()
37-
return data.data ?? []
38-
}, [], { immediate: $meta.client })
34+
defineProps<{ modelValue: ClusterForm, sshKeys: ModelsSSHKey[] }>()
3935
</script>

frontend/kubecloud-v2/components/deploy-cluster/place/PlaceVMsForm.vue

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
11
<template>
22
<v-row>
3+
{{ $props.modelValue.region }}
34
<v-col cols="12">
4-
<PlaceVMsHead v-model="$props.modelValue" />
5+
<PlaceVMsHead
6+
v-model="$props.modelValue"
7+
/>
58
</v-col>
69

710
<v-col cols="12">
811
<PlaceVMsNodes :cluster="$props.modelValue" />
912
</v-col>
10-
11-
<!-- <v-col cols="6">
12-
<DefineVMsNodes
13-
icon="mdi-server"
14-
node-type="Master"
15-
:ssh-keys="sshKeys"
16-
:nodes="$props.modelValue.masters"
17-
@add-node="$props.modelValue.masters.push(createClusterNode())"
18-
@remove-node="$props.modelValue.masters = $props.modelValue.masters.filter(master => master.id !== $event)"
19-
/>
20-
</v-col>
21-
22-
<v-col cols="6">
23-
<DefineVMsNodes
24-
icon="mdi-console"
25-
node-type="Worker"
26-
:ssh-keys="sshKeys"
27-
:nodes="$props.modelValue.workers"
28-
@add-node="$props.modelValue.workers.push(createClusterNode())"
29-
@remove-node="$props.modelValue.workers = $props.modelValue.workers.filter(worker => worker.id !== $event)"
30-
/>
31-
</v-col> -->
3213
</v-row>
3314
</template>
3415

frontend/kubecloud-v2/components/deploy-cluster/place/PlaceVMsHead.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
variant="outlined"
3737
density="compact"
3838
hide-details
39+
@update:model-value="() => {
40+
$props.modelValue.masters = $props.modelValue.masters.map(master => ({ ...master, node: null }))
41+
$props.modelValue.workers = $props.modelValue.workers.map(worker => ({ ...worker, node: null }))
42+
}"
3943
/>
4044
</v-card>
4145
</template>

frontend/kubecloud-v2/components/deploy-cluster/place/PlaceVMsNode.vue

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,19 @@
3232

3333
<v-expand-transition>
3434
<div v-if="activeNode">
35-
<NodeListItem :active="modelValue.node?.valid" :deactive="!modelValue.node?.valid" :node="activeNode!" />
35+
<NodeListItem class="mb-4" :active="modelValue.node?.valid" :deactive="!modelValue.node?.valid" :node="activeNode!" />
3636
</div>
3737
</v-expand-transition>
3838

3939
<v-table class="border-t border-0 border-dashed" :class="{ 'table-hidden-overflow': !!loadingNode }" :style="{ maxHeight: '400px' }">
4040
<tbody>
4141
<tr
42-
v-for="(node, index) in nodes"
42+
v-for="(node, index) in filteredNodes"
4343
:key="node.nodeId"
44+
:class="{ 'border-t border-0 border-dashed': index !== 0 }"
4445
>
4546
<VDivider v-if="index !== 0" />
47+
4648
<NodeListItem
4749
:disabled="pickedNodes.includes(node.nodeId!)"
4850
:node="node"
@@ -61,22 +63,37 @@ import type { HandlersNodesWithDiscount } from "~/generated/api"
6163
const props = defineProps<{ modelValue: ClusterNode, pickedNodes: number[], nodes: HandlersNodesWithDiscount[] }>()
6264
const emit = defineEmits<{
6365
(e: "reserve", nodeId: number): void
64-
(e: "pick", node: null | { id: number, valid: boolean }): void
66+
(e: "pick", node: null | { id: number, raw: HandlersNodesWithDiscount, valid: boolean }): void
6567
}>()
6668
6769
const { loadingNode, setLoadingNode } = inject(NodePickCtxKey)!
6870
6971
const activeNode = computed(() => props.nodes.filter(n => n.nodeId === props.modelValue.node?.id)[0])
72+
const filteredNodes = computed(() => {
73+
return props.nodes.filter((n) => {
74+
if (props.pickedNodes.includes(n.nodeId!) || props.modelValue.useFullNodeCapabilities) {
75+
return true
76+
}
77+
78+
const cpu = n.total_resources!.cru!
79+
const ram = n.total_resources!.mru! - n.used_resources!.mru!
80+
const ssd = n.total_resources!.sru! - n.used_resources!.sru!
81+
82+
return cpu >= props.modelValue.cpu
83+
&& ram >= (props.modelValue.memory / 1024 ** 3)
84+
&& ssd >= (props.modelValue.disk / 1024 ** 3)
85+
})
86+
})
7087
7188
const api = useApi()
7289
const { execute: getNodeStoragePool } = useAsyncState(async (node: HandlersNodesWithDiscount) => {
7390
setLoadingNode(node.nodeId!)
7491
try {
7592
/* const { data } = */ await api.nodes.getNodeStoragePool(node.nodeId!.toString())
76-
emit("pick", { id: node.nodeId!, valid: true })
93+
emit("pick", { id: node.nodeId!, raw: node, valid: true })
7794
}
7895
catch {
79-
emit("pick", { id: node.nodeId!, valid: false })
96+
emit("pick", { id: node.nodeId!, raw: node, valid: false })
8097
}
8198
finally {
8299
setLoadingNode(undefined)

frontend/kubecloud-v2/components/deploy-cluster/place/PlaceVMsNodes.vue

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,20 +102,13 @@ const { state: _nodes } = useAsyncState(async () => {
102102
}, [], { immediate: $meta.client, resetOnExecute: false })
103103
104104
function onReserveNode(nodeId: number): void {
105-
// const __nodes = _nodes.value.map(n => n.nodeId === nodeId ? { ...n, rented: true } : n)
106-
// _nodes.value = sortNodes(__nodes)
107105
_nodes.value = _nodes.value.map(n => n.nodeId === nodeId ? { ...n, rented: true } : n)
108106
}
109107
110108
const pickedNodes = computed(() => {
111109
return props.cluster.masters.concat(props.cluster.workers).map(n => n.node?.id).filter(v => !!v) as number[]
112110
})
113111
const nodes = computed(() => {
114-
// const pickedNodes = props.cluster.masters.concat(props.cluster.workers).map(n => n.nodeId)
115-
return _nodes.value.filter((n) => {
116-
const regionFilter = !props.cluster.region || n.location?.region === props.cluster.region
117-
// return regionFilter && !pickedNodes.includes(n.nodeId!)
118-
return regionFilter
119-
})
112+
return _nodes.value.filter(n => !props.cluster.region || n.location?.region === props.cluster.region)
120113
})
121114
</script>

frontend/kubecloud-v2/nuxt.config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,13 @@ export default defineNuxtConfig({
5858
standalone: false,
5959
},
6060
},
61+
toast: {
62+
settings: {
63+
timeout: 10_000,
64+
resetOnHover: true,
65+
maxWidth: 400,
66+
messageLineHeight: "18",
67+
titleLineHeight: "18",
68+
},
69+
},
6170
})

frontend/kubecloud-v2/pages/dashboard/clusters/deploy.vue

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,11 @@
5656

5757
<v-stepper-window>
5858
<v-stepper-window-item eager :value="1">
59-
<v-form v-model="defineFormValid">
60-
<DefineVMsForm v-model="cluster" />
61-
</v-form>
59+
<DefineVMsForm v-model="cluster" :ssh-keys="sshKeys" />
6260
</v-stepper-window-item>
6361

6462
<v-stepper-window-item eager :value="2">
65-
<v-form v-model="defineFormValid">
66-
<PlaceVMsForm v-model="cluster" />
67-
</v-form>
63+
<PlaceVMsForm v-model="cluster" />
6864
</v-stepper-window-item>
6965

7066
<v-stepper-window-item eager :value="3">
@@ -80,7 +76,17 @@
8076
text="Back"
8177
variant="text"
8278
:disabled="step === 1"
83-
@click="prev"
79+
@click="() => {
80+
if (step === 2) {
81+
$nextTick(() => {
82+
cluster.masters = cluster.masters.map(master => ({ ...master, node: null }))
83+
cluster.workers = cluster.workers.map(worker => ({ ...worker, node: null }))
84+
})
85+
}
86+
87+
prev()
88+
}
89+
"
8490
/>
8591

8692
<v-btn
@@ -98,12 +104,12 @@
98104

99105
<v-btn
100106
v-else
101-
disabled
102107
prepend-icon="mdi-rocket-launch"
103108
text="Deploy Cluster"
104109
variant="tonal"
105110
color="success"
106-
@click="console.log('deploy cluster')"
111+
:loading="deploying"
112+
@click="deployCluster()"
107113
/>
108114
</div>
109115
</v-stepper>
@@ -112,6 +118,8 @@
112118
</template>
113119

114120
<script setup lang="ts">
121+
import type { HandlersNodeInput } from "~/generated/api"
122+
115123
const { drawer, container } = inject(DashboardLayoutCtxKey)!
116124
117125
onMounted(drawer.close)
@@ -120,6 +128,12 @@ onBeforeUnmount(drawer.open)
120128
onMounted(container.fluidize)
121129
onBeforeUnmount(container.containerize)
122130
131+
const api = useApi()
132+
const { state: sshKeys } = useAsyncState(async () => {
133+
const { data } = await api.users.listSshKeys()
134+
return data.data ?? []
135+
}, [], { immediate: $meta.client })
136+
123137
const [DefineStepperLine, ReuseStepperLine] = createReusableTemplate({
124138
props: { completed: Boolean },
125139
})
@@ -128,17 +142,64 @@ const [DefineStepperItem, ReuseStepperItem] = createReusableTemplate({
128142
props: { title: String, step: Number, value: Number, completed: Boolean },
129143
})
130144
131-
const cluster = ref<ClusterForm>({
132-
name: "engine789",
133-
region: null,
134-
masters: [createClusterNode({ type: "leader", name: "Leader" })],
135-
workers: [],
136-
})
145+
const cluster = ref(createClusterForm())
137146
138147
const step = ref(1)
139-
const defineFormValid = ref(false)
148+
const defineFormValid = computed(() => {
149+
const { name, masters, workers } = cluster.value
150+
return name.length >= 3 && masters.concat(workers).every(isValidClusterNode)
151+
})
140152
const placeFormValid = computed(() => {
141153
const { masters, workers } = cluster.value
142154
return masters.every(v => v.node && v.node.valid) && workers.every(v => v.node && v.node.valid)
143155
})
156+
157+
async function toNodeInput(clusterNode: ClusterNode): Promise<HandlersNodeInput> {
158+
const keys = sshKeys.value
159+
const SSH_KEY = clusterNode.sshKeys.map(i => keys[i]!.public_key).join("\n")
160+
const root_size = 5 * 1024
161+
162+
const input: HandlersNodeInput = {
163+
name: clusterNode.name,
164+
type: clusterNode.type === "worker" ? "worker" : "master",
165+
node_id: clusterNode.node!.id,
166+
cpu: clusterNode.cpu,
167+
memory: clusterNode.memory * 1024,
168+
root_size,
169+
data_disks: [clusterNode.disk * 1024 - root_size],
170+
env_vars: {
171+
SSH_KEY,
172+
K3S_TOKEN: cluster.value.token,
173+
},
174+
}
175+
176+
if (clusterNode.useFullNodeCapabilities) {
177+
const { nodeId, total_resources, used_resources } = clusterNode.node!.raw
178+
179+
input.cpu = total_resources!.cru!
180+
input.memory = Math.floor(((total_resources!.mru! - used_resources!.mru!) * 0.99) / 1024 ** 3) * 1024
181+
182+
const pools = await api.nodes.getNodeStoragePool(nodeId!.toString()).then(v => v.data.data?.pools ?? [])
183+
if (pools.length === 0) {
184+
throw new Error("No storage pool found")
185+
}
186+
187+
input.data_disks = pools.map((pool, i) => {
188+
const size = Math.floor(pool.free! * 0.985 / 1024 ** 3)
189+
return size * 1024 - (i === 0 ? root_size : 0)
190+
})
191+
}
192+
193+
return input
194+
}
195+
196+
const { execute: deployCluster, isLoading: deploying } = useAsyncState(async () => {
197+
const { name, masters, workers } = cluster.value
198+
const nodes = await Promise.all(masters.concat(workers).map(toNodeInput))
199+
/* const { data } = */await api.deployments.deploymentsPost({
200+
name,
201+
token: cluster.value.token,
202+
nodes,
203+
})
204+
}, null, { immediate: false })
144205
</script>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export default defineNuxtPlugin(() => {
2+
// const api = useApi()
3+
// console.log("plugin?")
4+
5+
const { apiBasePath } = useRuntimeConfig().public
6+
const { accessToken } = useTokens()
7+
8+
// const toast = useToast()
9+
10+
const sse = new EventSource(`${apiBasePath}/events?token=${accessToken.value}`, { withCredentials: true })
11+
12+
const toast = useToast()
13+
sse.onopen = () => {
14+
// console.log("sse opened")
15+
}
16+
17+
sse.onmessage = (e) => {
18+
// console.log("Message", e.data)
19+
try {
20+
const d = JSON.parse(e.data)
21+
const { data } = d
22+
// console.log(d)
23+
24+
const fn = (toast as any)[d.severity]
25+
if (!fn) {
26+
// console.log(d.severity, "not found")
27+
28+
return
29+
}
30+
31+
fn.bind(toast)({ message: data.message, title: data.subject })
32+
}
33+
catch {
34+
console.error("Failed to parse message", e.data)
35+
}
36+
}
37+
38+
sse.onerror = (e) => {
39+
console.error("sse error", e)
40+
}
41+
42+
// sse.onmessage = (event) => {
43+
// console.log(event)
44+
// }
45+
46+
// sse.onerror = (event) => {
47+
// console.error(event)
48+
// }
49+
})

0 commit comments

Comments
 (0)