diff --git a/frontend/kubecloud/src/components/NodeCard.vue b/frontend/kubecloud/src/components/NodeCard.vue index 50a4edcb..bfe96055 100644 --- a/frontend/kubecloud/src/components/NodeCard.vue +++ b/frontend/kubecloud/src/components/NodeCard.vue @@ -121,11 +121,25 @@ const discountPercentage = computed(() => { const monthlyPrice = computed(() => baseNodePrice.value == null ? 'N/A' : baseNodePrice.value.toFixed(2)); const hourlyPrice = computed(() => baseNodePrice.value == null ? 'N/A' : (baseNodePrice.value / 720).toFixed(2)); const originalMonthlyPrice = computed(() => originalNodePrice.value == null ? 'N/A' : originalNodePrice.value.toFixed(2)); -const resources = [ - { icon: 'mdi-cpu-64-bit', color: '#0ea5e9', label: 'CPU:', value: () => `${props.node.cpu} vCPU` }, - { icon: 'mdi-memory', color: '#10B981', label: 'RAM:', value: () => `${props.node.ram} GB` }, - { icon: 'mdi-harddisk', color: '#38bdf8', label: 'Storage:', value: () => `${props.node.storage} GB` } -]; +const resources = computed(() => { + const totalCPU = props.node.cpu; + const usedCPU = props.node.used_cpu ?? 0; + const availableCPU = totalCPU - usedCPU; + + const totalRAM = props.node.ram; + const usedRAM = props.node.used_ram ?? 0; + const availableRAM = totalRAM - usedRAM; + + const totalStorage = props.node.storage; + const usedStorage = props.node.used_storage ?? 0; + const availableStorage = totalStorage - usedStorage; + + return [ + { icon: 'mdi-cpu-64-bit', color: '#0ea5e9', label: 'CPU:', value: () => `${availableCPU} vCPU` }, + { icon: 'mdi-memory', color: '#10B981', label: 'RAM:', value: () => `${availableRAM} GB` }, + { icon: 'mdi-harddisk', color: '#38bdf8', label: 'Storage:', value: () => `${availableStorage} GB` } + ]; +}); const { fetchAccountId } = useNodes(); const monitoringUrl = ref(''); diff --git a/frontend/kubecloud/src/components/ReservedNodeCard.vue b/frontend/kubecloud/src/components/ReservedNodeCard.vue new file mode 100644 index 00000000..a61fa4f0 --- /dev/null +++ b/frontend/kubecloud/src/components/ReservedNodeCard.vue @@ -0,0 +1,381 @@ + + + + + + diff --git a/frontend/kubecloud/src/components/dashboard/NodesCard.vue b/frontend/kubecloud/src/components/dashboard/NodesCard.vue index cda0de9c..a6de5958 100644 --- a/frontend/kubecloud/src/components/dashboard/NodesCard.vue +++ b/frontend/kubecloud/src/components/dashboard/NodesCard.vue @@ -97,17 +97,14 @@
- + - cpu: Math.round(node.total_resources?.cru ?? 0), ram: Math.round(node.total_resources?.mru ? node.total_resources.mru / (1024*1024*1024) : 0), storage: Math.round(node.total_resources?.sru ? node.total_resources.sru / (1024*1024*1024) : 0), + used_cpu: Math.round(node.used_resources?.cru ?? 0), + used_ram: Math.round(node.used_resources?.mru ? node.used_resources.mru / (1024*1024*1024) : 0), + used_storage: Math.round(node.used_resources?.sru ? node.used_resources.sru / (1024*1024*1024) : 0), country: node.country, gpu: !!node.num_gpu, id: node.id, @@ -378,11 +378,22 @@ const normalizedNodes = computed(() => margin-top: 2rem; } +.nodes-grid { + margin: -0.75rem; +} -.node-col { - flex: 1 1 250px; /* Allow growing and shrinking with a basis of 250px */ - min-width: 250px; /* Enforce the minimum width */ - max-width: 400px; +.nodes-grid .v-col { + display: flex; + padding: 0.75rem !important; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +@media (max-width: 959px) { + .nodes-grid .v-col { + flex: 0 0 100%; + max-width: 100%; + } } @@ -395,8 +406,5 @@ const normalizedNodes = computed(() => .header-actions { align-self: stretch; } - .nodes-grid { - gap: 1rem; - } } diff --git a/frontend/kubecloud/src/types/normalizedNode.ts b/frontend/kubecloud/src/types/normalizedNode.ts index 2192fe4a..a42b7704 100644 --- a/frontend/kubecloud/src/types/normalizedNode.ts +++ b/frontend/kubecloud/src/types/normalizedNode.ts @@ -1,36 +1,39 @@ export interface NormalizedNode { - nodeId: number; - cpu: number; // vCPU - ram: number; // GB - storage: number; // GB - available_ram?: number; // GB - available_storage?: number; // GB - price_usd: number | null; - discount_price: number | null; - gpu: boolean; - locationString: string; - country: string; - city: string; - status: string; - healthy: boolean; - rentable: boolean; - rented: boolean; - rentedByTwinId?: number; - dedicated: boolean; - certificationType: string; - extraFee: number; - farmId: number; - twinId: number; + nodeId: number + cpu: number // vCPU + ram: number // GB + storage: number // GB + used_cpu?: number // vCPU + used_ram?: number // GB + used_storage?: number // GB + available_ram?: number // GB + available_storage?: number // GB + price_usd: number | null + discount_price: number | null + gpu: boolean + locationString: string + country: string + city: string + status: string + healthy: boolean + rentable: boolean + rented: boolean + rentedByTwinId?: number + dedicated: boolean + certificationType: string + extraFee: number + farmId: number + twinId: number // Add any other UI fields needed } -type diskType = "ssd" | "hdd"; +type diskType = 'ssd' | 'hdd' export interface StoragePool { - name: string; - free: number; - type: diskType; + name: string + free: number + type: diskType } export interface NodeStoragePool { - pools: StoragePool[]; + pools: StoragePool[] } diff --git a/frontend/kubecloud/src/utils/nodeNormalizer.ts b/frontend/kubecloud/src/utils/nodeNormalizer.ts index a2e0ff54..9530bbc9 100644 --- a/frontend/kubecloud/src/utils/nodeNormalizer.ts +++ b/frontend/kubecloud/src/utils/nodeNormalizer.ts @@ -1,6 +1,6 @@ -import type { RawNode } from '../types/rawNode'; -import type { NormalizedNode } from '../types/normalizedNode'; -import type { RentedNode } from '../composables/useNodeManagement'; +import type { RawNode } from '../types/rawNode' +import type { NormalizedNode } from '../types/normalizedNode' +import type { RentedNode } from '../composables/useNodeManagement' export function normalizeNode(node: RawNode): NormalizedNode { return { @@ -8,10 +8,17 @@ export function normalizeNode(node: RawNode): NormalizedNode { farmId: node.farmId, twinId: node.twinId, cpu: node.total_resources?.cru ?? 0, - ram: node.total_resources?.mru ? Math.round(node.total_resources.mru / (1024 * 1024 * 1024)) : 0, + ram: node.total_resources?.mru + ? Math.round(node.total_resources.mru / (1024 * 1024 * 1024)) + : 0, + storage: node.total_resources?.sru + ? Math.round(node.total_resources.sru / (1024 * 1024 * 1024)) + : 0, + used_cpu: getUsedCPU(node), + used_ram: getUsedRAM(node), + used_storage: getUsedStorage(node), available_ram: getAvailableRAM(node), available_storage: getAvailableStorage(node), - storage: node.total_resources?.sru ? Math.round(node.total_resources.sru / (1024 * 1024 * 1024)) : 0, price_usd: typeof node.price_usd === 'number' ? node.price_usd : null, discount_price: typeof node.discount_price === 'number' ? node.discount_price : null, gpu: (node.num_gpu && node.num_gpu > 0) || (node.gpus && node.gpus.length > 0), @@ -26,62 +33,66 @@ export function normalizeNode(node: RawNode): NormalizedNode { dedicated: node.dedicated, extraFee: node.extraFee, certificationType: node.certificationType, - }; + } } -type ResourceKey = 'cru' | 'sru' | 'mru'; +type ResourceKey = 'cru' | 'sru' | 'mru' -function getResourceValue(node: RentedNode, resourceKey: ResourceKey, used: boolean = false): number { - if (!node) return 0; - const resources = used ? node.used_resources : node.total_resources; - const fallbackResources = used ? null : node.resources; +function getResourceValue( + node: RentedNode, + resourceKey: ResourceKey, + used: boolean = false, +): number { + if (!node) return 0 + const resources = used ? node.used_resources : node.total_resources + const fallbackResources = used ? null : node.resources if (resources && typeof resources[resourceKey] === 'number') { if (resourceKey === 'mru' || resourceKey === 'sru') { - return Math.round(resources[resourceKey] / (1024 * 1024 * 1024)); + return Math.round(resources[resourceKey] / (1024 * 1024 * 1024)) } - return resources[resourceKey]; + return resources[resourceKey] } if (fallbackResources) { if (resourceKey === 'mru' && typeof fallbackResources.memory === 'number') { - return fallbackResources.memory; + return fallbackResources.memory } if (resourceKey === 'sru' && typeof fallbackResources.storage === 'number') { - return fallbackResources.storage; + return fallbackResources.storage } if (resourceKey === 'cru' && typeof fallbackResources.cpu === 'number') { - return fallbackResources.cpu; + return fallbackResources.cpu } } - return 0; + return 0 } export function getTotalCPU(node: RentedNode): number { - return getResourceValue(node, 'cru', false); + return getResourceValue(node, 'cru', false) } export function getUsedCPU(node: RentedNode): number { - return getResourceValue(node, 'cru', true); + return getResourceValue(node, 'cru', true) } export function getAvailableCPU(node: RentedNode): number { - if (!node) return 0; - return getTotalCPU(node); + if (!node) return 0 + return getTotalCPU(node) } export function getTotalRAM(node: RentedNode): number { - return getResourceValue(node, 'mru', false); + return getResourceValue(node, 'mru', false) } export function getUsedRAM(node: RentedNode): number { - return getResourceValue(node, 'mru', true); + return getResourceValue(node, 'mru', true) } export function getAvailableRAM(node: RentedNode): number { - if (!node) return 0; - return Math.max(getTotalRAM(node) - getUsedRAM(node), 0); + if (!node) return 0 + return Math.max(getTotalRAM(node) - getUsedRAM(node), 0) } export function getTotalStorage(node: RentedNode): number { - return getResourceValue(node, 'sru', false); + return getResourceValue(node, 'sru', false) } export function getUsedStorage(node: RentedNode): number { - return getResourceValue(node, 'sru', true); + return getResourceValue(node, 'sru', true) } export function getAvailableStorage(node: RentedNode): number { - if (!node) return 0; - return Math.max(getTotalStorage(node) - getUsedStorage(node), 0); + if (!node) return 0 + return Math.max(getTotalStorage(node) - getUsedStorage(node), 0) }