Browse Source

Add PostgreSQL, ClickHouse, and HTTP/API metrics cards to dashboard

Frontend implementation:
- Added 3 new metric cards after device statistics:
  1. PostgreSQL: Connections (active/total), DB size, cache hit ratio, TPS
  2. ClickHouse: Active queries, DB size, QPS, memory usage
  3. HTTP/API: RPS, avg response time, error rate, active requests

UI features:
- Compact card layout with header + 4 metrics each
- Color-coded borders (purple accent)
- Monospace font for numeric values
- formatBytes() helper for human-readable sizes (B/KB/MB/GB/TB)
- latestMetrics ref for displaying most recent values
- Updates every 30 seconds with chart data

Design:
- Kept only time-series data in charts (no static values)
- Database sizes shown as text in cards (not graphed)
- Compact spacing to fit everything on one page without scroll
- Consistent styling with existing device cards

๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
root 4 weeks ago
parent
commit
364ba24049
1 changed files with 153 additions and 0 deletions
  1. 153 0
      frontend/src/views/superadmin/DashboardView.vue

+ 153 - 0
frontend/src/views/superadmin/DashboardView.vue

@@ -71,6 +71,87 @@
       </div>
     </div>
 
+    <!-- Database & API Metrics -->
+    <div class="db-api-grid">
+      <!-- PostgreSQL Card -->
+      <div class="db-card">
+        <div class="db-card-header">
+          <div class="db-icon">๐Ÿ˜</div>
+          <div class="db-title">PostgreSQL</div>
+        </div>
+        <div class="db-stats">
+          <div class="db-stat-item">
+            <span class="db-stat-label">Connections:</span>
+            <span class="db-stat-value">{{ latestMetrics?.pg_active_connections || 0 }} / {{ latestMetrics?.pg_total_connections || 100 }}</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">DB Size:</span>
+            <span class="db-stat-value">{{ formatBytes(latestMetrics?.pg_database_size_bytes || 0) }}</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">Cache Hit:</span>
+            <span class="db-stat-value">{{ (latestMetrics?.pg_cache_hit_ratio || 0).toFixed(1) }}%</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">TPS:</span>
+            <span class="db-stat-value">{{ latestMetrics?.pg_transactions_per_sec || 0 }}</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- ClickHouse Card -->
+      <div class="db-card">
+        <div class="db-card-header">
+          <div class="db-icon">๐Ÿ“Š</div>
+          <div class="db-title">ClickHouse</div>
+        </div>
+        <div class="db-stats">
+          <div class="db-stat-item">
+            <span class="db-stat-label">Active Queries:</span>
+            <span class="db-stat-value">{{ latestMetrics?.ch_active_queries || 0 }}</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">DB Size:</span>
+            <span class="db-stat-value">{{ formatBytes(latestMetrics?.ch_database_size_bytes || 0) }}</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">QPS:</span>
+            <span class="db-stat-value">{{ latestMetrics?.ch_queries_per_sec || 0 }}</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">Memory:</span>
+            <span class="db-stat-value">{{ formatBytes(latestMetrics?.ch_memory_usage_bytes || 0) }}</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- HTTP/API Card -->
+      <div class="db-card">
+        <div class="db-card-header">
+          <div class="db-icon">๐ŸŒ</div>
+          <div class="db-title">HTTP/API</div>
+        </div>
+        <div class="db-stats">
+          <div class="db-stat-item">
+            <span class="db-stat-label">RPS:</span>
+            <span class="db-stat-value">{{ latestMetrics?.http_requests_per_sec || 0 }}</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">Avg Response:</span>
+            <span class="db-stat-value">{{ (latestMetrics?.http_avg_response_time_ms || 0).toFixed(0) }} ms</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">Error Rate:</span>
+            <span class="db-stat-value">{{ (latestMetrics?.http_error_rate || 0).toFixed(1) }}%</span>
+          </div>
+          <div class="db-stat-item">
+            <span class="db-stat-label">Active:</span>
+            <span class="db-stat-value">{{ latestMetrics?.http_active_requests || 0 }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
     <!-- Host Metrics Charts -->
     <div class="metrics-section">
       <div class="charts-grid">
@@ -152,6 +233,7 @@ ChartJS.register(
 const activeAlerts = ref([])
 const deviceStats = ref({ total: 0, online: 0, offline: 0, error: 0 })
 const hostMetrics = ref([])
+const latestMetrics = ref(null)  // Latest metrics for database/API cards
 
 // Chart data
 const cpuChartData = ref(null)
@@ -263,6 +345,10 @@ async function loadHostMetrics() {
       params: { limit: 30 }
     })
     hostMetrics.value = data.metrics  // Already in chronological order
+    // Save latest for database/API cards
+    if (data.metrics.length > 0) {
+      latestMetrics.value = data.metrics[data.metrics.length - 1]
+    }
     updateCharts()
   } catch (error) {
     console.error('Failed to load host metrics:', error)
@@ -434,6 +520,14 @@ function formatTime(timestamp) {
   return date.toLocaleString()
 }
 
+function formatBytes(bytes) {
+  if (bytes === 0) return '0 B'
+  const k = 1024
+  const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
 async function refreshData() {
   await Promise.all([
     loadAlerts(),
@@ -636,6 +730,65 @@ onUnmounted(() => {
   font-weight: 500;
 }
 
+/* Database & API Cards */
+.db-api-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.db-card {
+  background: white;
+  border-radius: 8px;
+  padding: 14px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  border-left: 3px solid #667eea;
+}
+
+.db-card-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #e2e8f0;
+}
+
+.db-icon {
+  font-size: 20px;
+}
+
+.db-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1a202c;
+}
+
+.db-stats {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.db-stat-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-size: 12px;
+}
+
+.db-stat-label {
+  color: #718096;
+  font-weight: 500;
+}
+
+.db-stat-value {
+  color: #1a202c;
+  font-weight: 600;
+  font-family: 'Courier New', monospace;
+}
+
 /* Metrics Section */
 .metrics-section {
   background: white;