feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
276
claude-usage-stats.html
Normal file
276
claude-usage-stats.html
Normal file
@@ -0,0 +1,276 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Claude Code Usage Statistics</title>
|
||||
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
min-height: 100vh;
|
||||
color: #e0e0e0;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
font-size: 2.5rem;
|
||||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.stat-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.stat-label {
|
||||
color: #888;
|
||||
margin-top: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.chart-container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.chart-title {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
canvas {
|
||||
max-height: 400px;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="text/babel">
|
||||
const { useEffect, useRef } = React;
|
||||
|
||||
const USAGE_DATA = [{"date": "2025-09-27", "count": 52}, {"date": "2025-09-28", "count": 135}, {"date": "2025-09-29", "count": 87}, {"date": "2025-09-30", "count": 2}, {"date": "2025-10-02", "count": 20}, {"date": "2025-10-13", "count": 108}, {"date": "2025-10-14", "count": 99}, {"date": "2025-10-15", "count": 3}, {"date": "2025-10-16", "count": 10}, {"date": "2025-10-17", "count": 4}, {"date": "2025-10-21", "count": 4}, {"date": "2025-10-22", "count": 1}, {"date": "2025-10-23", "count": 2}, {"date": "2025-10-24", "count": 12}, {"date": "2025-10-28", "count": 10}, {"date": "2025-10-29", "count": 7}, {"date": "2025-11-03", "count": 10}, {"date": "2025-11-11", "count": 34}, {"date": "2025-11-12", "count": 16}, {"date": "2025-11-23", "count": 3}, {"date": "2025-12-23", "count": 67}, {"date": "2025-12-24", "count": 120}, {"date": "2025-12-25", "count": 272}, {"date": "2025-12-26", "count": 40}, {"date": "2025-12-27", "count": 382}, {"date": "2025-12-28", "count": 244}, {"date": "2025-12-29", "count": 396}, {"date": "2025-12-30", "count": 223}, {"date": "2025-12-31", "count": 215}, {"date": "2026-01-01", "count": 156}, {"date": "2026-01-02", "count": 182}, {"date": "2026-01-03", "count": 141}, {"date": "2026-01-04", "count": 448}, {"date": "2026-01-05", "count": 60}, {"date": "2026-01-06", "count": 160}, {"date": "2026-01-07", "count": 90}, {"date": "2026-01-08", "count": 51}, {"date": "2026-01-09", "count": 1}, {"date": "2026-01-13", "count": 59}, {"date": "2026-01-14", "count": 132}, {"date": "2026-01-15", "count": 4}, {"date": "2026-01-16", "count": 142}, {"date": "2026-01-17", "count": 386}, {"date": "2026-01-18", "count": 649}, {"date": "2026-01-19", "count": 99}, {"date": "2026-01-20", "count": 15}, {"date": "2026-01-21", "count": 160}, {"date": "2026-01-22", "count": 202}, {"date": "2026-01-23", "count": 161}, {"date": "2026-01-24", "count": 433}, {"date": "2026-01-25", "count": 252}, {"date": "2026-01-27", "count": 11}, {"date": "2026-01-28", "count": 177}, {"date": "2026-01-29", "count": 452}, {"date": "2026-01-30", "count": 325}, {"date": "2026-01-31", "count": 484}, {"date": "2026-02-01", "count": 614}, {"date": "2026-02-02", "count": 182}];
|
||||
|
||||
function LineChartComponent({ data }) {
|
||||
const chartRef = useRef(null);
|
||||
const chartInstance = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.destroy();
|
||||
}
|
||||
|
||||
const ctx = chartRef.current.getContext('2d');
|
||||
chartInstance.current = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: data.map(d => d.date),
|
||||
datasets: [{
|
||||
label: 'Prompts',
|
||||
data: data.map(d => d.count),
|
||||
borderColor: '#667eea',
|
||||
backgroundColor: 'rgba(102, 126, 234, 0.2)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 3,
|
||||
pointBackgroundColor: '#667eea'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: { color: '#888', maxRotation: 45 },
|
||||
grid: { color: 'rgba(255,255,255,0.1)' }
|
||||
},
|
||||
y: {
|
||||
ticks: { color: '#888' },
|
||||
grid: { color: 'rgba(255,255,255,0.1)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.destroy();
|
||||
}
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
return <canvas ref={chartRef} height="400"></canvas>;
|
||||
}
|
||||
|
||||
function BarChartComponent({ data }) {
|
||||
const chartRef = useRef(null);
|
||||
const chartInstance = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.destroy();
|
||||
}
|
||||
|
||||
const ctx = chartRef.current.getContext('2d');
|
||||
chartInstance.current = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: data.map(d => d.week),
|
||||
datasets: [{
|
||||
label: 'Prompts',
|
||||
data: data.map(d => d.count),
|
||||
backgroundColor: 'rgba(118, 75, 162, 0.7)',
|
||||
borderColor: '#764ba2',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: { color: '#888' },
|
||||
grid: { color: 'rgba(255,255,255,0.1)' }
|
||||
},
|
||||
y: {
|
||||
ticks: { color: '#888' },
|
||||
grid: { color: 'rgba(255,255,255,0.1)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.destroy();
|
||||
}
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
return <canvas ref={chartRef} height="300"></canvas>;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const data = USAGE_DATA;
|
||||
const totalPrompts = data.reduce((sum, d) => sum + d.count, 0);
|
||||
const avgPerDay = Math.round(totalPrompts / data.length);
|
||||
const maxDay = data.reduce((max, d) => d.count > max.count ? d : max, data[0]);
|
||||
const activeDays = data.filter(d => d.count > 0).length;
|
||||
|
||||
// Calculate weekly data
|
||||
const weeklyData = [];
|
||||
for (let i = 0; i < data.length; i += 7) {
|
||||
const weekSlice = data.slice(i, i + 7);
|
||||
const weekTotal = weekSlice.reduce((sum, d) => sum + d.count, 0);
|
||||
weeklyData.push({
|
||||
week: `Week ${Math.floor(i / 7) + 1}`,
|
||||
count: weekTotal
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<h1>Claude Code Usage Statistics</h1>
|
||||
<p className="subtitle">Tracking your AI-assisted development journey</p>
|
||||
|
||||
<div className="stats-grid">
|
||||
<div className="stat-card">
|
||||
<div className="stat-value">{totalPrompts.toLocaleString()}</div>
|
||||
<div className="stat-label">Total Prompts</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value">{activeDays}</div>
|
||||
<div className="stat-label">Active Days</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value">{avgPerDay}</div>
|
||||
<div className="stat-label">Avg Per Day</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value">{maxDay.count}</div>
|
||||
<div className="stat-label">Peak Day ({maxDay.date})</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="chart-container">
|
||||
<h2 className="chart-title">Daily Usage Over Time</h2>
|
||||
<div style={{height: '400px'}}>
|
||||
<LineChartComponent data={data} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="chart-container">
|
||||
<h2 className="chart-title">Weekly Summary</h2>
|
||||
<div style={{height: '300px'}}>
|
||||
<BarChartComponent data={weeklyData} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer">
|
||||
Generated on {new Date().toLocaleDateString()} • Data from ~/.claude/history.jsonl
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user