Refactor nodes page, update health chart

This commit is contained in:
Joshua Boniface 2025-02-28 20:20:10 -05:00
parent 11e1e8eec4
commit 9e14502bc2
2 changed files with 95 additions and 357 deletions

View File

@ -522,19 +522,21 @@ const healthChartOptions = {
}, },
annotation: { annotation: {
annotations: { annotations: {
warning: { criticalLine: {
type: 'line',
yMin: 90,
yMax: 90,
borderColor: 'rgba(255, 193, 7, 0.2)',
borderWidth: 1,
},
danger: {
type: 'line', type: 'line',
yMin: 50, yMin: 50,
yMax: 50, yMax: 50,
borderColor: 'rgba(220, 53, 69, 0.2)', borderColor: 'rgba(220, 53, 69, 0.5)',
borderWidth: 1, borderWidth: 1,
borderDash: [5, 5]
},
warningLine: {
type: 'line',
yMin: 90,
yMax: 90,
borderColor: 'rgba(255, 193, 7, 0.5)',
borderWidth: 1,
borderDash: [5, 5]
} }
} }
} }

View File

@ -170,96 +170,38 @@
<div class="section-content"> <div class="section-content">
<div class="graphs-row"> <div class="graphs-row">
<!-- Health Chart --> <!-- Health Chart -->
<div class="metric-card"> <MetricsChart
<div class="card-header"> title="Node Health"
<h6 class="card-title mb-0 metric-label">Node Health</h6> :value="selectedNodeData.health || 0"
</div> :chart-data="nodeHealthChartData"
<div class="card-body"> chart-type="health"
<div class="metric-percentage">
<h4 class="metric-value">
{{ selectedNodeData.health || 0 }}%
</h4>
</div>
<div class="chart-wrapper">
<Line
:data="healthChartData"
:options="healthChartOptions"
class="chart-health"
/> />
</div>
</div>
</div>
<!-- CPU Utilization Chart --> <!-- CPU Utilization Chart -->
<div class="metric-card"> <MetricsChart
<div class="card-header"> title="CPU Utilization"
<h6 class="card-title mb-0"> :value="calculateCpuUtilization(selectedNodeData)"
<span class="metric-label">CPU Utilization</span> :chart-data="nodeCpuChartData"
</h6> chart-type="cpu"
</div>
<div class="card-body">
<div class="metric-percentage">
<h4 class="metric-value">
{{ calculateCpuUtilization(selectedNodeData) }}%
</h4>
</div>
<div class="chart-wrapper">
<Line
:data="cpuChartData"
:options="chartOptions"
class="chart-cpu"
/> />
</div>
</div>
</div>
<!-- Memory Utilization Chart --> <!-- Memory Utilization Chart -->
<div class="metric-card"> <MetricsChart
<div class="card-header"> title="Memory Utilization"
<h6 class="card-title mb-0"> :value="calculateMemoryUtilization(selectedNodeData)"
<span class="metric-label">Memory Utilization</span> :chart-data="nodeMemoryChartData"
</h6> chart-type="memory"
</div>
<div class="card-body">
<div class="metric-percentage">
<h4 class="metric-value">
{{ calculateMemoryUtilization(selectedNodeData) }}%
</h4>
</div>
<div class="chart-wrapper">
<Line
:data="memoryChartData"
:options="chartOptions"
class="chart-memory"
/> />
</div>
</div>
</div>
<!-- Allocated Memory Chart --> <!-- Allocated Memory Chart -->
<div class="metric-card"> <MetricsChart
<div class="card-header"> title="Allocated Memory"
<h6 class="card-title mb-0"> :value="calculateAllocatedMemory(selectedNodeData)"
<span class="metric-label">Allocated Memory</span> :chart-data="nodeAllocatedMemoryChartData"
</h6> chart-type="storage"
</div>
<div class="card-body">
<div class="metric-percentage">
<h4 class="metric-value">
{{ calculateAllocatedMemory(selectedNodeData) }}%
</h4>
</div>
<div class="chart-wrapper">
<Line
:data="allocatedMemoryChartData"
:options="chartOptions"
class="chart-storage"
/> />
</div> </div>
</div> </div>
</div>
</div>
</div>
<div class="toggle-column expanded"> <div class="toggle-column expanded">
<button class="section-toggle" @click="toggleSection('graphs')"> <button class="section-toggle" @click="toggleSection('graphs')">
<i class="fas fa-chevron-up"></i> <i class="fas fa-chevron-up"></i>
@ -367,32 +309,31 @@
<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 { Line } from 'vue-chartjs'; import MetricsChart from '../components/MetricsCharts.vue';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler
} from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';
// Register Chart.js components // Implement formatBytes function directly
ChartJS.register( function formatBytes(bytes, decimals = 2) {
CategoryScale, if (bytes === undefined || bytes === null) return 'N/A';
LinearScale, if (bytes === 0) return '0 B';
PointElement,
LineElement, const k = 1024;
Title, const dm = decimals < 0 ? 0 : decimals;
Tooltip, const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
Legend,
Filler, const i = Math.floor(Math.log(bytes) / Math.log(k));
annotationPlugin
); 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`;
}
const props = defineProps({ const props = defineProps({
nodeData: { nodeData: {
@ -488,14 +429,6 @@ const scrollTabs = (direction) => {
setTimeout(checkScrollButtons, 300); setTimeout(checkScrollButtons, 300);
}; };
// Format memory values from MB to GB
const formatMemory = (memoryMB) => {
if (memoryMB === undefined || memoryMB === null) return 'N/A';
const memoryGB = memoryMB / 1024;
return `${memoryGB.toFixed(2)} GB`;
};
// Calculate CPU utilization (load / cpu_count * 100) // Calculate CPU utilization (load / cpu_count * 100)
const calculateCpuUtilization = (nodeData) => { const calculateCpuUtilization = (nodeData) => {
if (!nodeData || !nodeData.load || !nodeData.cpu_count) return 0; if (!nodeData || !nodeData.load || !nodeData.cpu_count) return 0;
@ -520,22 +453,9 @@ const calculateAllocatedMemory = (nodeData) => {
return Math.round(utilization); return Math.round(utilization);
}; };
// Chart data // Prepare chart data for MetricsChart component
const healthChartData = computed(() => { const nodeHealthChartData = computed(() => {
if (!selectedNodeData.value) { if (!selectedNodeData.value) return { labels: [], data: [] };
return {
labels: ['Health'],
datasets: [{
label: 'Node Health',
data: [0],
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
};
}
// 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];
@ -543,48 +463,19 @@ const healthChartData = computed(() => {
if (nodeMetrics && nodeMetrics.health && nodeMetrics.health.data.length > 0) { if (nodeMetrics && nodeMetrics.health && nodeMetrics.health.data.length > 0) {
return { return {
labels: nodeMetrics.health.labels, labels: nodeMetrics.health.labels,
datasets: [{ data: nodeMetrics.health.data
label: 'Node Health',
data: nodeMetrics.health.data,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
}; };
} }
// Fallback to current value only // Fallback to current value only
return { return {
labels: ['Health'], labels: ['Health'],
datasets: [{ data: [selectedNodeData.value.health || 0]
label: 'Node Health',
data: [selectedNodeData.value.health || 0],
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
}; };
}); });
const cpuChartData = computed(() => { const nodeCpuChartData = computed(() => {
if (!selectedNodeData.value) { if (!selectedNodeData.value) return { labels: [], data: [] };
return {
labels: ['CPU'],
datasets: [{
label: 'CPU Utilization',
data: [0],
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
};
}
// 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];
@ -592,48 +483,19 @@ const cpuChartData = computed(() => {
if (nodeMetrics && nodeMetrics.cpu && nodeMetrics.cpu.data.length > 0) { if (nodeMetrics && nodeMetrics.cpu && nodeMetrics.cpu.data.length > 0) {
return { return {
labels: nodeMetrics.cpu.labels, labels: nodeMetrics.cpu.labels,
datasets: [{ data: nodeMetrics.cpu.data
label: 'CPU Utilization',
data: nodeMetrics.cpu.data,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
}; };
} }
// Fallback to current value only // Fallback to current value only
return { return {
labels: ['CPU'], labels: ['CPU'],
datasets: [{ data: [calculateCpuUtilization(selectedNodeData.value)]
label: 'CPU Utilization',
data: [calculateCpuUtilization(selectedNodeData.value)],
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
}; };
}); });
const memoryChartData = computed(() => { const nodeMemoryChartData = computed(() => {
if (!selectedNodeData.value) { if (!selectedNodeData.value) return { labels: [], data: [] };
return {
labels: ['Memory'],
datasets: [{
label: 'Memory Utilization',
data: [0],
backgroundColor: 'rgba(153, 102, 255, 0.2)',
borderColor: 'rgba(153, 102, 255, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
};
}
// 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];
@ -641,48 +503,19 @@ const memoryChartData = computed(() => {
if (nodeMetrics && nodeMetrics.memory && nodeMetrics.memory.data.length > 0) { if (nodeMetrics && nodeMetrics.memory && nodeMetrics.memory.data.length > 0) {
return { return {
labels: nodeMetrics.memory.labels, labels: nodeMetrics.memory.labels,
datasets: [{ data: nodeMetrics.memory.data
label: 'Memory Utilization',
data: nodeMetrics.memory.data,
backgroundColor: 'rgba(153, 102, 255, 0.2)',
borderColor: 'rgba(153, 102, 255, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
}; };
} }
// Fallback to current value only // Fallback to current value only
return { return {
labels: ['Memory'], labels: ['Memory'],
datasets: [{ data: [calculateMemoryUtilization(selectedNodeData.value)]
label: 'Memory Utilization',
data: [calculateMemoryUtilization(selectedNodeData.value)],
backgroundColor: 'rgba(153, 102, 255, 0.2)',
borderColor: 'rgba(153, 102, 255, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
}; };
}); });
const allocatedMemoryChartData = computed(() => { const nodeAllocatedMemoryChartData = computed(() => {
if (!selectedNodeData.value) { if (!selectedNodeData.value) return { labels: [], data: [] };
return {
labels: ['Allocated'],
datasets: [{
label: 'Allocated Memory',
data: [0],
backgroundColor: 'rgba(255, 159, 64, 0.2)',
borderColor: 'rgba(255, 159, 64, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
};
}
// 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];
@ -690,82 +523,17 @@ const allocatedMemoryChartData = computed(() => {
if (nodeMetrics && nodeMetrics.allocated && nodeMetrics.allocated.data.length > 0) { if (nodeMetrics && nodeMetrics.allocated && nodeMetrics.allocated.data.length > 0) {
return { return {
labels: nodeMetrics.allocated.labels, labels: nodeMetrics.allocated.labels,
datasets: [{ data: nodeMetrics.allocated.data
label: 'Allocated Memory',
data: nodeMetrics.allocated.data,
backgroundColor: 'rgba(255, 159, 64, 0.2)',
borderColor: 'rgba(255, 159, 64, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
}; };
} }
// Fallback to current value only // Fallback to current value only
return { return {
labels: ['Allocated'], labels: ['Allocated'],
datasets: [{ data: [calculateAllocatedMemory(selectedNodeData.value)]
label: 'Allocated Memory',
data: [calculateAllocatedMemory(selectedNodeData.value)],
backgroundColor: 'rgba(255, 159, 64, 0.2)',
borderColor: 'rgba(255, 159, 64, 1)',
borderWidth: 2,
fill: true,
pointRadius: 0
}]
}; };
}); });
// Chart options
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
grid: {
display: false
},
ticks: {
display: false
}
},
x: {
grid: {
display: false
},
ticks: {
display: false
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
}
},
elements: {
line: {
tension: 0.4,
borderWidth: 2
},
point: {
radius: 0,
hitRadius: 10
}
}
};
const healthChartOptions = {
...chartOptions,
// Additional options specific to health chart if needed
};
// Initialize the component // Initialize the component
onMounted(() => { onMounted(() => {
// Select the first node by default if available // Select the first node by default if available
@ -1062,16 +830,16 @@ onUnmounted(() => {
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.8);
padding: 0.25rem 0.5rem; padding: 0.25rem 0.75rem;
border-radius: 0.5rem; border-radius: 0.5rem;
box-shadow: 0 0 10px rgba(0,0,0,0.05); box-shadow: 0 0 10px rgba(0,0,0,0.1);
z-index: 2; z-index: 2;
} }
.metric-percentage .metric-value { .metric-percentage .metric-value {
font-size: 2.5rem; font-size: 2.5rem;
font-weight: normal; font-weight: 500;
color: #333; color: #333;
line-height: 1; line-height: 1;
margin: 0; margin: 0;
@ -1081,10 +849,13 @@ onUnmounted(() => {
.chart-wrapper { .chart-wrapper {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 160px;
min-width: 0; min-height: 160px;
min-height: 200px;
overflow: hidden; overflow: hidden;
border: none;
border-radius: 0;
background-color: white;
margin: 0;
} }
/* Additional styles from ClusterOverview.vue */ /* Additional styles from ClusterOverview.vue */
@ -1128,57 +899,22 @@ onUnmounted(() => {
/* Chart colors */ /* Chart colors */
.chart-cpu { .chart-cpu {
background: rgba(75, 192, 192, 0.2); background: white;
border-color: rgba(75, 192, 192, 1); border: none;
} }
.chart-memory { .chart-memory {
background: rgba(153, 102, 255, 0.2); background: white;
border-color: rgba(153, 102, 255, 1); border: none;
} }
.chart-storage { .chart-storage, .chart-allocated {
background: rgba(255, 159, 64, 0.2); background: white;
border-color: rgba(255, 159, 64, 1); border: none;
} }
.chart-health { .chart-health {
background: rgba(255, 99, 132, 0.2); background: white;
border-color: rgba(255, 99, 132, 1); border: none;
}
/* Ensure consistent height for charts */
.chart-wrapper {
height: 200px;
}
/* Ensure consistent card heights */
.metric-card {
height: 100%;
min-height: 100px;
}
/* Ensure consistent spacing */
.section-content {
margin-bottom: 0.5rem;
}
/* Ensure consistent font sizes */
.metric-value {
font-size: 1.25rem;
}
.metric-percentage .metric-value {
font-size: 2.5rem;
}
/* Ensure consistent card body padding */
.metric-card .card-body {
padding: 1rem 0.5rem;
}
.chart-cpu, .chart-memory, .chart-storage, .chart-health {
border-width: 2px;
border-style: solid;
} }
</style> </style>