botui/ui/suite/monitoring/metrics.html

896 lines
32 KiB
HTML
Raw Normal View History

2025-12-06 11:09:12 -03:00
<div class="metrics-container">
<!-- Metrics Header -->
<div class="metrics-header">
<div class="header-info">
<h2></h2>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
Metrics Dashboard
</h2>
<span class="last-sync"
hx-get="/api/monitoring/metrics/last-sync"
hx-trigger="load, every 30s"
hx-swap="innerHTML">Last sync: --</span>
</div>
<div class="header-actions">
<select id="metrics-time-range" onchange="updateMetricsRange(this.value)">
<option value="15m">Last 15 minutes</option>
<option value="1h" selected>Last 1 hour</option>
<option value="6h">Last 6 hours</option>
<option value="24h">Last 24 hours</option>
<option value="7d">Last 7 days</option>
</select>
<button class="action-btn secondary" onclick="refreshMetrics()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"></polyline>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
</svg>
Refresh
</button>
<a href="/metrics" target="_blank" class="action-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
Prometheus Export
</a>
</div>
</div>
<!-- Key Metrics Overview -->
<div class="key-metrics"
hx-get="/api/analytics/dashboard"
hx-trigger="load, every 30s"
hx-swap="innerHTML">
<div class="metric-card">
<div class="metric-icon requests">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
</svg>
</div>
<div class="metric-info">
<span class="metric-value">--</span>
<span class="metric-label">Requests/sec</span>
</div>
<div class="metric-trend up">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
<span>--%</span>
</div>
</div>
<div class="metric-card">
<div class="metric-icon latency">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
</div>
<div class="metric-info">
<span class="metric-value">-- ms</span>
<span class="metric-label">Avg Latency</span>
</div>
<div class="metric-trend down">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
<span>--%</span>
</div>
</div>
<div class="metric-card">
<div class="metric-icon errors">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>
</div>
<div class="metric-info">
<span class="metric-value">--%</span>
<span class="metric-label">Error Rate</span>
</div>
<div class="metric-trend neutral">
<span>--</span>
</div>
</div>
<div class="metric-card">
<div class="metric-icon users">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
</div>
<div class="metric-info">
<span class="metric-value">--</span>
<span class="metric-label">Active Users</span>
</div>
<div class="metric-trend up">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
<span>--%</span>
</div>
</div>
<div class="metric-card">
<div class="metric-icon conversations">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
</div>
<div class="metric-info">
<span class="metric-value">--</span>
<span class="metric-label">Conversations</span>
</div>
<div class="metric-trend up">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
<span>--%</span>
</div>
</div>
<div class="metric-card">
<div class="metric-icon uptime">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</div>
<div class="metric-info">
<span class="metric-value">--%</span>
<span class="metric-label">Uptime</span>
</div>
<div class="metric-trend neutral">
<span>--</span>
</div>
</div>
</div>
<!-- Charts Grid -->
<div class="charts-grid">
<!-- Request Rate Chart -->
<div class="chart-panel">
<div class="chart-header">
<h3>Request Rate</h3>
<div class="chart-controls">
<select onchange="updateChartType('requests', this.value)">
<option value="line">Line</option>
<option value="area">Area</option>
<option value="bar">Bar</option>
</select>
</div>
</div>
<div class="chart-body" id="requests-chart"
hx-get="/api/analytics/metric?name=requests"
hx-trigger="load, every 30s"
hx-swap="innerHTML">
<svg viewBox="0 0 600 200" class="chart-svg">
<defs>
<linearGradient id="requests-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:var(--primary);stop-opacity:0.3"/>
<stop offset="100%" style="stop-color:var(--primary);stop-opacity:0"/>
</linearGradient>
</defs>
<g class="chart-grid">
<line x1="50" y1="20" x2="580" y2="20" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="70" x2="580" y2="70" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="120" x2="580" y2="120" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="170" x2="580" y2="170" stroke="var(--border)" stroke-dasharray="4"/>
</g>
<g class="chart-axis-y">
<text x="45" y="25" fill="var(--text-secondary)" font-size="10" text-anchor="end">1000</text>
<text x="45" y="75" fill="var(--text-secondary)" font-size="10" text-anchor="end">750</text>
<text x="45" y="125" fill="var(--text-secondary)" font-size="10" text-anchor="end">500</text>
<text x="45" y="175" fill="var(--text-secondary)" font-size="10" text-anchor="end">250</text>
</g>
<path class="chart-area" d="M50,170 Q100,160 150,140 T250,100 T350,120 T450,90 T550,110 L550,170 Z" fill="url(#requests-gradient)"/>
<path class="chart-line" d="M50,170 Q100,160 150,140 T250,100 T350,120 T450,90 T550,110" fill="none" stroke="var(--primary)" stroke-width="2"/>
</svg>
</div>
</div>
<!-- Latency Chart -->
<div class="chart-panel">
<div class="chart-header">
<h3>Response Latency</h3>
<div class="chart-legend">
<span class="legend-item"><span class="legend-dot p50"></span>P50</span>
<span class="legend-item"><span class="legend-dot p95"></span>P95</span>
<span class="legend-item"><span class="legend-dot p99"></span>P99</span>
</div>
</div>
<div class="chart-body" id="latency-chart"
hx-get="/api/analytics/metric?name=latency"
hx-trigger="load, every 30s"
hx-swap="innerHTML">
<svg viewBox="0 0 600 200" class="chart-svg">
<defs>
<linearGradient id="latency-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:var(--success);stop-opacity:0.2"/>
<stop offset="100%" style="stop-color:var(--success);stop-opacity:0"/>
</linearGradient>
</defs>
<g class="chart-grid">
<line x1="50" y1="20" x2="580" y2="20" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="70" x2="580" y2="70" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="120" x2="580" y2="120" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="170" x2="580" y2="170" stroke="var(--border)" stroke-dasharray="4"/>
</g>
<g class="chart-axis-y">
<text x="45" y="25" fill="var(--text-secondary)" font-size="10" text-anchor="end">500ms</text>
<text x="45" y="75" fill="var(--text-secondary)" font-size="10" text-anchor="end">375ms</text>
<text x="45" y="125" fill="var(--text-secondary)" font-size="10" text-anchor="end">250ms</text>
<text x="45" y="175" fill="var(--text-secondary)" font-size="10" text-anchor="end">125ms</text>
</g>
<path class="chart-line p99" d="M50,80 Q150,75 250,85 T450,70 T550,80" fill="none" stroke="var(--error)" stroke-width="1.5" stroke-dasharray="4"/>
<path class="chart-line p95" d="M50,110 Q150,100 250,115 T450,95 T550,105" fill="none" stroke="var(--warning)" stroke-width="1.5"/>
<path class="chart-area" d="M50,170 Q150,160 250,155 T450,150 T550,160 L550,170 Z" fill="url(#latency-gradient)"/>
<path class="chart-line p50" d="M50,170 Q150,160 250,155 T450,150 T550,160" fill="none" stroke="var(--success)" stroke-width="2"/>
</svg>
</div>
</div>
<!-- Error Rate Chart -->
<div class="chart-panel">
<div class="chart-header">
<h3>Error Rate</h3>
<div class="chart-legend">
<span class="legend-item"><span class="legend-dot error-4xx"></span>4xx</span>
<span class="legend-item"><span class="legend-dot error-5xx"></span>5xx</span>
</div>
</div>
<div class="chart-body" id="errors-chart"
hx-get="/api/analytics/metric?name=errors"
hx-trigger="load, every 30s"
hx-swap="innerHTML">
<svg viewBox="0 0 600 200" class="chart-svg">
<g class="chart-grid">
<line x1="50" y1="20" x2="580" y2="20" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="70" x2="580" y2="70" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="120" x2="580" y2="120" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="170" x2="580" y2="170" stroke="var(--border)" stroke-dasharray="4"/>
</g>
<g class="chart-axis-y">
<text x="45" y="25" fill="var(--text-secondary)" font-size="10" text-anchor="end">10%</text>
<text x="45" y="75" fill="var(--text-secondary)" font-size="10" text-anchor="end">7.5%</text>
<text x="45" y="125" fill="var(--text-secondary)" font-size="10" text-anchor="end">5%</text>
<text x="45" y="175" fill="var(--text-secondary)" font-size="10" text-anchor="end">2.5%</text>
</g>
<g class="chart-bars">
<rect x="70" y="160" width="20" height="10" fill="var(--warning)" rx="2"/>
<rect x="70" y="165" width="20" height="5" fill="var(--error)" rx="2"/>
<rect x="120" y="155" width="20" height="15" fill="var(--warning)" rx="2"/>
<rect x="120" y="162" width="20" height="8" fill="var(--error)" rx="2"/>
<rect x="170" y="150" width="20" height="20" fill="var(--warning)" rx="2"/>
<rect x="170" y="160" width="20" height="10" fill="var(--error)" rx="2"/>
<rect x="220" y="158" width="20" height="12" fill="var(--warning)" rx="2"/>
<rect x="220" y="165" width="20" height="5" fill="var(--error)" rx="2"/>
<rect x="270" y="162" width="20" height="8" fill="var(--warning)" rx="2"/>
<rect x="270" y="167" width="20" height="3" fill="var(--error)" rx="2"/>
</g>
</svg>
</div>
</div>
<!-- Throughput Chart -->
<div class="chart-panel">
<div class="chart-header">
<h3>Throughput</h3>
<div class="chart-legend">
<span class="legend-item"><span class="legend-dot incoming"></span>In</span>
<span class="legend-item"><span class="legend-dot outgoing"></span>Out</span>
</div>
</div>
<div class="chart-body" id="throughput-chart"
hx-get="/api/analytics/metric?name=throughput"
hx-trigger="load, every 30s"
hx-swap="innerHTML">
<svg viewBox="0 0 600 200" class="chart-svg">
<defs>
<linearGradient id="throughput-in-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#8b5cf6;stop-opacity:0.3"/>
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:0"/>
</linearGradient>
<linearGradient id="throughput-out-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#06b6d4;stop-opacity:0.3"/>
<stop offset="100%" style="stop-color:#06b6d4;stop-opacity:0"/>
</linearGradient>
</defs>
<g class="chart-grid">
<line x1="50" y1="20" x2="580" y2="20" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="70" x2="580" y2="70" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="120" x2="580" y2="120" stroke="var(--border)" stroke-dasharray="4"/>
<line x1="50" y1="170" x2="580" y2="170" stroke="var(--border)" stroke-dasharray="4"/>
</g>
<g class="chart-axis-y">
<text x="45" y="25" fill="var(--text-secondary)" font-size="10" text-anchor="end">100MB</text>
<text x="45" y="75" fill="var(--text-secondary)" font-size="10" text-anchor="end">75MB</text>
<text x="45" y="125" fill="var(--text-secondary)" font-size="10" text-anchor="end">50MB</text>
<text x="45" y="175" fill="var(--text-secondary)" font-size="10" text-anchor="end">25MB</text>
</g>
<path class="chart-area" d="M50,130 Q150,110 250,120 T450,100 T550,115 L550,170 Z" fill="url(#throughput-in-gradient)"/>
<path class="chart-line" d="M50,130 Q150,110 250,120 T450,100 T550,115" fill="none" stroke="#8b5cf6" stroke-width="2"/>
<path class="chart-area" d="M50,150 Q150,140 250,145 T450,130 T550,140 L550,170 Z" fill="url(#throughput-out-gradient)"/>
<path class="chart-line" d="M50,150 Q150,140 250,145 T450,130 T550,140" fill="none" stroke="#06b6d4" stroke-width="2"/>
</svg>
</div>
</div>
</div>
<!-- Individual Metrics Table -->
<div class="metrics-table-section">
<div class="section-header">
<h3></h3>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
</svg>
All Metrics
</h3>
<div class="section-actions">
<div class="search-box">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input type="text" id="metric-search" placeholder="Search metrics..." onkeyup="filterMetrics(this.value)">
</div>
<select id="metric-category" onchange="filterByCategory(this.value)">
<option value="all">All Categories</option>
<option value="http">HTTP</option>
<option value="system">System</option>
<option value="database">Database</option>
<option value="cache">Cache</option>
<option value="queue">Queue</option>
</select>
</div>
</div>
<div class="metrics-table-container">
<table class="metrics-table" id="metrics-table"
hx-get="/api/analytics/metrics/list"
hx-trigger="load"
hx-swap="innerHTML">
<thead>
<tr>
<th>Metric Name</th>
<th>Type</th>
<th>Value</th>
<th>Category</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="6" class="loading-cell">Loading metrics...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<style>
.metrics-container {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
/* Header */
.metrics-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 1rem;
}
.header-info h2 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1.25rem;
font-weight: 600;
color: var(--text);
margin-bottom: 0.25rem;
}
.header-info h2 svg {
color: var(--primary);
}
.last-sync {
font-size: 0.75rem;
color: var(--text-secondary);
}
.header-actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
.header-actions select {
background: var(--surface);
border: 1px solid var(--border);
color: var(--text);
padding: 0.5rem 0.75rem;
border-radius: 6px;
font-size: 0.8125rem;
cursor: pointer;
}
.header-actions select:focus {
outline: none;
border-color: var(--primary);
}
.action-btn {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 0.875rem;
background: var(--primary);
color: white;
border: none;
border-radius: 6px;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
text-decoration: none;
transition: all 0.2s ease;
}
.action-btn:hover {
background: var(--primary-hover);
}
.action-btn.secondary {
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
}
.action-btn.secondary:hover {
background: var(--surface-hover);
border-color: var(--primary);
}
/* Key Metrics */
.key-metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
}
.metric-card {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.25rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
transition: all 0.2s ease;
}
.metric-card:hover {
border-color: var(--primary);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.metric-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
flex-shrink: 0;
}
.metric-icon.requests { background: var(--primary-light); color: var(--primary); }
.metric-icon.latency { background: rgba(34, 197, 94, 0.1); color: var(--success); }
.metric-icon.errors { background: rgba(239, 68, 68, 0.1); color: var(--error); }
.metric-icon.users { background: rgba(139, 92, 246, 0.1); color: #8b5cf6; }
.metric-icon.conversations { background: rgba(6, 182, 212, 0.1); color: #06b6d4; }
.metric-icon.uptime { background: rgba(245, 158, 11, 0.1); color: var(--warning); }
.metric-info {
flex: 1;
min-width: 0;
}
.metric-value {
display: block;
font-size: 1.375rem;
font-weight: 700;
color: var(--text);
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
}
.metric-label {
font-size: 0.75rem;
color: var(--text-secondary);
}
.metric-trend {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
.metric-trend.up {
background: rgba(34, 197, 94, 0.1);
color: var(--success);
}
.metric-trend.down {
background: rgba(239, 68, 68, 0.1);
color: var(--error);
}
.metric-trend.neutral {
background: var(--bg);
color: var(--text-secondary);
}
/* Charts Grid */
.charts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 1rem;
}
.chart-panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
}
.chart-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border);
}
.chart-header h3 {
font-size: 0.9375rem;
font-weight: 600;
color: var(--text);
}
.chart-controls select {
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
cursor: pointer;
}
.chart-legend {
display: flex;
gap: 0.75rem;
}
.legend-item {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.75rem;
color: var(--text-secondary);
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 2px;
}
.legend-dot.p50 { background: var(--success); }
.legend-dot.p95 { background: var(--warning); }
.legend-dot.p99 { background: var(--error); }
.legend-dot.error-4xx { background: var(--warning); }
.legend-dot.error-5xx { background: var(--error); }
.legend-dot.incoming { background: #8b5cf6; }
.legend-dot.outgoing { background: #06b6d4; }
.chart-body {
padding: 1rem;
min-height: 220px;
}
.chart-svg {
width: 100%;
height: 180px;
}
/* Metrics Table */
.metrics-table-section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
gap: 1rem;
}
.section-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9375rem;
font-weight: 600;
color: var(--text);
}
.section-header h3 svg {
color: var(--primary);
}
.section-actions {
display: flex;
gap: 0.5rem;
}
.search-box {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.375rem 0.625rem;
}
.search-box svg {
color: var(--text-secondary);
flex-shrink: 0;
}
.search-box input {
background: transparent;
border: none;
color: var(--text);
font-size: 0.8125rem;
width: 160px;
outline: none;
}
.search-box input::placeholder {
color: var(--text-secondary);
}
.section-actions select {
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
padding: 0.375rem 0.625rem;
border-radius: 6px;
font-size: 0.8125rem;
cursor: pointer;
}
.metrics-table-container {
overflow-x: auto;
}
.metrics-table {
width: 100%;
border-collapse: collapse;
}
.metrics-table th,
.metrics-table td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border);
}
.metrics-table th {
font-size: 0.6875rem;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
background: var(--bg);
}
.metrics-table td {
font-size: 0.8125rem;
color: var(--text);
}
.metrics-table tr:last-child td {
border-bottom: none;
}
.metrics-table tr:hover td {
background: var(--surface-hover);
}
.metrics-table .metric-name {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
font-weight: 500;
}
.metrics-table .metric-type {
display: inline-block;
padding: 0.125rem 0.375rem;
background: var(--primary-light);
color: var(--primary);
border-radius: 4px;
font-size: 0.6875rem;
font-weight: 500;
text-transform: uppercase;
}
.metrics-table .metric-value-cell {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
font-weight: 600;
}
.metrics-table .metric-category {
color: var(--text-secondary);
}
.metrics-table .metric-desc {
color: var(--text-secondary);
font-size: 0.75rem;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.metrics-table .action-link {
color: var(--primary);
text-decoration: none;
font-size: 0.75rem;
}
.metrics-table .action-link:hover {
text-decoration: underline;
}
.loading-cell {
text-align: center !important;
color: var(--text-secondary) !important;
padding: 2rem !important;
}
/* Responsive */
@media (max-width: 1024px) {
.charts-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.metrics-header {
flex-direction: column;
align-items: stretch;
}
.header-actions {
flex-wrap: wrap;
}
.key-metrics {
grid-template-columns: repeat(2, 1fr);
}
.metric-card {
flex-direction: column;
text-align: center;
padding: 1rem;
}
.metric-trend {
margin-top: 0.5rem;
}
.section-header {
flex-direction: column;
align-items: stretch;
}
.section-actions {
flex-wrap: wrap;
}
.search-box {
flex: 1;
}
.search-box input {
width: 100%;
}
}
@media (max-width: 480px) {
.key-metrics {
grid-template-columns: 1fr;
}
}
</style>
<script>
function updateMetricsRange(range) {
localStorage.setItem('metrics-time-range', range);
refreshMetrics();
}
function refreshMetrics() {
// Refresh all metric components
htmx.trigger('.key-metrics', 'refresh');
htmx.trigger('#requests-chart', 'refresh');
htmx.trigger('#latency-chart', 'refresh');
htmx.trigger('#errors-chart', 'refresh');
htmx.trigger('#throughput-chart', 'refresh');
}
function updateChartType(chart, type) {
// This would update the chart visualization type
console.log(`Updating ${chart} chart to ${type} type`);
// Implementation depends on charting library
}
function filterMetrics(query) {
const rows = document.querySelectorAll('.metrics-table tbody tr');
const lowerQuery = query.toLowerCase();
rows.forEach(row => {
const text = row.textContent.toLowerCase();
row.style.display = text.includes(lowerQuery) ? '' : 'none';
});
}
function filterByCategory(category) {
const rows = document.querySelectorAll('.metrics-table tbody tr');
rows.forEach(row => {
if (category === 'all') {
row.style.display = '';
} else {
const rowCategory = row.dataset.category || '';
row.style.display = rowCategory === category ? '' : 'none';
}
});
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
// Restore time range preference
const savedRange = localStorage.getItem('metrics-time-range');
if (savedRange) {
document.getElementById('metrics-time-range').value = savedRange;
}
});
</script>