claude-usage-dashboard 1.3.11 → 1.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/js/app.js +43 -12
- package/public/js/charts/quota-gauge.js +11 -2
- package/server/aggregator.js +7 -3
package/package.json
CHANGED
package/public/js/app.js
CHANGED
|
@@ -47,18 +47,40 @@ function getTimezoneAbbr() {
|
|
|
47
47
|
return tz ? tz.value : '';
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Derive the 7-day quota window from resets_at, truncated to the hour
|
|
51
|
+
function getQuotaWindow(sevenDay) {
|
|
52
|
+
if (!sevenDay?.resets_at) return null;
|
|
53
|
+
const resetsAt = new Date(sevenDay.resets_at);
|
|
54
|
+
resetsAt.setMinutes(0, 0, 0);
|
|
55
|
+
const windowStart = new Date(resetsAt);
|
|
56
|
+
windowStart.setDate(windowStart.getDate() - 7);
|
|
57
|
+
return { from: windowStart, to: resetsAt };
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
async function loadQuota() {
|
|
51
61
|
try {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
const data = await fetchQuota();
|
|
63
|
+
|
|
64
|
+
// Use the actual quota window (resets_at - 7 days → resets_at)
|
|
65
|
+
let cost7dValue = 0;
|
|
66
|
+
let quotaWindowFrom = null;
|
|
67
|
+
let quotaWindowTo = null;
|
|
68
|
+
const sevenDay = data.seven_day;
|
|
69
|
+
const window = getQuotaWindow(sevenDay);
|
|
70
|
+
if (window && sevenDay.utilization > 0) {
|
|
71
|
+
quotaWindowFrom = window.from;
|
|
72
|
+
quotaWindowTo = window.to;
|
|
73
|
+
const cost7d = await fetchCost({
|
|
74
|
+
from: window.from.toISOString(),
|
|
75
|
+
to: window.to.toISOString(),
|
|
76
|
+
plan: state.plan.plan,
|
|
77
|
+
});
|
|
78
|
+
cost7dValue = cost7d.api_equivalent_cost_usd;
|
|
79
|
+
}
|
|
56
80
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
]);
|
|
61
|
-
renderQuotaGauges(document.getElementById('chart-quota'), data, { cost7d: cost7d.api_equivalent_cost_usd });
|
|
81
|
+
renderQuotaGauges(document.getElementById('chart-quota'), data, {
|
|
82
|
+
cost7d: cost7dValue, quotaWindowFrom, quotaWindowTo,
|
|
83
|
+
});
|
|
62
84
|
const el = document.getElementById('quota-last-updated');
|
|
63
85
|
if (el && data.lastFetched) el.textContent = `Updated ${new Date(data.lastFetched).toLocaleTimeString()} ${getTimezoneAbbr()}`;
|
|
64
86
|
} catch { /* silently degrade */ }
|
|
@@ -265,9 +287,18 @@ function init() {
|
|
|
265
287
|
}
|
|
266
288
|
}).catch(() => {});
|
|
267
289
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
290
|
+
// Default date range to the 7-day quota window (resets_at - 7 days → resets_at)
|
|
291
|
+
fetchQuota().then(data => {
|
|
292
|
+
const window = getQuotaWindow(data.seven_day);
|
|
293
|
+
if (window) {
|
|
294
|
+
const fmt = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
|
|
295
|
+
datePicker.setRange(fmt(window.from), fmt(window.to));
|
|
296
|
+
}
|
|
297
|
+
}).catch(() => {}).finally(() => {
|
|
298
|
+
loadAll();
|
|
299
|
+
loadQuota();
|
|
300
|
+
startAutoRefresh();
|
|
301
|
+
});
|
|
271
302
|
}
|
|
272
303
|
|
|
273
304
|
document.addEventListener('DOMContentLoaded', init);
|
|
@@ -95,9 +95,18 @@ export function renderQuotaGauges(container, data, opts = {}) {
|
|
|
95
95
|
const proj = document.createElement('div');
|
|
96
96
|
proj.style.cssText = 'margin-top:12px;padding:8px 12px;background:#1e293b;border-radius:6px;font-size:12px;color:#94a3b8';
|
|
97
97
|
const monthlyProjected = projectedCost * (30 / 7);
|
|
98
|
+
|
|
99
|
+
// Format the quota window range for display
|
|
100
|
+
const dtFmt = { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' };
|
|
101
|
+
const tzParts = new Intl.DateTimeFormat(undefined, { timeZoneName: 'short' }).formatToParts(new Date());
|
|
102
|
+
const tz = tzParts.find(p => p.type === 'timeZoneName')?.value || '';
|
|
103
|
+
const windowRange = opts.quotaWindowFrom && opts.quotaWindowTo
|
|
104
|
+
? ` <span style="color:#64748b">(${opts.quotaWindowFrom.toLocaleString(undefined, dtFmt)} → ${opts.quotaWindowTo.toLocaleString(undefined, dtFmt)} ${tz})</span>`
|
|
105
|
+
: '';
|
|
106
|
+
|
|
98
107
|
proj.innerHTML =
|
|
99
|
-
`7-day usage: <strong style="color:#e2e8f0">$${opts.cost7d.toFixed(2)}</strong> API cost at <strong style="color:#e2e8f0">${sevenDay.utilization.toFixed(1)}%</strong> quota` +
|
|
100
|
-
|
|
108
|
+
`7-day usage: <strong style="color:#e2e8f0">$${opts.cost7d.toFixed(2)}</strong> API cost at <strong style="color:#e2e8f0">${sevenDay.utilization.toFixed(1)}%</strong> quota${windowRange}` +
|
|
109
|
+
`<br>Projected at 100%: <strong style="color:#fbbf24">$${projectedCost.toFixed(2)}</strong>/week` +
|
|
101
110
|
` · <strong style="color:#fbbf24">$${monthlyProjected.toFixed(2)}</strong>/month`;
|
|
102
111
|
container.appendChild(proj);
|
|
103
112
|
}
|
package/server/aggregator.js
CHANGED
|
@@ -2,9 +2,13 @@ import { calculateRecordCost, getModelPricing } from './pricing.js';
|
|
|
2
2
|
|
|
3
3
|
export function filterByDateRange(records, from, to) {
|
|
4
4
|
if (!from && !to) return records;
|
|
5
|
-
//
|
|
6
|
-
const start = from
|
|
7
|
-
|
|
5
|
+
// Support both date-only (YYYY-MM-DD → local time boundaries) and full ISO timestamps
|
|
6
|
+
const start = from
|
|
7
|
+
? new Date(from.includes('T') ? from : from + 'T00:00:00.000').getTime()
|
|
8
|
+
: -Infinity;
|
|
9
|
+
const end = to
|
|
10
|
+
? new Date(to.includes('T') ? to : to + 'T23:59:59.999').getTime()
|
|
11
|
+
: Infinity;
|
|
8
12
|
return records.filter(r => {
|
|
9
13
|
const t = new Date(r.timestamp).getTime();
|
|
10
14
|
return t >= start && t <= end;
|