Refactor cards in Nodes

This commit is contained in:
Joshua Boniface 2025-02-28 21:41:17 -05:00
parent 3d13a05c0f
commit b367d4086e

View File

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