Improve and unify graphs
This commit is contained in:
		| @@ -25,14 +25,11 @@ | ||||
|         {{ connectionStatusMessage }} | ||||
|       </div> | ||||
|       <div class="content-grid"> | ||||
|         <div class="overview-row"> | ||||
|           <ClusterOverview  | ||||
|             :clusterData="clusterData" | ||||
|             :metricsHistory="metricsHistory.health" | ||||
|           /> | ||||
|           <NodeStatus :nodeData="nodeData" /> | ||||
|         </div> | ||||
|         <MetricsCharts :metricsData="metricsHistory" class="metrics-section" /> | ||||
|         <ClusterOverview  | ||||
|           :clusterData="clusterData" | ||||
|           :metricsData="metricsHistory" | ||||
|         /> | ||||
|         <NodeStatus :nodeData="nodeData" /> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| @@ -246,23 +243,6 @@ onUnmounted(() => { | ||||
|   gap: 0.5rem; | ||||
| } | ||||
|  | ||||
| .overview-row { | ||||
|   display: grid; | ||||
|   gap: 0.5rem; | ||||
|   grid-template-columns: 2fr 3fr; | ||||
| } | ||||
|  | ||||
| .overview-row > * { | ||||
|   height: 100%; | ||||
|   min-height: 400px; | ||||
| } | ||||
|  | ||||
| @media (max-width: 1200px) { | ||||
|   .overview-row { | ||||
|     grid-template-columns: 1fr; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .metric-card { | ||||
|   /* Remove transition and hover effect */ | ||||
| } | ||||
|   | ||||
| @@ -5,50 +5,96 @@ | ||||
|     </div> | ||||
|     <div class="card-body"> | ||||
|       <div class="overview-container"> | ||||
|         <div class="health-card"> | ||||
|           <div class="card-body"> | ||||
|             <h6 class="card-subtitle mb-2 text-muted">Cluster Health</h6> | ||||
|             <div class="health-content-wrapper"> | ||||
|               <div class="health-graph"> | ||||
|                 <div class="health-percentage"> | ||||
|                   <h4 :class="['health-title', getHealthClass(clusterData.cluster_health?.health)]"> | ||||
|                     {{ clusterData.cluster_health?.health || 0 }}% | ||||
|                   </h4> | ||||
|         <div class="graphs-row"> | ||||
|           <div class="health-card"> | ||||
|             <div class="card-header"> | ||||
|               <h6 class="card-title mb-0 metric-label">Cluster Health</h6> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|               <div class="health-content-wrapper"> | ||||
|                 <div class="health-graph"> | ||||
|                   <div class="health-percentage"> | ||||
|                     <h4 :class="['health-title', getHealthClass(clusterData.cluster_health?.health)]"> | ||||
|                       {{ clusterData.cluster_health?.health || 0 }}% | ||||
|                     </h4> | ||||
|                   </div> | ||||
|                   <Line | ||||
|                     :data="healthChartData" | ||||
|                     :options="healthChartOptions" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <Line | ||||
|                   :data="healthChartData" | ||||
|                   :options="healthChartOptions" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div class="health-messages"> | ||||
|                 <div class="messages-grid"> | ||||
|                   <template v-if="clusterData.cluster_health?.messages?.length"> | ||||
|                     <div  | ||||
|                       v-for="(msg, idx) in clusterData.cluster_health.messages"  | ||||
|                       :key="idx" | ||||
|                       :class="['health-message', getDeltaClass(msg.health_delta)]" | ||||
|                       :title="msg.text || 'No details available'" | ||||
|                     > | ||||
|                       <i class="fas fa-circle-exclamation me-1"></i> | ||||
|                       <span class="message-text"> | ||||
|                         {{ msg.id || 'Unknown Issue' }} | ||||
|                         <span  | ||||
|                           v-if="msg.health_delta"  | ||||
|                           class="health-delta" | ||||
|                         > | ||||
|                           ({{ msg.health_delta }}%) | ||||
|                 <div class="health-messages"> | ||||
|                   <div class="messages-grid"> | ||||
|                     <template v-if="clusterData.cluster_health?.messages?.length"> | ||||
|                       <div  | ||||
|                         v-for="(msg, idx) in clusterData.cluster_health.messages"  | ||||
|                         :key="idx" | ||||
|                         :class="['health-message', getDeltaClass(msg.health_delta)]" | ||||
|                         :title="msg.text || 'No details available'" | ||||
|                       > | ||||
|                         <i class="fas fa-circle-exclamation me-1"></i> | ||||
|                         <span class="message-text"> | ||||
|                           {{ msg.id || 'Unknown Issue' }} | ||||
|                           <span  | ||||
|                             v-if="msg.health_delta"  | ||||
|                             class="health-delta" | ||||
|                           > | ||||
|                             ({{ msg.health_delta }}%) | ||||
|                           </span> | ||||
|                         </span> | ||||
|                       </span> | ||||
|                       </div> | ||||
|                     </template> | ||||
|                     <div v-else class="health-message healthy"> | ||||
|                       <i class="fas fa-check-circle me-1"></i> | ||||
|                       <span>No issues detected</span> | ||||
|                     </div> | ||||
|                   </template> | ||||
|                   <div v-else class="health-message healthy"> | ||||
|                     <i class="fas fa-check-circle me-1"></i> | ||||
|                     <span>No issues detected</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <!-- CPU Chart --> | ||||
|           <div class="metric-card"> | ||||
|             <div class="card-header"> | ||||
|               <h6 class="card-title mb-0"> | ||||
|                 <span class="metric-label">CPU Utilization</span> | ||||
|                 <span class="metric-value"> | ||||
|                   {{ metricsData.cpu.data[metricsData.cpu.data.length - 1] || 0 }}% | ||||
|                 </span> | ||||
|               </h6> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|               <Line :data="cpuChartData" :options="chartOptions" /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <!-- Memory Chart --> | ||||
|           <div class="metric-card"> | ||||
|             <div class="card-header"> | ||||
|               <h6 class="card-title mb-0"> | ||||
|                 <span class="metric-label">Memory Utilization</span> | ||||
|                 <span class="metric-value"> | ||||
|                   {{ metricsData.memory.data[metricsData.memory.data.length - 1] || 0 }}% | ||||
|                 </span> | ||||
|               </h6> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|               <Line :data="memoryChartData" :options="chartOptions" /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <!-- Storage Chart --> | ||||
|           <div class="metric-card"> | ||||
|             <div class="card-header"> | ||||
|               <h6 class="card-title mb-0"> | ||||
|                 <span class="metric-label">Storage Utilization</span> | ||||
|                 <span class="metric-value"> | ||||
|                   {{ metricsData.storage?.data[metricsData.storage?.data.length - 1] || 0 }}% | ||||
|                 </span> | ||||
|               </h6> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|               <Line :data="storageChartData" :options="chartOptions" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="metrics-row"> | ||||
|           <!-- Card 2: PVC Version --> | ||||
| @@ -145,10 +191,10 @@ const props = defineProps({ | ||||
|     required: true, | ||||
|     default: () => ({}) | ||||
|   }, | ||||
|   metricsHistory: { | ||||
|   metricsData: { | ||||
|     type: Object, | ||||
|     required: true, | ||||
|     default: () => ({ labels: [], data: [] }) | ||||
|     default: () => ({}) | ||||
|   } | ||||
| }); | ||||
|  | ||||
| @@ -173,9 +219,9 @@ const getDeltaClass = (delta) => { | ||||
|  | ||||
| const healthChartData = computed(() => { | ||||
|   return { | ||||
|     labels: props.metricsHistory.labels, | ||||
|     labels: props.metricsData.health.labels, | ||||
|     datasets: [{ | ||||
|       data: props.metricsHistory.data, | ||||
|       data: props.metricsData.health.data, | ||||
|       fill: true, | ||||
|       segment: { | ||||
|         borderColor: (ctx) => { | ||||
| @@ -263,6 +309,83 @@ const healthChartOptions = { | ||||
|     intersect: false | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const cpuChartData = computed(() => ({ | ||||
|   labels: props.metricsData.cpu.labels, | ||||
|   datasets: [{ | ||||
|     label: 'CPU', | ||||
|     data: props.metricsData.cpu.data, | ||||
|     borderColor: 'rgb(75, 192, 192)', | ||||
|     fill: false | ||||
|   }] | ||||
| })); | ||||
|  | ||||
| const memoryChartData = computed(() => ({ | ||||
|   labels: props.metricsData.memory.labels, | ||||
|   datasets: [{ | ||||
|     label: 'Memory', | ||||
|     data: props.metricsData.memory.data, | ||||
|     borderColor: 'rgb(153, 102, 255)', | ||||
|     fill: false | ||||
|   }] | ||||
| })); | ||||
|  | ||||
| const storageChartData = computed(() => ({ | ||||
|   labels: props.metricsData.storage?.labels || [], | ||||
|   datasets: [{ | ||||
|     label: 'Storage', | ||||
|     data: props.metricsData.storage?.data || [], | ||||
|     borderColor: 'rgb(255, 159, 64)', | ||||
|     fill: false | ||||
|   }] | ||||
| })); | ||||
|  | ||||
| const chartOptions = { | ||||
|   responsive: true, | ||||
|   maintainAspectRatio: false, | ||||
|   clip: false, | ||||
|   plugins: { | ||||
|     legend: { | ||||
|       display: false | ||||
|     }, | ||||
|     tooltip: { | ||||
|       callbacks: { | ||||
|         label: (context) => `${context.parsed.y}%` | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   scales: { | ||||
|     x: { | ||||
|       display: false, | ||||
|       grid: { | ||||
|         display: false | ||||
|       } | ||||
|     }, | ||||
|     y: { | ||||
|       display: true, | ||||
|       grid: { | ||||
|         display: true | ||||
|       }, | ||||
|       min: 0, | ||||
|       max: 100, | ||||
|       ticks: { | ||||
|         stepSize: 20 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   elements: { | ||||
|     point: { | ||||
|       radius: 0, | ||||
|       hoverRadius: 4 | ||||
|     } | ||||
|   }, | ||||
|   animation: false, | ||||
|   interaction: { | ||||
|     enabled: true, | ||||
|     mode: 'nearest', | ||||
|     intersect: false | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| @@ -270,7 +393,18 @@ const healthChartOptions = { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 0.5rem; | ||||
|   height: 100%; | ||||
| } | ||||
|  | ||||
| .graphs-row { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(4, 1fr); | ||||
|   gap: 0.5rem; | ||||
| } | ||||
|  | ||||
| .metrics-row { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(4, 1fr); | ||||
|   gap: 0.5rem; | ||||
| } | ||||
|  | ||||
| .health-card { | ||||
| @@ -284,7 +418,6 @@ const healthChartOptions = { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 0.5rem; | ||||
|   height: calc(100% - 2rem); /* Account for header */ | ||||
|   width: 100%; | ||||
|   position: relative; | ||||
| } | ||||
| @@ -292,7 +425,7 @@ const healthChartOptions = { | ||||
| .health-graph { | ||||
|   position: relative; | ||||
|   flex-grow: 1; | ||||
|   min-height: 0; /* Allow shrinking */ | ||||
|   min-height: 0; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| } | ||||
| @@ -314,6 +447,7 @@ const healthChartOptions = { | ||||
|   margin: 0; | ||||
|   text-shadow: 1px 1px 2px rgba(0,0,0,0.1); | ||||
|   line-height: 1; | ||||
|   color: #333; | ||||
| } | ||||
|  | ||||
| .health-messages { | ||||
| @@ -323,8 +457,9 @@ const healthChartOptions = { | ||||
|   position: relative; | ||||
|   bottom: 0; | ||||
|   min-height: 2.5rem; | ||||
|   max-height: 30%; /* Reduce maximum height */ | ||||
|   max-height: 30%; | ||||
|   overflow-y: auto; | ||||
|   margin-top: auto;  /* Push to bottom of container */ | ||||
| } | ||||
|  | ||||
| .messages-grid { | ||||
| @@ -409,48 +544,6 @@ const healthChartOptions = { | ||||
|   background: rgba(0, 0, 0, 0.3); | ||||
| } | ||||
|  | ||||
| .metrics-row { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(4, 1fr); | ||||
|   gap: 0.5rem; | ||||
| } | ||||
|  | ||||
| .metric-card { | ||||
|   background: white; | ||||
|   border: 1px solid rgba(0,0,0,0.125); | ||||
|   border-radius: 0.25rem; | ||||
| } | ||||
|  | ||||
| .storage-metrics { | ||||
|   min-height: 3.5rem; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| @media (max-width: 1400px) { | ||||
|   .metrics-row { | ||||
|     grid-template-columns: repeat(2, 1fr); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media (max-width: 992px) { | ||||
|   .metrics-row { | ||||
|     grid-template-columns: 1fr; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .card-body { | ||||
|   padding: 0.5rem; | ||||
| } | ||||
|  | ||||
| .card-subtitle { | ||||
|   margin-bottom: 0.5rem !important; | ||||
| } | ||||
|  | ||||
| .metric-card .card-body { | ||||
|   padding: 0.5rem; | ||||
| } | ||||
|  | ||||
| .health-delta { | ||||
|   margin-left: 0.5rem; | ||||
|   font-size: 0.8em; | ||||
| @@ -470,4 +563,59 @@ const healthChartOptions = { | ||||
|   background: rgba(220, 53, 69, 0.1); | ||||
|   color: #721c24;  /* Darker red */ | ||||
| } | ||||
|  | ||||
| .metric-card { | ||||
|   background: white; | ||||
|   border: 1px solid rgba(0,0,0,0.125); | ||||
|   border-radius: 0.25rem; | ||||
|   height: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
|  | ||||
| .metric-card .card-body { | ||||
|   flex: 1; | ||||
|   padding: 0.5rem; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
|  | ||||
| .metric-card .card-body > * { | ||||
|   flex: 1; | ||||
| } | ||||
|  | ||||
| .current-value { | ||||
|   font-size: 1.1rem; | ||||
|   font-weight: bold; | ||||
|   color: #333; | ||||
| } | ||||
|  | ||||
| @media (max-width: 1400px) { | ||||
|   .graphs-row { | ||||
|     grid-template-columns: 1fr 1fr; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media (max-width: 768px) { | ||||
|   .graphs-row { | ||||
|     grid-template-columns: 1fr; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .card-header h6 { | ||||
|   font-size: 0.95rem; | ||||
|   font-weight: 600; | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| .metric-label { | ||||
|   color: #666; | ||||
| } | ||||
|  | ||||
| .metric-value { | ||||
|   font-weight: bold; | ||||
|   color: #333; | ||||
| } | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user