Refactor cards in Nodes
This commit is contained in:
		| @@ -44,7 +44,7 @@ | ||||
|         <!-- Toggle button for expanded section --> | ||||
|         <div v-show="sections.info" class="section-content-wrapper"> | ||||
|           <div class="section-content"> | ||||
|             <div class="metrics-row"> | ||||
|             <div class="info-row"> | ||||
|               <!-- Card 1: Daemon State --> | ||||
|               <ValueCard  | ||||
|                 title="Daemon State"  | ||||
| @@ -63,34 +63,22 @@ | ||||
|                 :value="selectedNodeData.domain_state || 'Unknown'"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Card 4: Domains Count --> | ||||
|               <ValueCard  | ||||
|                 title="Domains Count"  | ||||
|                 :value="selectedNodeData.domains_count || 0"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Card 5: PVC Version --> | ||||
|               <!-- Card 4: PVC Version --> | ||||
|               <ValueCard  | ||||
|                 title="PVC Version"  | ||||
|                 :value="selectedNodeData.pvc_version || 'Unknown'"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Card 6: Kernel Version --> | ||||
|               <!-- Card 5: Kernel Version --> | ||||
|               <ValueCard  | ||||
|                 title="Kernel Version"  | ||||
|                 :value="selectedNodeData.kernel || 'Unknown'"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Card 7: Host CPU Count --> | ||||
|               <!-- Card 6: VM Count --> | ||||
|               <ValueCard  | ||||
|                 title="Host CPU Count"  | ||||
|                 :value="selectedNodeData.cpu_count || 0"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Card 8: Guest CPU Count --> | ||||
|               <ValueCard  | ||||
|                 title="Guest CPU Count"  | ||||
|                 :value="selectedNodeData.vcpu?.allocated || 0"  | ||||
|                 title="VM Count"  | ||||
|                 :value="selectedNodeData.domains_count || 0"  | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
| @@ -158,6 +146,52 @@ | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <!-- CPU Resources Section --> | ||||
|       <div class="section-container" :class="{ 'collapsed': !sections.cpu }"> | ||||
|         <!-- Collapsed section indicator --> | ||||
|         <div v-if="!sections.cpu" class="section-content-wrapper"> | ||||
|           <div class="section-content"> | ||||
|             <div class="collapsed-section-header"> | ||||
|               <h6 class="card-title mb-0 metric-label">CPU Resources</h6> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="toggle-column"> | ||||
|             <button class="section-toggle" @click="toggleSection('cpu')"> | ||||
|               <i class="fas fa-chevron-down"></i> | ||||
|             </button> | ||||
|           </div> | ||||
|         </div> | ||||
|         <!-- Toggle button for expanded section --> | ||||
|         <div v-show="sections.cpu" class="section-content-wrapper"> | ||||
|           <div class="section-content"> | ||||
|             <div class="resources-row-cpu"> | ||||
|               <!-- Card 1: Host CPUs --> | ||||
|               <ValueCard  | ||||
|                 title="Host CPUs"  | ||||
|                 :value="selectedNodeData.cpu_count || 0"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Card 2: Guest CPUs --> | ||||
|               <ValueCard  | ||||
|                 title="Guest CPUs"  | ||||
|                 :value="selectedNodeData.vcpu?.allocated || 0"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Card 3: Load --> | ||||
|               <ValueCard  | ||||
|                 title="Load"  | ||||
|                 :value="selectedNodeData.load || 0"  | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="toggle-column expanded"> | ||||
|             <button class="section-toggle" @click="toggleSection('cpu')"> | ||||
|               <i class="fas fa-chevron-up"></i> | ||||
|             </button> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <!-- Memory Resources Section --> | ||||
|       <div class="section-container" :class="{ 'collapsed': !sections.resources }"> | ||||
|         <!-- Collapsed section indicator --> | ||||
| @@ -176,66 +210,36 @@ | ||||
|         <!-- Toggle button for expanded section --> | ||||
|         <div v-show="sections.resources" class="section-content-wrapper"> | ||||
|           <div class="section-content"> | ||||
|             <div class="resources-row"> | ||||
|             <div class="resources-row-memory"> | ||||
|               <!-- Total Memory --> | ||||
|               <div class="metric-card"> | ||||
|                 <div class="card-header"> | ||||
|                   <h6 class="card-title mb-0"> | ||||
|                     <span class="metric-label">Total Memory</span> | ||||
|                   </h6> | ||||
|                 </div> | ||||
|                 <div class="card-body"> | ||||
|                   <h4 class="metric-value">{{ formatMemory(selectedNodeData.memory?.total) }}</h4> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <ValueCard  | ||||
|                 title="Total Memory"  | ||||
|                 :value="formatMemory(selectedNodeData.memory?.total)"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Used Memory --> | ||||
|               <div class="metric-card"> | ||||
|                 <div class="card-header"> | ||||
|                   <h6 class="card-title mb-0"> | ||||
|                     <span class="metric-label">Used Memory</span> | ||||
|                   </h6> | ||||
|                 </div> | ||||
|                 <div class="card-body"> | ||||
|                   <h4 class="metric-value">{{ formatMemory(selectedNodeData.memory?.used) }}</h4> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <ValueCard  | ||||
|                 title="Used Memory"  | ||||
|                 :value="formatMemory(selectedNodeData.memory?.used)"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Free Memory --> | ||||
|               <div class="metric-card"> | ||||
|                 <div class="card-header"> | ||||
|                   <h6 class="card-title mb-0"> | ||||
|                     <span class="metric-label">Free Memory</span> | ||||
|                   </h6> | ||||
|                 </div> | ||||
|                 <div class="card-body"> | ||||
|                   <h4 class="metric-value">{{ formatMemory(selectedNodeData.memory?.free) }}</h4> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <ValueCard  | ||||
|                 title="Free Memory"  | ||||
|                 :value="formatMemory(selectedNodeData.memory?.free)"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Allocated Memory --> | ||||
|               <div class="metric-card"> | ||||
|                 <div class="card-header"> | ||||
|                   <h6 class="card-title mb-0"> | ||||
|                     <span class="metric-label">Allocated Memory</span> | ||||
|                   </h6> | ||||
|                 </div> | ||||
|                 <div class="card-body"> | ||||
|                   <h4 class="metric-value">{{ formatMemory(selectedNodeData.memory?.allocated) }}</h4> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <ValueCard  | ||||
|                 title="Allocated Memory"  | ||||
|                 :value="formatMemory(selectedNodeData.memory?.allocated)"  | ||||
|               /> | ||||
|  | ||||
|               <!-- Provisioned Memory --> | ||||
|               <div class="metric-card"> | ||||
|                 <div class="card-header"> | ||||
|                   <h6 class="card-title mb-0"> | ||||
|                     <span class="metric-label">Provisioned Memory</span> | ||||
|                   </h6> | ||||
|                 </div> | ||||
|                 <div class="card-body"> | ||||
|                   <h4 class="metric-value">{{ formatMemory(selectedNodeData.memory?.provisioned) }}</h4> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <ValueCard  | ||||
|                 title="Provisioned Memory"  | ||||
|                 :value="formatMemory(selectedNodeData.memory?.provisioned)"  | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="toggle-column expanded"> | ||||
| @@ -257,10 +261,10 @@ | ||||
| <script setup> | ||||
| import { ref, computed, onMounted, watch, nextTick, onUnmounted } from 'vue'; | ||||
| import PageTitle from '../components/PageTitle.vue'; | ||||
| import HealthChart from '../components/charts/HealthChart.vue'; | ||||
| import CPUChart from '../components/charts/CPUChart.vue'; | ||||
| import MemoryChart from '../components/charts/MemoryChart.vue'; | ||||
| import StorageChart from '../components/charts/StorageChart.vue'; | ||||
| import HealthChart from '../components/charts/HealthChart.vue'; | ||||
| import ValueCard from '../components/ValueCard.vue'; | ||||
|  | ||||
| // Implement formatBytes function directly | ||||
| @@ -277,15 +281,12 @@ function formatBytes(bytes, decimals = 2) { | ||||
|   return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; | ||||
| } | ||||
|  | ||||
| // Format memory values (similar to formatBytes but with GB as default unit) | ||||
| function formatMemory(bytes) { | ||||
|   if (bytes === undefined || bytes === null) return 'N/A'; | ||||
|   if (bytes === 0) return '0 GB'; | ||||
|  | ||||
|   // Convert to GB with 2 decimal places | ||||
|   const gbValue = (bytes / (1024 * 1024 * 1024)).toFixed(2); | ||||
|   return `${gbValue} GB`; | ||||
| } | ||||
| // Format memory values | ||||
| const formatMemory = (memoryMB) => { | ||||
|   if (!memoryMB) return '0 GB'; | ||||
|   // The values are already in MB, so we just need to convert to GB | ||||
|   return Math.round(memoryMB / 1024) + ' GB'; | ||||
| }; | ||||
|  | ||||
| const props = defineProps({ | ||||
|   nodeData: { | ||||
| @@ -309,11 +310,17 @@ const props = defineProps({ | ||||
| const sections = ref({ | ||||
|   info: true, | ||||
|   graphs: true, | ||||
|   cpu: true, | ||||
|   resources: true | ||||
| }); | ||||
|  | ||||
| // Toggle section visibility | ||||
| const toggleSection = (section) => { | ||||
|   sections.value[section] = !sections.value[section]; | ||||
| }; | ||||
|  | ||||
| // State for selected node and tab scrolling | ||||
| const selectedNode = ref(localStorage.getItem('selectedNodeTab') || ''); | ||||
| const selectedNode = ref(''); | ||||
| const tabsContainer = ref(null); | ||||
| const showLeftScroll = ref(false); | ||||
| const showRightScroll = ref(false); | ||||
| @@ -327,53 +334,61 @@ const selectedNodeData = computed(() => { | ||||
| // Select a node | ||||
| const selectNode = (nodeName) => { | ||||
|   selectedNode.value = nodeName; | ||||
|   // Save to localStorage | ||||
|   localStorage.setItem('selectedNodeTab', nodeName); | ||||
|  | ||||
|   // Scroll the selected tab into view | ||||
|   nextTick(() => { | ||||
|     const activeTab = document.querySelector('.node-tab.active'); | ||||
|     const activeTab = tabsContainer.value?.querySelector('.node-tab.active'); | ||||
|     if (activeTab) { | ||||
|       activeTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); | ||||
|       activeTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); | ||||
|     } | ||||
|  | ||||
|     // Check if scroll buttons should be shown | ||||
|     checkScrollButtons(); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // Toggle section visibility | ||||
| const toggleSection = (section) => { | ||||
|   sections.value[section] = !sections.value[section]; | ||||
| }; | ||||
|  | ||||
| // Scroll the tabs | ||||
| const scrollTabs = (direction) => { | ||||
|   if (!tabsContainer.value) return; | ||||
|    | ||||
|   const container = tabsContainer.value; | ||||
|   const scrollAmount = 200; // Adjust as needed | ||||
|    | ||||
|   if (direction === 'left') { | ||||
|     container.scrollLeft -= scrollAmount; | ||||
|   } else { | ||||
|     container.scrollLeft += scrollAmount; | ||||
|   } | ||||
|    | ||||
|   // Update scroll button visibility after scrolling | ||||
|   checkScrollButtons(); | ||||
| }; | ||||
|  | ||||
| // Check if scroll buttons should be shown | ||||
| const checkScrollButtons = () => { | ||||
|   if (!tabsContainer.value) return; | ||||
|  | ||||
|   const container = tabsContainer.value; | ||||
|   showLeftScroll.value = container.scrollLeft > 0; | ||||
|   showRightScroll.value = container.scrollLeft < (container.scrollWidth - container.clientWidth - 5); // 5px buffer | ||||
|   const { scrollLeft, scrollWidth, clientWidth } = tabsContainer.value; | ||||
|  | ||||
|   // Show left scroll button if not at the beginning | ||||
|   showLeftScroll.value = scrollLeft > 0; | ||||
|  | ||||
|   // Show right scroll button if not at the end | ||||
|   showRightScroll.value = scrollLeft + clientWidth < scrollWidth - 1; // -1 for rounding errors | ||||
| }; | ||||
|  | ||||
| // Calculate CPU utilization | ||||
| const calculateCpuUtilization = (node) => { | ||||
|   if (!node || !node.load || !node.cpu_count) return 0; | ||||
|   return Math.round((node.load / node.cpu_count) * 100); | ||||
| // Scroll tabs left or right | ||||
| const scrollTabs = (direction) => { | ||||
|   if (!tabsContainer.value) return; | ||||
|  | ||||
|   const scrollAmount = tabsContainer.value.clientWidth / 2; | ||||
|   const currentScroll = tabsContainer.value.scrollLeft; | ||||
|  | ||||
|   if (direction === 'left') { | ||||
|     tabsContainer.value.scrollTo({ | ||||
|       left: Math.max(0, currentScroll - scrollAmount), | ||||
|       behavior: 'smooth' | ||||
|     }); | ||||
|   } else { | ||||
|     tabsContainer.value.scrollTo({ | ||||
|       left: currentScroll + scrollAmount, | ||||
|       behavior: 'smooth' | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // Check scroll buttons after scrolling | ||||
|   setTimeout(checkScrollButtons, 300); | ||||
| }; | ||||
|  | ||||
| // Calculate CPU utilization (load / cpu_count * 100) | ||||
| const calculateCpuUtilization = (nodeData) => { | ||||
|   if (!nodeData || !nodeData.load || !nodeData.cpu_count) return 0; | ||||
|  | ||||
|   const utilization = (nodeData.load / nodeData.cpu_count) * 100; | ||||
|   return Math.round(utilization); | ||||
| }; | ||||
|  | ||||
| // Calculate memory utilization | ||||
| @@ -382,7 +397,7 @@ const calculateMemoryUtilization = (node) => { | ||||
|   const used = node.memory.used || 0; | ||||
|   const total = node.memory.total || 1; // Avoid division by zero | ||||
|   if (total === 0) return 0; | ||||
|   return Math.round((node.memory.used / node.memory.total) * 100); | ||||
|   return Math.round((used / total) * 100); | ||||
| }; | ||||
|  | ||||
| // Calculate allocated memory | ||||
| @@ -391,13 +406,13 @@ const calculateAllocatedMemory = (node) => { | ||||
|   const allocated = node.memory.allocated || 0; | ||||
|   const total = node.memory.total || 1; // Avoid division by zero | ||||
|   if (total === 0) return 0; | ||||
|   return Math.round((node.memory.allocated / node.memory.total) * 100); | ||||
|   return Math.round((allocated / total) * 100); | ||||
| }; | ||||
|  | ||||
| // Prepare chart data for the node | ||||
| const nodeHealthChartData = computed(() => { | ||||
|   // Get node metrics history if available | ||||
|   const nodeMetrics = props.metricsData.nodes?.[selectedNodeData.value?.name]; | ||||
|   const nodeMetrics = props.metricsData.nodes?.[selectedNodeData.value.name]; | ||||
|  | ||||
|   if (nodeMetrics && nodeMetrics.health && nodeMetrics.health.data.length > 0) { | ||||
|     return { | ||||
| @@ -409,13 +424,13 @@ const nodeHealthChartData = computed(() => { | ||||
|   // Fallback to current value only | ||||
|   return { | ||||
|     labels: ['Health'], | ||||
|     data: [selectedNodeData.value?.health || 0] | ||||
|     data: [selectedNodeData.value.health || 0] | ||||
|   }; | ||||
| }); | ||||
|  | ||||
| const nodeCpuChartData = computed(() => { | ||||
|   // Get node metrics history if available | ||||
|   const nodeMetrics = props.metricsData.nodes?.[selectedNodeData.value?.name]; | ||||
|   const nodeMetrics = props.metricsData.nodes?.[selectedNodeData.value.name]; | ||||
|  | ||||
|   if (nodeMetrics && nodeMetrics.cpu && nodeMetrics.cpu.data.length > 0) { | ||||
|     return { | ||||
| @@ -473,15 +488,8 @@ const nodeAllocatedMemoryChartData = computed(() => { | ||||
|  | ||||
| // Initialize the component | ||||
| onMounted(() => { | ||||
|   // Check if the saved node exists in the current node data | ||||
|   const savedNode = localStorage.getItem('selectedNodeTab'); | ||||
|   const nodeExists = props.nodeData.some(node => node.name === savedNode); | ||||
|    | ||||
|   if (savedNode && nodeExists) { | ||||
|     // Use the saved node | ||||
|     selectedNode.value = savedNode; | ||||
|   } else if (props.nodeData && props.nodeData.length > 0) { | ||||
|     // Default to the first node if saved node doesn't exist | ||||
|   // Select the first node by default if available | ||||
|   if (props.nodeData && props.nodeData.length > 0) { | ||||
|     selectNode(props.nodeData[0].name); | ||||
|   } | ||||
|  | ||||
| @@ -491,10 +499,9 @@ onMounted(() => { | ||||
| }); | ||||
|  | ||||
| // Watch for changes in the nodes data | ||||
| watch(() => props.nodeData, (newNodes) => { | ||||
| watch(() => props.nodeData, () => { | ||||
|   // If the currently selected node no longer exists, select the first available node | ||||
|   const nodeExists = newNodes.some(node => node.name === selectedNode.value); | ||||
|   if (newNodes && newNodes.length > 0 && !nodeExists) { | ||||
|   if (props.nodeData && props.nodeData.length > 0 && !props.nodeData.find(node => node.name === selectedNode.value)) { | ||||
|     selectNode(props.nodeData[0].name); | ||||
|   } | ||||
|  | ||||
| @@ -669,7 +676,7 @@ onUnmounted(() => { | ||||
|   font-weight: 600; | ||||
| } | ||||
|  | ||||
| .metrics-row, .graphs-row, .resources-row { | ||||
| .metrics-row, .graphs-row, .resources-row-cpu, .resources-row-memory, .info-row { | ||||
|   display: grid; | ||||
|   gap: 0.5rem; | ||||
|   width: 100%; | ||||
| @@ -681,11 +688,19 @@ onUnmounted(() => { | ||||
|     grid-template-columns: repeat(4, 1fr); | ||||
|   } | ||||
|    | ||||
|   .info-row { | ||||
|     grid-template-columns: repeat(3, 1fr); | ||||
|   } | ||||
|  | ||||
|   .graphs-row { | ||||
|     grid-template-columns: repeat(4, 1fr); | ||||
|   } | ||||
|  | ||||
|   .resources-row { | ||||
|   .resources-row-cpu { | ||||
|     grid-template-columns: repeat(3, 1fr); | ||||
|   } | ||||
|  | ||||
|   .resources-row-memory { | ||||
|     grid-template-columns: repeat(5, 1fr); | ||||
|   } | ||||
| } | ||||
| @@ -695,17 +710,29 @@ onUnmounted(() => { | ||||
|     grid-template-columns: repeat(2, 1fr); | ||||
|   } | ||||
|    | ||||
|   .info-row { | ||||
|     grid-template-columns: repeat(2, 1fr); | ||||
|   } | ||||
|  | ||||
|   .graphs-row { | ||||
|     grid-template-columns: repeat(2, 1fr); | ||||
|   } | ||||
|  | ||||
|   .resources-row { | ||||
|     grid-template-columns: repeat(5, 1fr); | ||||
|   .resources-row-cpu { | ||||
|     grid-template-columns: repeat(3, 1fr); | ||||
|   } | ||||
|  | ||||
|   .resources-row-memory { | ||||
|     grid-template-columns: repeat(3, 1fr); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media (max-width: 800px) { | ||||
|   .metrics-row, .graphs-row, .resources-row { | ||||
|   .metrics-row,  | ||||
|   .info-row, | ||||
|   .graphs-row,  | ||||
|   .resources-row-cpu, | ||||
|   .resources-row-memory { | ||||
|     grid-template-columns: 1fr; | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user