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 @@
+
+
+
+
+
+
+ ${{
+ monthlyPrice }}
+ /month
+
+
+
+ ${{ originalMonthlyPrice }}
+
+
+
+
+
+ ${{ hourlyPrice }}
+ /hr
+
+
+
+ {{ discountPercentage }}% OFF
+
+
+
+
+
+
+
+ Node {{ node.nodeId }}
+ GPU
+
+
+ mdi-map-marker
+ {{ node.country }}
+
+
+
+
+
+
+
+
+ {{ r.value }}
+
+
+
+
+
+
+ mdi-information
+ Some of the displayed resources are used by the system.
+
+
+
+
+ Check Node Health
+
+
+ {{ buttonLabel }}
+
+
+
+
+
+
+
+
+
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)
}