Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions frontend/kubecloud/src/components/DashboardSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ const navigationItems = [
title: 'Clusters',
icon: 'mdi-server'
},
{
key: 'vms',
title: 'Virtual Machines',
icon: 'mdi-desktop-classic'
},
{
key: 'nodes',
title: 'My Nodes',
Expand Down
14 changes: 14 additions & 0 deletions frontend/kubecloud/src/components/dashboard/OverviewCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '../../stores/user'
import { useVMStore } from '../../stores/vms'
import StatsGrid from '../StatsGrid.vue'

interface Cluster {
Expand Down Expand Up @@ -65,6 +66,7 @@ interface Props {
const props = defineProps<Props>()
const router = useRouter()
const userStore = useUserStore()
const vmStore = useVMStore()

const uptimeHours = computed(() => {
return props.clusters
Expand All @@ -79,6 +81,11 @@ const statsData = computed(() => [
value: props.clusters.length,
label: 'Active Clusters'
},
{
icon: 'mdi-desktop-classic',
value: vmStore.vmCount,
label: 'Virtual Machines'
},
{
icon: 'mdi-currency-usd',
value: `$${userStore.netBalance.toFixed(2)}`,
Expand All @@ -101,6 +108,13 @@ const quickActions = [
variant: 'elevated' as const,
handler: () => router.push('/deploy')
},
{
label: 'Deploy VM',
icon: 'mdi-desktop-classic',
color: 'primary',
variant: 'outlined' as const,
handler: () => emit('navigate', 'vms')
},
{
label: 'Reserve Node',
icon: 'mdi-server-plus',
Expand Down
10 changes: 10 additions & 0 deletions frontend/kubecloud/src/components/dashboard/VMsCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<div>

</div>
</template>
<script setup lang="ts">
import useVMs from '@/composables/vms'

const { vmCount, isLoading, fetchVMs } = useVMs()
</script>
160 changes: 160 additions & 0 deletions frontend/kubecloud/src/composables/vms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { ref } from 'vue'
import * as vm from '../utils/vm'
import { useNotificationStore } from '@/stores/notifications'
import type { VMInput, VM } from '@/types/vms'

/**
* Vue composable for managing Virtual Machine operations
*
* Provides reactive state management and methods for VM CRUD operations
* including deployment, fetching, deletion, and state tracking.
*
* @returns Object containing VM state and management methods
*
* @example
* ```typescript
* const { vmCount, isLoading, fetchVMs, deployVM, getVM } = useVMs()
*
* // Fetch all VMs
* await fetchVMs()
*
* // Deploy a new VM
* await deployVM({
* name: 'my-vm',
* node_id: 1,
* cpu: 2,
* memory: 4096,
* disk: 20
* })
* ```
*/
export default function useVMs() {
/** Reactive count of total VMs */
const vmCount = ref<number>(0)

/** Reactive loading state for all VM operations */
const isLoading = ref<boolean>(false)

/** Notification store instance for error handling */
const notificationStore = useNotificationStore()

/** Reactive array of VM objects */
const vms = ref<VM[]>([])

/**
* Fetches all Virtual Machines from the API
*
* Updates the vmCount and vms reactive references with the fetched data.
* Shows error notifications if the request fails.
*
* @async
* @throws Will show error notification if API request fails
*/
const fetchVMs = async () => {
isLoading.value = true
try {
const response = await vm.listVMs()
vmCount.value = response.data.length
vms.value = response.data
} catch (err: any) {
notificationStore.error('Failed to fetch VMs:', err)
} finally {
isLoading.value = false
}
}

/**
* Deploys a new Virtual Machine
*
* @param {VMInput} vmData - The VM configuration data
* @async
* @throws Will show error notification if deployment fails
*/
const deployVM = async (vmData: VMInput) => {
isLoading.value = true
try {
await vm.deployVM(vmData)
} catch (err: any) {
notificationStore.error('Failed to deploy VM:', err)
} finally {
isLoading.value = false
}
}

/**
* Retrieves a specific Virtual Machine by ID
*
* @param id - The unique identifier of the VM
* @returns Promise that resolves to the VM data or undefined if error occurs
* @async
* @throws Will show error notification if fetch fails
*/
const getVM = async (id: number) => {
isLoading.value = true
try {
const response = await vm.getVM(id)
return response.data
} catch (err: any) {
notificationStore.error('Failed to fetch VM:', err)
} finally {
isLoading.value = false
}
}

/**
* Deletes a specific Virtual Machine by ID
*
* @param id - The unique identifier of the VM to delete
* @async
* @throws Will show error notification if deletion fails
*/
const deleteVM = async (id: number) => {
isLoading.value = true
try {
await vm.deleteVM(id)
} catch (err: any) {
notificationStore.error('Failed to delete VM:', err)
} finally {
isLoading.value = false
}
}

/**
* Deletes all Virtual Machines
*
* ⚠️ **Warning**: This operation will permanently delete all VMs.
* Use with caution as this action cannot be undone.
*
* @async
* @throws Will show error notification if bulk deletion fails
*/
const deleteAllVMs = async () => {
isLoading.value = true
try {
await vm.deleteAllVMs()
} catch (err: any) {
notificationStore.error('Failed to delete all VMs:', err)
} finally {
isLoading.value = false
}
}

return {
/** Reactive count of total VMs */
vmCount,
/** Reactive loading state indicator */
isLoading,
/** Reactive array of VM objects */
vms,
/** Fetch all VMs from the API */
fetchVMs,
/** Deploy a new VM */
deployVM,
/** Get a specific VM by ID */
getVM,
/** Delete a specific VM by ID */
deleteVM,
/** Delete all VMs (use with caution) */
deleteAllVMs,
}
}
143 changes: 143 additions & 0 deletions frontend/kubecloud/src/stores/vms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useNotificationStore } from './notifications'
import { api } from '../utils/api'

export interface VM {
id: number
project_name: string
vm: {
name: string
node_id: number
cpu: number
memory: number
root_size: number
disk_size: number
flist?: string
entrypoint?: string
env_vars?: Record<string, string>
status?: string
}
created_at: string
}

export interface VMInput {
name: string
node_id: number
cpu: number
memory: number
root_size: number
disk_size: number
flist?: string
entrypoint?: string
env_vars?: Record<string, string>
}

export const useVMStore = defineStore('vms', () => {
const vms = ref<VM[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const notificationStore = useNotificationStore()

const fetchVMs = async () => {
isLoading.value = true
error.value = null
try {
const response = await api.get('/v1/deployments/vms', { requiresAuth: true })
vms.value = Array.isArray(response.data) ? response.data : []
} catch (err: any) {
const errorMsg = err.message || 'Failed to fetch VMs'
error.value = errorMsg
notificationStore.error('Error', errorMsg)
} finally {
isLoading.value = false
}
}

const deployVM = async (vmData: VMInput) => {
isLoading.value = true
error.value = null
try {
const response = await api.post('/v1/deployments/vms', vmData, { requiresAuth: true })
notificationStore.success('VM Deployment Started', `VM "${vmData.name}" deployment has been initiated`)
await fetchVMs() // Refresh the list
return response.data
} catch (err: any) {
const errorMsg = err.message || 'Failed to deploy VM'
error.value = errorMsg
notificationStore.error('Deployment Failed', errorMsg)
throw err
} finally {
isLoading.value = false
}
}

const getVM = async (id: number) => {
isLoading.value = true
error.value = null
try {
const response = await api.get(`/v1/deployments/vms/${id}`, { requiresAuth: true })
return response.data
} catch (err: any) {
const errorMsg = err.message || 'Failed to fetch VM details'
error.value = errorMsg
notificationStore.error('Error', errorMsg)
throw err
} finally {
isLoading.value = false
}
}

const deleteVM = async (id: number) => {
isLoading.value = true
error.value = null
try {
await api.delete(`/v1/deployments/vms/${id}`, { requiresAuth: true })
notificationStore.success('VM Deletion Started', 'VM deletion has been initiated')
await fetchVMs() // Refresh the list
} catch (err: any) {
const errorMsg = err.message || 'Failed to delete VM'
error.value = errorMsg
notificationStore.error('Deletion Failed', errorMsg)
throw err
} finally {
isLoading.value = false
}
}

const deleteAllVMs = async () => {
isLoading.value = true
error.value = null
try {
// Delete all VMs one by one since there's no bulk delete endpoint
const deletePromises = vms.value.map(vm => api.delete(`/v1/deployments/vms/${vm.id}`, { requiresAuth: true }))
await Promise.all(deletePromises)
notificationStore.success('All VMs Deletion Started', 'All VM deletions have been initiated')
await fetchVMs() // Refresh the list
} catch (err: any) {
const errorMsg = err.message || 'Failed to delete all VMs'
error.value = errorMsg
notificationStore.error('Bulk Deletion Failed', errorMsg)
throw err
} finally {
isLoading.value = false
}
}

// Computed properties
const vmCount = computed(() => vms.value.length)
const runningVMs = computed(() => vms.value.filter(vm => vm.vm.status === 'running').length)

return {
vms,
isLoading,
error,
vmCount,
runningVMs,
fetchVMs,
deployVM,
getVM,
deleteVM,
deleteAllVMs
}
})
29 changes: 29 additions & 0 deletions frontend/kubecloud/src/types/vms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface VM {
id: number
project_name: string
vm: {
name: string
node_id: number
cpu: number
memory: number
root_size: number
disk_size: number
flist?: string
entrypoint?: string
env_vars?: Record<string, string>
status?: string
}
created_at: string
}

export interface VMInput {
name: string
node_id: number
cpu: number
memory: number
root_size: number
disk_size: number
flist?: string
entrypoint?: string
env_vars?: Record<string, string>
}
Loading
Loading