mirror of
https://github.com/Alexander-D-Karpov/about.git
synced 2026-03-16 22:06:08 +03:00
1113 lines
46 KiB
HTML
1113 lines
46 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
|
<title>{{ .Title }}</title>
|
|
<link href="/favicon.ico" rel="icon" type="image/x-icon">
|
|
<style>
|
|
:root {
|
|
--bg-primary: #0a0c10;
|
|
--bg-secondary: #12161c;
|
|
--bg-card: #161b22;
|
|
--bg-hover: #1c2128;
|
|
--text-primary: #e6edf3;
|
|
--text-secondary: #8b949e;
|
|
--text-muted: #6e7681;
|
|
--accent-blue: #58a6ff;
|
|
--accent-green: #3fb950;
|
|
--accent-red: #f85149;
|
|
--accent-orange: #d29922;
|
|
--accent-purple: #a371f7;
|
|
--accent-pink: #db61a2;
|
|
--accent-cyan: #39c5cf;
|
|
--border-color: rgba(240, 246, 252, 0.1);
|
|
--shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
--chart-grid: rgba(255, 255, 255, 0.06);
|
|
}
|
|
|
|
[data-theme="light"] {
|
|
--bg-primary: #f6f8fa;
|
|
--bg-secondary: #ffffff;
|
|
--bg-card: #ffffff;
|
|
--bg-hover: #f3f4f6;
|
|
--text-primary: #1f2328;
|
|
--text-secondary: #656d76;
|
|
--text-muted: #8b949e;
|
|
--accent-blue: #0969da;
|
|
--accent-green: #1a7f37;
|
|
--accent-red: #cf222e;
|
|
--accent-orange: #9a6700;
|
|
--accent-purple: #8250df;
|
|
--accent-pink: #bf3989;
|
|
--accent-cyan: #0598bc;
|
|
--border-color: rgba(31, 35, 40, 0.15);
|
|
--shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
|
|
--chart-grid: rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
|
line-height: 1.5;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1600px;
|
|
margin: 0 auto;
|
|
padding: 24px;
|
|
}
|
|
|
|
header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 32px;
|
|
padding-bottom: 16px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
flex-wrap: wrap;
|
|
gap: 16px;
|
|
}
|
|
|
|
.header-left h1 {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.header-left .subtitle {
|
|
font-size: 14px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.theme-toggle {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 8px;
|
|
padding: 8px 12px;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 14px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.theme-toggle:hover {
|
|
background: var(--bg-hover);
|
|
}
|
|
|
|
.theme-toggle svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
.meta-info {
|
|
font-size: 13px;
|
|
color: var(--text-muted);
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
}
|
|
|
|
.status-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 4px 10px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-badge.online {
|
|
background: rgba(63, 185, 80, 0.15);
|
|
color: var(--accent-green);
|
|
}
|
|
|
|
.status-badge .dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: currentColor;
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.summary-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.summary-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.summary-card:hover {
|
|
border-color: var(--accent-blue);
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.summary-card .label {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.summary-card .label svg {
|
|
width: 14px;
|
|
height: 14px;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.summary-card .value {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
}
|
|
|
|
.summary-card .change {
|
|
font-size: 12px;
|
|
margin-top: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.summary-card .change.positive {
|
|
color: var(--accent-green);
|
|
}
|
|
|
|
.summary-card .change.negative {
|
|
color: var(--accent-red);
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.section-title svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.charts-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
gap: 24px;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
@media (max-width: 860px) {
|
|
.charts-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.chart-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
}
|
|
|
|
.chart-card.wide {
|
|
grid-column: span 2;
|
|
}
|
|
|
|
@media (max-width: 860px) {
|
|
.chart-card.wide {
|
|
grid-column: span 1;
|
|
}
|
|
}
|
|
|
|
.chart-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.chart-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.chart-subtitle {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.chart-value {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
}
|
|
|
|
.chart-container {
|
|
height: 200px;
|
|
position: relative;
|
|
}
|
|
|
|
.chart-container.tall {
|
|
height: 300px;
|
|
}
|
|
|
|
.plugin-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
.plugin-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.plugin-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.plugin-name {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.plugin-name svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
.plugin-status {
|
|
font-size: 11px;
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.plugin-status.active {
|
|
background: rgba(63, 185, 80, 0.15);
|
|
color: var(--accent-green);
|
|
}
|
|
|
|
.plugin-metrics {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 12px;
|
|
}
|
|
|
|
.plugin-metric {
|
|
padding: 12px;
|
|
background: var(--bg-secondary);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.plugin-metric .label {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
margin-bottom: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.plugin-metric .label svg {
|
|
width: 12px;
|
|
height: 12px;
|
|
}
|
|
|
|
.plugin-metric .value {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
}
|
|
|
|
.loading-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: var(--bg-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
opacity: 1;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.loading-overlay.hidden {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 3px solid var(--border-color);
|
|
border-top-color: var(--accent-blue);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.color-blue {
|
|
color: var(--accent-blue);
|
|
}
|
|
|
|
.color-green {
|
|
color: var(--accent-green);
|
|
}
|
|
|
|
.color-red {
|
|
color: var(--accent-red);
|
|
}
|
|
|
|
.color-orange {
|
|
color: var(--accent-orange);
|
|
}
|
|
|
|
.color-purple {
|
|
color: var(--accent-purple);
|
|
}
|
|
|
|
.color-pink {
|
|
color: var(--accent-pink);
|
|
}
|
|
|
|
.color-cyan {
|
|
color: var(--accent-cyan);
|
|
}
|
|
|
|
.bg-blue {
|
|
background: rgba(88, 166, 255, 0.15);
|
|
}
|
|
|
|
.bg-green {
|
|
background: rgba(63, 185, 80, 0.15);
|
|
}
|
|
|
|
.bg-red {
|
|
background: rgba(248, 81, 73, 0.15);
|
|
}
|
|
|
|
.bg-orange {
|
|
background: rgba(210, 153, 34, 0.15);
|
|
}
|
|
|
|
.bg-purple {
|
|
background: rgba(163, 113, 247, 0.15);
|
|
}
|
|
|
|
.bg-pink {
|
|
background: rgba(219, 97, 162, 0.15);
|
|
}
|
|
|
|
.bg-cyan {
|
|
background: rgba(57, 197, 207, 0.15);
|
|
}
|
|
</style>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="loading-overlay" id="loading">
|
|
<div class="loading-spinner"></div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<header>
|
|
<div class="header-left">
|
|
<h1>System Statistics</h1>
|
|
</div>
|
|
<div class="header-right">
|
|
<span class="status-badge online">
|
|
<span class="dot"></span>
|
|
Live
|
|
</span>
|
|
<button class="theme-toggle" onclick="toggleTheme()">
|
|
<svg fill="currentColor" id="theme-icon" viewBox="0 0 24 24">
|
|
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
|
|
</svg>
|
|
<span id="theme-text">Dark</span>
|
|
</button>
|
|
<div class="meta-info">
|
|
Updated: <span id="last-update">--:--:--</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="summary-grid" id="summary-grid">
|
|
<div class="summary-card">
|
|
<div class="label">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
|
|
</svg>
|
|
Total Visits
|
|
</div>
|
|
<div class="value" id="val-total-visits">-</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM9 10H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2zm-8 4H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2z"/>
|
|
</svg>
|
|
Today Visits
|
|
</div>
|
|
<div class="value" id="val-today-visits">-</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
|
|
</svg>
|
|
Heart Rate
|
|
</div>
|
|
<div class="value" id="val-heart-rate">-</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M13.5 5.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zM9.8 8.9L7 23h2.1l1.8-8 2.1 2v6h2v-7.5l-2.1-2 .6-3C14.8 12 16.8 13 19 13v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1L6 8.3V13h2V9.6l1.8-.7"/>
|
|
</svg>
|
|
Steps Today
|
|
</div>
|
|
<div class="value" id="val-steps">-</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
|
</svg>
|
|
Services Online
|
|
</div>
|
|
<div class="value" id="val-services-online">-</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>
|
|
</svg>
|
|
GitHub Stars
|
|
</div>
|
|
<div class="value" id="val-github-stars">-</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/>
|
|
</svg>
|
|
GitHub Commits
|
|
</div>
|
|
<div class="value" id="val-github-commits">-</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-10 7H8v3H6v-3H3v-2h3V8h2v3h3v2zm4.5 2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4-3c-.83 0-1.5-.67-1.5-1.5S18.67 9 19.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/>
|
|
</svg>
|
|
Gaming Status
|
|
</div>
|
|
<div class="value" id="val-gaming-status">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="section-title">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/>
|
|
</svg>
|
|
Time Series Charts
|
|
</h2>
|
|
|
|
<div class="charts-grid">
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<div>
|
|
<div class="chart-title">Visitor Traffic</div>
|
|
<div class="chart-subtitle">Total visits over time</div>
|
|
</div>
|
|
<div class="chart-value color-blue" id="chart-val-visits">-</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart-visits"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<div>
|
|
<div class="chart-title">Heart Rate</div>
|
|
<div class="chart-subtitle">BPM monitoring</div>
|
|
</div>
|
|
<div class="chart-value color-red" id="chart-val-hr">-</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart-hr"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<div>
|
|
<div class="chart-title">Daily Steps</div>
|
|
<div class="chart-subtitle">Activity tracking</div>
|
|
</div>
|
|
<div class="chart-value color-orange" id="chart-val-steps">-</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart-steps"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<div>
|
|
<div class="chart-title">Services Status</div>
|
|
<div class="chart-subtitle">Online services count</div>
|
|
</div>
|
|
<div class="chart-value color-green" id="chart-val-services">-</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart-services"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<div>
|
|
<div class="chart-title">Coding Activity</div>
|
|
<div class="chart-subtitle">WakaTime hours (7 days)</div>
|
|
</div>
|
|
<div class="chart-value color-purple" id="chart-val-coding">-</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart-coding"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-card">
|
|
<div class="chart-header">
|
|
<div>
|
|
<div class="chart-title">Music Scrobbles</div>
|
|
<div class="chart-subtitle">Last.fm total plays</div>
|
|
</div>
|
|
<div class="chart-value color-pink" id="chart-val-scrobbles">-</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="chart-scrobbles"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-card wide">
|
|
<div class="chart-header">
|
|
<div>
|
|
<div class="chart-title">Service Response Times</div>
|
|
<div class="chart-subtitle">Average latency in milliseconds</div>
|
|
</div>
|
|
<div class="chart-value color-cyan" id="chart-val-latency">-</div>
|
|
</div>
|
|
<div class="chart-container tall">
|
|
<canvas id="chart-latency"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="section-title">
|
|
<svg fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>
|
|
</svg>
|
|
Plugin Metrics
|
|
</h2>
|
|
|
|
<div class="plugin-grid" id="plugin-grid"></div>
|
|
</div>
|
|
|
|
<script>
|
|
const ICONS = {
|
|
visitors: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>',
|
|
calendar: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM9 10H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2z"/></svg>',
|
|
heart: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>',
|
|
steps: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M13.5 5.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zM9.8 8.9L7 23h2.1l1.8-8 2.1 2v6h2v-7.5l-2.1-2 .6-3C14.8 12 16.8 13 19 13v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1L6 8.3V13h2V9.6l1.8-.7"/></svg>',
|
|
fire: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67z"/></svg>',
|
|
sleep: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 2c-1.05 0-2.05.16-3 .46 4.06 1.27 7 5.06 7 9.54 0 4.48-2.94 8.27-7 9.54.95.3 1.95.46 3 .46 5.52 0 10-4.48 10-10S14.52 2 9 2z"/></svg>',
|
|
services: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>',
|
|
offline: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>',
|
|
signal: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 3C6.95 3 3.15 4.85 0 7.23L12 22 24 7.25C20.85 4.87 17.05 3 12 3z"/></svg>',
|
|
speed: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44zm-9.79 6.84a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z"/></svg>',
|
|
folder: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>',
|
|
star: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>',
|
|
code: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>',
|
|
keyboard: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z"/></svg>',
|
|
gamepad: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-10 7H8v3H6v-3H3v-2h3V8h2v3h3v2zm4.5 2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4-3c-.83 0-1.5-.67-1.5-1.5S18.67 9 19.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></svg>',
|
|
playing: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>',
|
|
games: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15 7.5V2H9v5.5l3 3 3-3zM7.5 9H2v6h5.5l3-3-3-3zM9 16.5V22h6v-5.5l-3-3-3 3zM16.5 9l-3 3 3 3H22V9h-5.5z"/></svg>',
|
|
time: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>',
|
|
music: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>',
|
|
headphones: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 1c-4.97 0-9 4.03-9 9v7c0 1.66 1.34 3 3 3h3v-8H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-4v8h3c1.66 0 3-1.34 3-3v-7c0-4.97-4.03-9-9-9z"/></svg>',
|
|
link: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>',
|
|
web: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>',
|
|
computer: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/></svg>',
|
|
camera: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/></svg>',
|
|
photo: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>',
|
|
location: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>',
|
|
globe: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>',
|
|
city: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15 11V5l-3-3-3 3v2H3v14h18V11h-6zm-8 8H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm6 8h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm6 12h-2v-2h2v2zm0-4h-2v-2h2v2z"/></svg>',
|
|
trophy: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>',
|
|
bolt: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.34.05-.08.07-.12C8.48 10.94 10.42 7.54 13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15C12.96 17.55 11 21 11 21z"/></svg>',
|
|
note: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/></svg>',
|
|
};
|
|
|
|
const METRIC_NAMES = {
|
|
'about_visitors_total_visits': {label: 'Total Visits', icon: 'visitors', color: 'blue'},
|
|
'about_visitors_today_visits': {label: 'Today Visits', icon: 'calendar', color: 'blue'},
|
|
'about_health_heart_rate_current': {label: 'Heart Rate', icon: 'heart', color: 'red', unit: 'bpm'},
|
|
'about_health_steps_today': {label: 'Steps Today', icon: 'steps', color: 'orange'},
|
|
'about_health_calories_today': {label: 'Calories', icon: 'fire', color: 'orange', unit: 'kcal'},
|
|
'about_health_sleep_hours': {label: 'Sleep', icon: 'sleep', color: 'purple', unit: 'hrs'},
|
|
'about_services_online_count': {label: 'Services Online', icon: 'services', color: 'green'},
|
|
'about_services_offline_count': {label: 'Services Offline', icon: 'offline', color: 'red'},
|
|
'about_services_total_count': {label: 'Total Services', icon: 'signal', color: 'cyan'},
|
|
'about_services_avg_response_time_ms': {label: 'Avg Response', icon: 'speed', color: 'cyan', unit: 'ms'},
|
|
'about_code_github_repos': {label: 'GitHub Repos', icon: 'folder', color: 'purple'},
|
|
'about_code_github_stars': {label: 'GitHub Stars', icon: 'star', color: 'orange'},
|
|
'about_code_github_commits': {label: 'Total Commits', icon: 'code', color: 'green'},
|
|
'about_code_wakatime_hours_7d': {label: 'Coding (7d)', icon: 'keyboard', color: 'purple', unit: 'hrs'},
|
|
'about_steam_is_online': {label: 'Steam Online', icon: 'gamepad', color: 'blue'},
|
|
'about_steam_is_playing': {label: 'Playing Game', icon: 'playing', color: 'green'},
|
|
'about_steam_recent_games_count': {label: 'Recent Games', icon: 'games', color: 'purple'},
|
|
'about_steam_total_playtime_hours': {label: 'Total Playtime', icon: 'time', color: 'blue', unit: 'hrs'},
|
|
'about_lastfm_is_playing': {label: 'Music Playing', icon: 'music', color: 'pink'},
|
|
'about_lastfm_total_scrobbles': {label: 'Scrobbles', icon: 'headphones', color: 'pink'},
|
|
'about_webring_status_ok': {label: 'Webring Status', icon: 'link', color: 'cyan'},
|
|
'about_webring_sites_count': {label: 'Webring Sites', icon: 'web', color: 'cyan'},
|
|
'about_neofetch_machines_count': {label: 'Machines', icon: 'computer', color: 'green'},
|
|
'about_personal_info_items_count': {label: 'Personal Items', icon: 'note', color: 'blue'},
|
|
'about_photos_folders_total': {label: 'Photo Folders', icon: 'folder', color: 'orange'},
|
|
'about_photos_images_total': {label: 'Total Photos', icon: 'photo', color: 'orange'},
|
|
'about_places_total_places': {label: 'Places Visited', icon: 'location', color: 'red'},
|
|
'about_places_countries_count': {label: 'Countries', icon: 'globe', color: 'green'},
|
|
'about_places_cities_count': {label: 'Cities', icon: 'city', color: 'blue'},
|
|
'about_beatleader_rank': {label: 'BeatLeader Rank', icon: 'trophy', color: 'orange'},
|
|
'about_beatleader_pp': {label: 'Performance Points', icon: 'bolt', color: 'purple'},
|
|
'about_beatleader_play_count': {label: 'Plays', icon: 'gamepad', color: 'blue'},
|
|
};
|
|
|
|
const PLUGIN_CONFIG = {
|
|
'visitors': {name: 'Visitors', icon: 'visitors', metrics: ['total_visits', 'today_visits']},
|
|
'health': {
|
|
name: 'Health',
|
|
icon: 'heart',
|
|
metrics: ['heart_rate_current', 'steps_today', 'calories_today', 'sleep_hours']
|
|
},
|
|
'services': {
|
|
name: 'Services',
|
|
icon: 'signal',
|
|
metrics: ['online_count', 'offline_count', 'total_count', 'avg_response_time_ms']
|
|
},
|
|
'code': {
|
|
name: 'Code',
|
|
icon: 'code',
|
|
metrics: ['github_repos', 'github_stars', 'github_commits', 'wakatime_hours_7d']
|
|
},
|
|
'steam': {
|
|
name: 'Steam',
|
|
icon: 'gamepad',
|
|
metrics: ['is_online', 'is_playing', 'recent_games_count', 'total_playtime_hours']
|
|
},
|
|
'lastfm': {name: 'Last.fm', icon: 'music', metrics: ['is_playing', 'total_scrobbles']},
|
|
'webring': {name: 'Webring', icon: 'link', metrics: ['status_ok', 'sites_count']},
|
|
'neofetch': {name: 'Neofetch', icon: 'computer', metrics: ['machines_count']},
|
|
'photos': {name: 'Photos', icon: 'camera', metrics: ['folders_total', 'images_total']},
|
|
'places': {name: 'Places', icon: 'location', metrics: ['total_places', 'countries_count', 'cities_count']},
|
|
'beatleader': {name: 'BeatLeader', icon: 'trophy', metrics: ['rank', 'pp', 'play_count']},
|
|
};
|
|
|
|
function getIcon(name) {
|
|
return ICONS[name] || ICONS.note;
|
|
}
|
|
|
|
const charts = {};
|
|
let currentTheme = localStorage.getItem('stats-theme') || 'dark';
|
|
let allMetricsData = {};
|
|
|
|
function getChartColors() {
|
|
const isDark = currentTheme === 'dark';
|
|
return {
|
|
grid: isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.08)',
|
|
text: isDark ? '#8b949e' : '#656d76',
|
|
blue: '#58a6ff',
|
|
green: '#3fb950',
|
|
red: '#f85149',
|
|
orange: '#d29922',
|
|
purple: '#a371f7',
|
|
pink: '#db61a2',
|
|
cyan: '#39c5cf',
|
|
};
|
|
}
|
|
|
|
function createChartConfig(label, color, data = []) {
|
|
const colors = getChartColors();
|
|
return {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
label: label,
|
|
data: data,
|
|
borderColor: colors[color] || colors.blue,
|
|
backgroundColor: (colors[color] || colors.blue) + '20',
|
|
borderWidth: 2,
|
|
fill: true,
|
|
tension: 0.4,
|
|
pointRadius: 0,
|
|
pointHoverRadius: 5,
|
|
pointHoverBackgroundColor: colors[color] || colors.blue,
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
},
|
|
plugins: {
|
|
legend: {display: false},
|
|
tooltip: {
|
|
backgroundColor: currentTheme === 'dark' ? 'rgba(0,0,0,0.9)' : 'rgba(255,255,255,0.95)',
|
|
titleColor: currentTheme === 'dark' ? '#fff' : '#1f2328',
|
|
bodyColor: currentTheme === 'dark' ? '#e6edf3' : '#1f2328',
|
|
borderColor: currentTheme === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
|
|
borderWidth: 1,
|
|
padding: 12,
|
|
displayColors: false,
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
display: true,
|
|
grid: {display: false},
|
|
ticks: {
|
|
color: colors.text,
|
|
maxRotation: 0,
|
|
maxTicksLimit: 6,
|
|
}
|
|
},
|
|
y: {
|
|
display: true,
|
|
grid: {color: colors.grid},
|
|
ticks: {
|
|
color: colors.text,
|
|
maxTicksLimit: 5,
|
|
},
|
|
beginAtZero: true,
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function initCharts() {
|
|
charts.visits = new Chart(
|
|
document.getElementById('chart-visits').getContext('2d'),
|
|
createChartConfig('Visits', 'blue')
|
|
);
|
|
|
|
charts.hr = new Chart(
|
|
document.getElementById('chart-hr').getContext('2d'),
|
|
createChartConfig('Heart Rate', 'red')
|
|
);
|
|
|
|
charts.steps = new Chart(
|
|
document.getElementById('chart-steps').getContext('2d'),
|
|
createChartConfig('Steps', 'orange')
|
|
);
|
|
|
|
charts.services = new Chart(
|
|
document.getElementById('chart-services').getContext('2d'),
|
|
createChartConfig('Online', 'green')
|
|
);
|
|
|
|
charts.coding = new Chart(
|
|
document.getElementById('chart-coding').getContext('2d'),
|
|
createChartConfig('Hours', 'purple')
|
|
);
|
|
|
|
charts.scrobbles = new Chart(
|
|
document.getElementById('chart-scrobbles').getContext('2d'),
|
|
createChartConfig('Scrobbles', 'pink')
|
|
);
|
|
|
|
charts.latency = new Chart(
|
|
document.getElementById('chart-latency').getContext('2d'),
|
|
createChartConfig('Response Time (ms)', 'cyan')
|
|
);
|
|
}
|
|
|
|
function updateChart(chart, metricData, valueElementId) {
|
|
if (!metricData || !Array.isArray(metricData) || metricData.length === 0) return null;
|
|
|
|
const labels = metricData.map(pt => {
|
|
const date = new Date(pt[0] * 1000);
|
|
return date.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
|
|
});
|
|
const values = metricData.map(pt => parseFloat(pt[1]));
|
|
|
|
chart.data.labels = labels;
|
|
chart.data.datasets[0].data = values;
|
|
chart.update('none');
|
|
|
|
const latestValue = values[values.length - 1];
|
|
if (valueElementId) {
|
|
const el = document.getElementById(valueElementId);
|
|
if (el) el.textContent = formatValue(latestValue);
|
|
}
|
|
|
|
return latestValue;
|
|
}
|
|
|
|
function formatValue(value, unit = '') {
|
|
if (value === null || value === undefined || isNaN(value)) return '-';
|
|
|
|
if (value >= 1000000) {
|
|
return (value / 1000000).toFixed(1) + 'M' + unit;
|
|
} else if (value >= 1000) {
|
|
return (value / 1000).toFixed(1) + 'K' + unit;
|
|
} else if (Number.isInteger(value)) {
|
|
return value.toLocaleString() + unit;
|
|
} else {
|
|
return value.toFixed(1) + unit;
|
|
}
|
|
}
|
|
|
|
function getLatestValue(data, metricKey) {
|
|
const metricData = data[metricKey];
|
|
if (metricData && Array.isArray(metricData) && metricData.length > 0) {
|
|
return parseFloat(metricData[metricData.length - 1][1]);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function updateSummaryCards(data) {
|
|
const mappings = {
|
|
'val-total-visits': ['about_visitors_total_visits'],
|
|
'val-today-visits': ['about_visitors_today_visits'],
|
|
'val-heart-rate': ['about_health_heart_rate_current', ' bpm'],
|
|
'val-steps': ['about_health_steps_today'],
|
|
'val-services-online': ['about_services_online_count'],
|
|
'val-github-stars': ['about_code_github_stars'],
|
|
'val-github-commits': ['about_code_github_commits'],
|
|
};
|
|
|
|
for (const [elementId, [metricKey, unit]] of Object.entries(mappings)) {
|
|
const value = getLatestValue(data, metricKey);
|
|
if (value !== null) {
|
|
const el = document.getElementById(elementId);
|
|
if (el) {
|
|
el.textContent = formatValue(value) + (unit || '');
|
|
}
|
|
}
|
|
}
|
|
|
|
const gamingEl = document.getElementById('val-gaming-status');
|
|
if (gamingEl) {
|
|
const steamPlaying = getLatestValue(data, 'about_steam_is_playing');
|
|
const steamOnline = getLatestValue(data, 'about_steam_is_online');
|
|
|
|
if (steamPlaying === 1) {
|
|
gamingEl.textContent = 'Playing';
|
|
gamingEl.style.color = 'var(--accent-green)';
|
|
} else if (steamOnline === 1) {
|
|
gamingEl.textContent = 'Online';
|
|
gamingEl.style.color = 'var(--accent-blue)';
|
|
} else {
|
|
gamingEl.textContent = 'Offline';
|
|
gamingEl.style.color = 'var(--text-muted)';
|
|
}
|
|
}
|
|
}
|
|
|
|
function updatePluginGrid(data) {
|
|
const grid = document.getElementById('plugin-grid');
|
|
if (!grid) return;
|
|
|
|
let html = '';
|
|
|
|
for (const [pluginId, config] of Object.entries(PLUGIN_CONFIG)) {
|
|
const hasData = config.metrics.some(m => data[`about_${pluginId}_${m}`]);
|
|
if (!hasData) continue;
|
|
|
|
html += `
|
|
<div class="plugin-card">
|
|
<div class="plugin-header">
|
|
<div class="plugin-name">${getIcon(config.icon)} ${config.name}</div>
|
|
<span class="plugin-status active">Active</span>
|
|
</div>
|
|
<div class="plugin-metrics">
|
|
`;
|
|
|
|
for (const metricName of config.metrics) {
|
|
const fullKey = `about_${pluginId}_${metricName}`;
|
|
const metricInfo = METRIC_NAMES[fullKey] || {label: metricName, icon: 'note', color: 'blue'};
|
|
|
|
const rawValue = getLatestValue(data, fullKey);
|
|
const value = rawValue !== null
|
|
? formatValue(rawValue) + (metricInfo.unit ? ' ' + metricInfo.unit : '')
|
|
: '-';
|
|
|
|
html += `
|
|
<div class="plugin-metric">
|
|
<div class="label">${getIcon(metricInfo.icon)} ${metricInfo.label}</div>
|
|
<div class="value color-${metricInfo.color}">${value}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
html += '</div></div>';
|
|
}
|
|
|
|
grid.innerHTML = html || '<div class="empty-state">No plugin data available</div>';
|
|
}
|
|
|
|
async function fetchData() {
|
|
try {
|
|
const res = await fetch('/api/stats/data');
|
|
if (!res.ok) throw new Error('Failed to fetch');
|
|
|
|
const data = await res.json();
|
|
allMetricsData = data;
|
|
|
|
updateChart(charts.visits, data.about_visitors_total_visits, 'chart-val-visits');
|
|
updateChart(charts.hr, data.about_health_heart_rate_current, 'chart-val-hr');
|
|
updateChart(charts.steps, data.about_health_steps_today, 'chart-val-steps');
|
|
updateChart(charts.services, data.about_services_online_count, 'chart-val-services');
|
|
updateChart(charts.coding, data.about_code_wakatime_hours_7d, 'chart-val-coding');
|
|
updateChart(charts.scrobbles, data.about_lastfm_total_scrobbles, 'chart-val-scrobbles');
|
|
updateChart(charts.latency, data.about_services_avg_response_time_ms, 'chart-val-latency');
|
|
|
|
updateSummaryCards(data);
|
|
updatePluginGrid(data);
|
|
|
|
document.getElementById('last-update').textContent = new Date().toLocaleTimeString();
|
|
document.getElementById('loading').classList.add('hidden');
|
|
|
|
} catch (e) {
|
|
console.error('Stats fetch error:', e);
|
|
document.getElementById('loading').classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
function toggleTheme() {
|
|
currentTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
document.documentElement.setAttribute('data-theme', currentTheme);
|
|
localStorage.setItem('stats-theme', currentTheme);
|
|
|
|
const icon = document.getElementById('theme-icon');
|
|
if (currentTheme === 'dark') {
|
|
icon.innerHTML = '<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>';
|
|
} else {
|
|
icon.innerHTML = '<path d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"/>';
|
|
}
|
|
document.getElementById('theme-text').textContent = currentTheme === 'dark' ? 'Dark' : 'Light';
|
|
|
|
updateChartThemes();
|
|
}
|
|
|
|
function updateChartThemes() {
|
|
const colors = getChartColors();
|
|
|
|
for (const chart of Object.values(charts)) {
|
|
chart.options.scales.x.ticks.color = colors.text;
|
|
chart.options.scales.y.ticks.color = colors.text;
|
|
chart.options.scales.y.grid.color = colors.grid;
|
|
chart.options.plugins.tooltip.backgroundColor = currentTheme === 'dark' ? 'rgba(0,0,0,0.9)' : 'rgba(255,255,255,0.95)';
|
|
chart.options.plugins.tooltip.titleColor = currentTheme === 'dark' ? '#fff' : '#1f2328';
|
|
chart.options.plugins.tooltip.bodyColor = currentTheme === 'dark' ? '#e6edf3' : '#1f2328';
|
|
chart.update('none');
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
document.documentElement.setAttribute('data-theme', currentTheme);
|
|
|
|
const icon = document.getElementById('theme-icon');
|
|
if (currentTheme === 'dark') {
|
|
icon.innerHTML = '<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>';
|
|
} else {
|
|
icon.innerHTML = '<path d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"/>';
|
|
}
|
|
document.getElementById('theme-text').textContent = currentTheme === 'dark' ? 'Dark' : 'Light';
|
|
|
|
initCharts();
|
|
fetchData();
|
|
setInterval(fetchData, 30000);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |