Refactor cards in Nodes
This commit is contained in:
parent
3d13a05c0f
commit
b367d4086e
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user