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: {
annotations: {
warning: {
type: 'line',
yMin: 90,
yMax: 90,
borderColor: 'rgba(255, 193, 7, 0.2)',
borderWidth: 1,
},
danger: {
criticalLine: {
type: 'line',
yMin: 50,
yMax: 50,
borderColor: 'rgba(220, 53, 69, 0.2)',
borderColor: 'rgba(220, 53, 69, 0.5)',
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="graphs-row">
<!-- Health Chart -->
<div class="metric-card">
<div class="card-header">
<h6 class="card-title mb-0 metric-label">Node Health</h6>
</div>
<div class="card-body">
<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"
<MetricsChart
title="Node Health"
:value="selectedNodeData.health || 0"
:chart-data="nodeHealthChartData"
chart-type="health"
/>
</div>
</div>
</div>
<!-- CPU Utilization Chart -->
<div class="metric-card">
<div class="card-header">
<h6 class="card-title mb-0">
<span class="metric-label">CPU Utilization</span>
</h6>
</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"
<MetricsChart
title="CPU Utilization"
:value="calculateCpuUtilization(selectedNodeData)"
:chart-data="nodeCpuChartData"
chart-type="cpu"
/>
</div>
</div>
</div>
<!-- Memory Utilization Chart -->
<div class="metric-card">
<div class="card-header">
<h6 class="card-title mb-0">
<span class="metric-label">Memory Utilization</span>
</h6>
</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"
<MetricsChart
title="Memory Utilization"
:value="calculateMemoryUtilization(selectedNodeData)"
:chart-data="nodeMemoryChartData"
chart-type="memory"
/>
</div>
</div>
</div>
<!-- Allocated Memory Chart -->
<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">
<div class="metric-percentage">
<h4 class="metric-value">
{{ calculateAllocatedMemory(selectedNodeData) }}%
</h4>
</div>
<div class="chart-wrapper">
<Line
:data="allocatedMemoryChartData"
:options="chartOptions"
class="chart-storage"
<MetricsChart
title="Allocated Memory"
:value="calculateAllocatedMemory(selectedNodeData)"
:chart-data="nodeAllocatedMemoryChartData"
chart-type="storage"
/>
</div>
</div>
</div>
</div>
</div>
<div class="toggle-column expanded">
<button class="section-toggle" @click="toggleSection('graphs')">
<i class="fas fa-chevron-up"></i>
@ -367,32 +309,31 @@
<script setup>
import { ref, computed, onMounted, watch, nextTick, onUnmounted } from 'vue';
import PageTitle from '../components/PageTitle.vue';
import { Line } from 'vue-chartjs';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler
} from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';
import MetricsChart from '../components/MetricsCharts.vue';
// Register Chart.js components
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler,
annotationPlugin
);
// Implement formatBytes function directly
function formatBytes(bytes, decimals = 2) {
if (bytes === undefined || bytes === null) return 'N/A';
if (bytes === 0) return '0 B';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
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({
nodeData: {
@ -488,14 +429,6 @@ const scrollTabs = (direction) => {
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)
const calculateCpuUtilization = (nodeData) => {
if (!nodeData || !nodeData.load || !nodeData.cpu_count) return 0;
@ -520,22 +453,9 @@ const calculateAllocatedMemory = (nodeData) => {
return Math.round(utilization);
};
// Chart data
const healthChartData = computed(() => {
if (!selectedNodeData.value) {
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
}]
};
}
// Prepare chart data for MetricsChart component
const nodeHealthChartData = computed(() => {
if (!selectedNodeData.value) return { labels: [], data: [] };
// Get node metrics history if available
const nodeMetrics = props.metricsData.nodes?.[selectedNodeData.value.name];
@ -543,48 +463,19 @@ const healthChartData = computed(() => {
if (nodeMetrics && nodeMetrics.health && nodeMetrics.health.data.length > 0) {
return {
labels: nodeMetrics.health.labels,
datasets: [{
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
}]
data: nodeMetrics.health.data
};
}
// Fallback to current value only
return {
labels: ['Health'],
datasets: [{
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
}]
data: [selectedNodeData.value.health || 0]
};
});
const cpuChartData = computed(() => {
if (!selectedNodeData.value) {
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
}]
};
}
const nodeCpuChartData = computed(() => {
if (!selectedNodeData.value) return { labels: [], data: [] };
// Get node metrics history if available
const nodeMetrics = props.metricsData.nodes?.[selectedNodeData.value.name];
@ -592,48 +483,19 @@ const cpuChartData = computed(() => {
if (nodeMetrics && nodeMetrics.cpu && nodeMetrics.cpu.data.length > 0) {
return {
labels: nodeMetrics.cpu.labels,
datasets: [{
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
}]
data: nodeMetrics.cpu.data
};
}
// Fallback to current value only
return {
labels: ['CPU'],
datasets: [{
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
}]
data: [calculateCpuUtilization(selectedNodeData.value)]
};
});
const memoryChartData = computed(() => {
if (!selectedNodeData.value) {
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
}]
};
}
const nodeMemoryChartData = computed(() => {
if (!selectedNodeData.value) return { labels: [], data: [] };
// Get node metrics history if available
const nodeMetrics = props.metricsData.nodes?.[selectedNodeData.value.name];
@ -641,48 +503,19 @@ const memoryChartData = computed(() => {
if (nodeMetrics && nodeMetrics.memory && nodeMetrics.memory.data.length > 0) {
return {
labels: nodeMetrics.memory.labels,
datasets: [{
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
}]
data: nodeMetrics.memory.data
};
}
// Fallback to current value only
return {
labels: ['Memory'],
datasets: [{
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
}]
data: [calculateMemoryUtilization(selectedNodeData.value)]
};
});
const allocatedMemoryChartData = computed(() => {
if (!selectedNodeData.value) {
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
}]
};
}
const nodeAllocatedMemoryChartData = computed(() => {
if (!selectedNodeData.value) return { labels: [], data: [] };
// Get node metrics history if available
const nodeMetrics = props.metricsData.nodes?.[selectedNodeData.value.name];
@ -690,82 +523,17 @@ const allocatedMemoryChartData = computed(() => {
if (nodeMetrics && nodeMetrics.allocated && nodeMetrics.allocated.data.length > 0) {
return {
labels: nodeMetrics.allocated.labels,
datasets: [{
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
}]
data: nodeMetrics.allocated.data
};
}
// Fallback to current value only
return {
labels: ['Allocated'],
datasets: [{
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
}]
data: [calculateAllocatedMemory(selectedNodeData.value)]
};
});
// 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
onMounted(() => {
// Select the first node by default if available
@ -1062,16 +830,16 @@ onUnmounted(() => {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.5);
padding: 0.25rem 0.5rem;
background: rgba(255, 255, 255, 0.8);
padding: 0.25rem 0.75rem;
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;
}
.metric-percentage .metric-value {
font-size: 2.5rem;
font-weight: normal;
font-weight: 500;
color: #333;
line-height: 1;
margin: 0;
@ -1081,10 +849,13 @@ onUnmounted(() => {
.chart-wrapper {
position: relative;
width: 100%;
height: 100%;
min-width: 0;
min-height: 200px;
height: 160px;
min-height: 160px;
overflow: hidden;
border: none;
border-radius: 0;
background-color: white;
margin: 0;
}
/* Additional styles from ClusterOverview.vue */
@ -1128,57 +899,22 @@ onUnmounted(() => {
/* Chart colors */
.chart-cpu {
background: rgba(75, 192, 192, 0.2);
border-color: rgba(75, 192, 192, 1);
background: white;
border: none;
}
.chart-memory {
background: rgba(153, 102, 255, 0.2);
border-color: rgba(153, 102, 255, 1);
background: white;
border: none;
}
.chart-storage {
background: rgba(255, 159, 64, 0.2);
border-color: rgba(255, 159, 64, 1);
.chart-storage, .chart-allocated {
background: white;
border: none;
}
.chart-health {
background: rgba(255, 99, 132, 0.2);
border-color: rgba(255, 99, 132, 1);
}
/* 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;
background: white;
border: none;
}
</style>