ccus-cli 0.1.3 → 0.1.4
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/README.md +178 -174
- package/dist/cli.js +27 -13
- package/dist/lib/aggregate-dashboard.js +407 -407
- package/dist/lib/dashboard.js +176 -42
- package/package.json +35 -35
|
@@ -126,16 +126,16 @@ function statValue(value, suffix = "%") {
|
|
|
126
126
|
*/
|
|
127
127
|
function renderDailyUserRequestChart(people, dailyIndex, dateAxis) {
|
|
128
128
|
if (people.length === 0 || dateAxis.length === 0) {
|
|
129
|
-
return `
|
|
130
|
-
<section class="panel chart-panel">
|
|
131
|
-
<div class="panel-header">
|
|
132
|
-
<div>
|
|
133
|
-
<p class="eyebrow">Daily User Requests</p>
|
|
134
|
-
<h2>每日用户请求数对比</h2>
|
|
135
|
-
</div>
|
|
136
|
-
<p class="muted">没有可绘制的日度数据。</p>
|
|
137
|
-
</div>
|
|
138
|
-
</section>
|
|
129
|
+
return `
|
|
130
|
+
<section class="panel chart-panel">
|
|
131
|
+
<div class="panel-header">
|
|
132
|
+
<div>
|
|
133
|
+
<p class="eyebrow">Daily User Requests</p>
|
|
134
|
+
<h2>每日用户请求数对比</h2>
|
|
135
|
+
</div>
|
|
136
|
+
<p class="muted">没有可绘制的日度数据。</p>
|
|
137
|
+
</div>
|
|
138
|
+
</section>
|
|
139
139
|
`;
|
|
140
140
|
}
|
|
141
141
|
const width = 920;
|
|
@@ -184,22 +184,22 @@ function renderDailyUserRequestChart(people, dailyIndex, dateAxis) {
|
|
|
184
184
|
return `<span class="legend-chip"><span class="legend-dot" style="background:${color}"></span>${escapeHtml(series.person.personKey)}</span>`;
|
|
185
185
|
})
|
|
186
186
|
.join("");
|
|
187
|
-
return `
|
|
188
|
-
<section class="panel chart-panel">
|
|
189
|
-
<div class="panel-header">
|
|
190
|
-
<div>
|
|
191
|
-
<p class="eyebrow">Daily User Requests</p>
|
|
192
|
-
<h2>每日用户请求数对比</h2>
|
|
193
|
-
</div>
|
|
194
|
-
<p class="muted">基于每人 daily 汇总中的 userMessageCount(已剔除 tool_result 工具回填;sidechain 子 agent 提示保留)。</p>
|
|
195
|
-
</div>
|
|
196
|
-
<div class="legend">${legend}</div>
|
|
197
|
-
<svg viewBox="0 0 ${width} ${height}" class="chart" role="img" aria-label="每日用户请求数对比">
|
|
198
|
-
${ticks}
|
|
199
|
-
${xLabels}
|
|
200
|
-
${seriesPaths}
|
|
201
|
-
</svg>
|
|
202
|
-
</section>
|
|
187
|
+
return `
|
|
188
|
+
<section class="panel chart-panel">
|
|
189
|
+
<div class="panel-header">
|
|
190
|
+
<div>
|
|
191
|
+
<p class="eyebrow">Daily User Requests</p>
|
|
192
|
+
<h2>每日用户请求数对比</h2>
|
|
193
|
+
</div>
|
|
194
|
+
<p class="muted">基于每人 daily 汇总中的 userMessageCount(已剔除 tool_result 工具回填;sidechain 子 agent 提示保留)。</p>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="legend">${legend}</div>
|
|
197
|
+
<svg viewBox="0 0 ${width} ${height}" class="chart" role="img" aria-label="每日用户请求数对比">
|
|
198
|
+
${ticks}
|
|
199
|
+
${xLabels}
|
|
200
|
+
${seriesPaths}
|
|
201
|
+
</svg>
|
|
202
|
+
</section>
|
|
203
203
|
`;
|
|
204
204
|
}
|
|
205
205
|
/**
|
|
@@ -213,16 +213,16 @@ function renderSevenDayPeakChart(people) {
|
|
|
213
213
|
.filter((person) => person.sevenDayPeakUsagePct !== null)
|
|
214
214
|
.sort((left, right) => (right.sevenDayPeakUsagePct ?? 0) - (left.sevenDayPeakUsagePct ?? 0));
|
|
215
215
|
if (ranked.length === 0) {
|
|
216
|
-
return `
|
|
217
|
-
<section class="panel chart-panel">
|
|
218
|
-
<div class="panel-header">
|
|
219
|
-
<div>
|
|
220
|
-
<p class="eyebrow">Weekly Peak Usage</p>
|
|
221
|
-
<h2>周使用量峰值对比</h2>
|
|
222
|
-
</div>
|
|
223
|
-
<p class="muted">还没有 7 天额度使用率样本。</p>
|
|
224
|
-
</div>
|
|
225
|
-
</section>
|
|
216
|
+
return `
|
|
217
|
+
<section class="panel chart-panel">
|
|
218
|
+
<div class="panel-header">
|
|
219
|
+
<div>
|
|
220
|
+
<p class="eyebrow">Weekly Peak Usage</p>
|
|
221
|
+
<h2>周使用量峰值对比</h2>
|
|
222
|
+
</div>
|
|
223
|
+
<p class="muted">还没有 7 天额度使用率样本。</p>
|
|
224
|
+
</div>
|
|
225
|
+
</section>
|
|
226
226
|
`;
|
|
227
227
|
}
|
|
228
228
|
const rowHeight = 38;
|
|
@@ -243,28 +243,28 @@ function renderSevenDayPeakChart(people) {
|
|
|
243
243
|
const barY = rowTop + (rowHeight - barHeight) / 2;
|
|
244
244
|
const barWidth = Math.max(2, (pct / maxValue) * trackWidth);
|
|
245
245
|
const textY = barY + barHeight / 2 + 4;
|
|
246
|
-
return `
|
|
247
|
-
<g>
|
|
248
|
-
<text x="8" y="${textY}" class="bar-label">${escapeHtml(person.personKey)}</text>
|
|
249
|
-
<rect x="${trackX}" y="${barY}" width="${trackWidth}" height="${barHeight}" rx="6" class="bar-track" />
|
|
250
|
-
<rect x="${trackX}" y="${barY}" width="${barWidth.toFixed(2)}" height="${barHeight}" rx="6" class="bar-fill" />
|
|
251
|
-
<text x="${trackX + trackWidth + 10}" y="${textY}" class="bar-value">${pct.toFixed(1)}%</text>
|
|
246
|
+
return `
|
|
247
|
+
<g>
|
|
248
|
+
<text x="8" y="${textY}" class="bar-label">${escapeHtml(person.personKey)}</text>
|
|
249
|
+
<rect x="${trackX}" y="${barY}" width="${trackWidth}" height="${barHeight}" rx="6" class="bar-track" />
|
|
250
|
+
<rect x="${trackX}" y="${barY}" width="${barWidth.toFixed(2)}" height="${barHeight}" rx="6" class="bar-fill" />
|
|
251
|
+
<text x="${trackX + trackWidth + 10}" y="${textY}" class="bar-value">${pct.toFixed(1)}%</text>
|
|
252
252
|
</g>`;
|
|
253
253
|
})
|
|
254
254
|
.join("");
|
|
255
|
-
return `
|
|
256
|
-
<section class="panel chart-panel">
|
|
257
|
-
<div class="panel-header">
|
|
258
|
-
<div>
|
|
259
|
-
<p class="eyebrow">Weekly Peak Usage</p>
|
|
260
|
-
<h2>周使用量峰值对比</h2>
|
|
261
|
-
</div>
|
|
262
|
-
<p class="muted">每个人 7 天额度使用率(sevenDayPeakUsagePct)的峰值,条越长越接近用满周额度。</p>
|
|
263
|
-
</div>
|
|
264
|
-
<svg viewBox="0 0 ${width} ${height}" class="chart" role="img" aria-label="周使用量峰值对比">
|
|
265
|
-
${bars}
|
|
266
|
-
</svg>
|
|
267
|
-
</section>
|
|
255
|
+
return `
|
|
256
|
+
<section class="panel chart-panel">
|
|
257
|
+
<div class="panel-header">
|
|
258
|
+
<div>
|
|
259
|
+
<p class="eyebrow">Weekly Peak Usage</p>
|
|
260
|
+
<h2>周使用量峰值对比</h2>
|
|
261
|
+
</div>
|
|
262
|
+
<p class="muted">每个人 7 天额度使用率(sevenDayPeakUsagePct)的峰值,条越长越接近用满周额度。</p>
|
|
263
|
+
</div>
|
|
264
|
+
<svg viewBox="0 0 ${width} ${height}" class="chart" role="img" aria-label="周使用量峰值对比">
|
|
265
|
+
${bars}
|
|
266
|
+
</svg>
|
|
267
|
+
</section>
|
|
268
268
|
`;
|
|
269
269
|
}
|
|
270
270
|
/** 把时间戳格式化成图表 X 轴用的本地「MM-DD HH:mm」短标签。 */
|
|
@@ -302,16 +302,16 @@ function renderFiveHourUsageChart(people, detailRows) {
|
|
|
302
302
|
.filter((entry) => entry.fivePoints.length > 0 || entry.sevenPoints.length > 0);
|
|
303
303
|
const allPoints = series.flatMap((entry) => [...entry.fivePoints, ...entry.sevenPoints]);
|
|
304
304
|
if (allPoints.length === 0) {
|
|
305
|
-
return `
|
|
306
|
-
<section class="panel chart-panel">
|
|
307
|
-
<div class="panel-header">
|
|
308
|
-
<div>
|
|
309
|
-
<p class="eyebrow">Usage Detail</p>
|
|
310
|
-
<h2>5h / 7d 使用率详细曲线</h2>
|
|
311
|
-
</div>
|
|
312
|
-
<p class="muted">还没有带使用率的 statusline 采样。</p>
|
|
313
|
-
</div>
|
|
314
|
-
</section>
|
|
305
|
+
return `
|
|
306
|
+
<section class="panel chart-panel">
|
|
307
|
+
<div class="panel-header">
|
|
308
|
+
<div>
|
|
309
|
+
<p class="eyebrow">Usage Detail</p>
|
|
310
|
+
<h2>5h / 7d 使用率详细曲线</h2>
|
|
311
|
+
</div>
|
|
312
|
+
<p class="muted">还没有带使用率的 statusline 采样。</p>
|
|
313
|
+
</div>
|
|
314
|
+
</section>
|
|
315
315
|
`;
|
|
316
316
|
}
|
|
317
317
|
const width = 920;
|
|
@@ -361,91 +361,91 @@ function renderFiveHourUsageChart(people, detailRows) {
|
|
|
361
361
|
return `<span class="legend-chip"><span class="legend-dot" style="background:${color}"></span><span class="legend-dot" style="background:${sevenColor}"></span>${escapeHtml(entry.person.personKey)}</span>`;
|
|
362
362
|
})
|
|
363
363
|
.join("");
|
|
364
|
-
return `
|
|
365
|
-
<section class="panel chart-panel">
|
|
366
|
-
<div class="panel-header">
|
|
367
|
-
<div>
|
|
368
|
-
<p class="eyebrow">Usage Detail</p>
|
|
369
|
-
<h2>5h / 7d 使用率详细曲线</h2>
|
|
370
|
-
</div>
|
|
371
|
-
<p class="muted">每条 statusline 采样的 5 小时额度(实线)与 7 天周额度(对比色虚线)使用率,按真实时间戳绘制、共用 Y 轴。</p>
|
|
372
|
-
</div>
|
|
373
|
-
<div class="legend">
|
|
374
|
-
${legend}
|
|
375
|
-
<span class="legend-chip"><span class="legend-line legend-line-solid"></span>5h 使用率(实线)</span>
|
|
376
|
-
<span class="legend-chip"><span class="legend-line legend-line-dashed"></span>7d 周使用量(对比色虚线)</span>
|
|
377
|
-
</div>
|
|
378
|
-
<svg viewBox="0 0 ${width} ${height}" class="chart" role="img" aria-label="5h / 7d 使用率详细曲线">
|
|
379
|
-
${ticks}
|
|
380
|
-
${xLabels}
|
|
381
|
-
${seriesPaths}
|
|
382
|
-
</svg>
|
|
383
|
-
</section>
|
|
364
|
+
return `
|
|
365
|
+
<section class="panel chart-panel">
|
|
366
|
+
<div class="panel-header">
|
|
367
|
+
<div>
|
|
368
|
+
<p class="eyebrow">Usage Detail</p>
|
|
369
|
+
<h2>5h / 7d 使用率详细曲线</h2>
|
|
370
|
+
</div>
|
|
371
|
+
<p class="muted">每条 statusline 采样的 5 小时额度(实线)与 7 天周额度(对比色虚线)使用率,按真实时间戳绘制、共用 Y 轴。</p>
|
|
372
|
+
</div>
|
|
373
|
+
<div class="legend">
|
|
374
|
+
${legend}
|
|
375
|
+
<span class="legend-chip"><span class="legend-line legend-line-solid"></span>5h 使用率(实线)</span>
|
|
376
|
+
<span class="legend-chip"><span class="legend-line legend-line-dashed"></span>7d 周使用量(对比色虚线)</span>
|
|
377
|
+
</div>
|
|
378
|
+
<svg viewBox="0 0 ${width} ${height}" class="chart" role="img" aria-label="5h / 7d 使用率详细曲线">
|
|
379
|
+
${ticks}
|
|
380
|
+
${xLabels}
|
|
381
|
+
${seriesPaths}
|
|
382
|
+
</svg>
|
|
383
|
+
</section>
|
|
384
384
|
`;
|
|
385
385
|
}
|
|
386
386
|
/** 按人渲染汇总卡片,给出请求 / 消息 / token / usage 几个核心指标。 */
|
|
387
387
|
function renderPeopleLeaderboard(people) {
|
|
388
388
|
if (people.length === 0) {
|
|
389
|
-
return `
|
|
390
|
-
<section class="panel table-panel">
|
|
391
|
-
<div class="panel-header">
|
|
392
|
-
<div>
|
|
393
|
-
<p class="eyebrow">People</p>
|
|
394
|
-
<h2>多人对比</h2>
|
|
395
|
-
</div>
|
|
396
|
-
<p class="muted">还没有匹配的导出 bundle。</p>
|
|
397
|
-
</div>
|
|
398
|
-
</section>
|
|
389
|
+
return `
|
|
390
|
+
<section class="panel table-panel">
|
|
391
|
+
<div class="panel-header">
|
|
392
|
+
<div>
|
|
393
|
+
<p class="eyebrow">People</p>
|
|
394
|
+
<h2>多人对比</h2>
|
|
395
|
+
</div>
|
|
396
|
+
<p class="muted">还没有匹配的导出 bundle。</p>
|
|
397
|
+
</div>
|
|
398
|
+
</section>
|
|
399
399
|
`;
|
|
400
400
|
}
|
|
401
401
|
const rows = people
|
|
402
|
-
.map((person, index) => `
|
|
403
|
-
<tr>
|
|
404
|
-
<td class="rank">${index + 1}</td>
|
|
405
|
-
<td><strong>${escapeHtml(person.personKey)}</strong></td>
|
|
406
|
-
<td>${formatNumber(person.userMessageCount)}</td>
|
|
407
|
-
<td>${formatTokensM(person.inputTokens)}</td>
|
|
408
|
-
<td>${formatTokensM(person.outputTokens)}</td>
|
|
409
|
-
<td>${formatTokensM(person.cacheReadInputTokens)}</td>
|
|
410
|
-
<td>${escapeHtml(statValue(person.fiveHourPeakUsagePct))}</td>
|
|
411
|
-
<td>${escapeHtml(statValue(person.fiveHourLatestUsagePct))}</td>
|
|
412
|
-
<td>${escapeHtml(statValue(person.sevenDayPeakUsagePct))}</td>
|
|
413
|
-
<td>${escapeHtml(statValue(person.sevenDayLatestUsagePct))}</td>
|
|
414
|
-
<td>${person.activeDays}</td>
|
|
415
|
-
<td class="muted-col">${formatNumber(person.apiRequestCount)}</td>
|
|
402
|
+
.map((person, index) => `
|
|
403
|
+
<tr>
|
|
404
|
+
<td class="rank">${index + 1}</td>
|
|
405
|
+
<td><strong>${escapeHtml(person.personKey)}</strong></td>
|
|
406
|
+
<td>${formatNumber(person.userMessageCount)}</td>
|
|
407
|
+
<td>${formatTokensM(person.inputTokens)}</td>
|
|
408
|
+
<td>${formatTokensM(person.outputTokens)}</td>
|
|
409
|
+
<td>${formatTokensM(person.cacheReadInputTokens)}</td>
|
|
410
|
+
<td>${escapeHtml(statValue(person.fiveHourPeakUsagePct))}</td>
|
|
411
|
+
<td>${escapeHtml(statValue(person.fiveHourLatestUsagePct))}</td>
|
|
412
|
+
<td>${escapeHtml(statValue(person.sevenDayPeakUsagePct))}</td>
|
|
413
|
+
<td>${escapeHtml(statValue(person.sevenDayLatestUsagePct))}</td>
|
|
414
|
+
<td>${person.activeDays}</td>
|
|
415
|
+
<td class="muted-col">${formatNumber(person.apiRequestCount)}</td>
|
|
416
416
|
</tr>`)
|
|
417
417
|
.join("");
|
|
418
|
-
return `
|
|
419
|
-
<section class="panel table-panel">
|
|
420
|
-
<div class="panel-header">
|
|
421
|
-
<div>
|
|
422
|
-
<p class="eyebrow">People</p>
|
|
423
|
-
<h2>多人对比</h2>
|
|
424
|
-
</div>
|
|
425
|
-
<p class="muted">按用户消息数降序排列,所有数字直接来自 daily/weekly 汇总。</p>
|
|
426
|
-
</div>
|
|
427
|
-
<div class="table-wrap">
|
|
428
|
-
<table>
|
|
429
|
-
<thead>
|
|
430
|
-
<tr>
|
|
431
|
-
<th>#</th>
|
|
432
|
-
<th>personKey</th>
|
|
433
|
-
<th>消息</th>
|
|
434
|
-
<th>Input tokens</th>
|
|
435
|
-
<th>Output tokens</th>
|
|
436
|
-
<th>Cache read tokens</th>
|
|
437
|
-
<th>5h Peak</th>
|
|
438
|
-
<th>5h Latest</th>
|
|
439
|
-
<th>7d Peak</th>
|
|
440
|
-
<th>7d Latest</th>
|
|
441
|
-
<th>活跃天数</th>
|
|
442
|
-
<th class="muted-col">API 请求</th>
|
|
443
|
-
</tr>
|
|
444
|
-
</thead>
|
|
445
|
-
<tbody>${rows}</tbody>
|
|
446
|
-
</table>
|
|
447
|
-
</div>
|
|
448
|
-
</section>
|
|
418
|
+
return `
|
|
419
|
+
<section class="panel table-panel">
|
|
420
|
+
<div class="panel-header">
|
|
421
|
+
<div>
|
|
422
|
+
<p class="eyebrow">People</p>
|
|
423
|
+
<h2>多人对比</h2>
|
|
424
|
+
</div>
|
|
425
|
+
<p class="muted">按用户消息数降序排列,所有数字直接来自 daily/weekly 汇总。</p>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="table-wrap">
|
|
428
|
+
<table>
|
|
429
|
+
<thead>
|
|
430
|
+
<tr>
|
|
431
|
+
<th>#</th>
|
|
432
|
+
<th>personKey</th>
|
|
433
|
+
<th>消息</th>
|
|
434
|
+
<th>Input tokens</th>
|
|
435
|
+
<th>Output tokens</th>
|
|
436
|
+
<th>Cache read tokens</th>
|
|
437
|
+
<th>5h Peak</th>
|
|
438
|
+
<th>5h Latest</th>
|
|
439
|
+
<th>7d Peak</th>
|
|
440
|
+
<th>7d Latest</th>
|
|
441
|
+
<th>活跃天数</th>
|
|
442
|
+
<th class="muted-col">API 请求</th>
|
|
443
|
+
</tr>
|
|
444
|
+
</thead>
|
|
445
|
+
<tbody>${rows}</tbody>
|
|
446
|
+
</table>
|
|
447
|
+
</div>
|
|
448
|
+
</section>
|
|
449
449
|
`;
|
|
450
450
|
}
|
|
451
451
|
/** 把 daily 行按 personKey × date 排成一个矩阵表。 */
|
|
@@ -470,27 +470,27 @@ function renderDailyMatrix(people, dailyIndex, dateAxis) {
|
|
|
470
470
|
return `<tr><th class="row-head">${escapeHtml(person.personKey)}</th>${cells}</tr>`;
|
|
471
471
|
})
|
|
472
472
|
.join("");
|
|
473
|
-
return `
|
|
474
|
-
<section class="panel table-panel">
|
|
475
|
-
<div class="panel-header">
|
|
476
|
-
<div>
|
|
477
|
-
<p class="eyebrow">Daily Matrix</p>
|
|
478
|
-
<h2>按天 × 人 矩阵</h2>
|
|
479
|
-
</div>
|
|
480
|
-
<p class="muted">单元格上方为用户消息数,下方为 API 请求数。</p>
|
|
481
|
-
</div>
|
|
482
|
-
<div class="table-wrap">
|
|
483
|
-
<table class="matrix">
|
|
484
|
-
<thead>
|
|
485
|
-
<tr>
|
|
486
|
-
<th class="row-head">personKey</th>
|
|
487
|
-
${headerCells}
|
|
488
|
-
</tr>
|
|
489
|
-
</thead>
|
|
490
|
-
<tbody>${bodyRows}</tbody>
|
|
491
|
-
</table>
|
|
492
|
-
</div>
|
|
493
|
-
</section>
|
|
473
|
+
return `
|
|
474
|
+
<section class="panel table-panel">
|
|
475
|
+
<div class="panel-header">
|
|
476
|
+
<div>
|
|
477
|
+
<p class="eyebrow">Daily Matrix</p>
|
|
478
|
+
<h2>按天 × 人 矩阵</h2>
|
|
479
|
+
</div>
|
|
480
|
+
<p class="muted">单元格上方为用户消息数,下方为 API 请求数。</p>
|
|
481
|
+
</div>
|
|
482
|
+
<div class="table-wrap">
|
|
483
|
+
<table class="matrix">
|
|
484
|
+
<thead>
|
|
485
|
+
<tr>
|
|
486
|
+
<th class="row-head">personKey</th>
|
|
487
|
+
${headerCells}
|
|
488
|
+
</tr>
|
|
489
|
+
</thead>
|
|
490
|
+
<tbody>${bodyRows}</tbody>
|
|
491
|
+
</table>
|
|
492
|
+
</div>
|
|
493
|
+
</section>
|
|
494
494
|
`;
|
|
495
495
|
}
|
|
496
496
|
/** 把 weekly 行也照搬出来,便于看每个人每个 ISO 周的总账。 */
|
|
@@ -505,51 +505,51 @@ function renderWeeklyTable(weeklyRows) {
|
|
|
505
505
|
return left.personKey.localeCompare(right.personKey);
|
|
506
506
|
});
|
|
507
507
|
const rows = sorted
|
|
508
|
-
.map((row) => `
|
|
509
|
-
<tr>
|
|
510
|
-
<td>${escapeHtml(row.week)}</td>
|
|
511
|
-
<td>${escapeHtml(row.personKey)}</td>
|
|
512
|
-
<td>${formatNumber(row.userMessageCount)}</td>
|
|
513
|
-
<td>${formatTokensM(row.inputTokens)}</td>
|
|
514
|
-
<td>${formatTokensM(row.outputTokens)}</td>
|
|
515
|
-
<td>${formatTokensM(row.cacheReadInputTokens)}</td>
|
|
516
|
-
<td>${escapeHtml(statValue(row.fiveHourPeakUsagePct))}</td>
|
|
517
|
-
<td>${escapeHtml(statValue(row.fiveHourLatestUsagePct))}</td>
|
|
518
|
-
<td>${escapeHtml(statValue(row.sevenDayPeakUsagePct))}</td>
|
|
519
|
-
<td>${escapeHtml(statValue(row.sevenDayLatestUsagePct))}</td>
|
|
520
|
-
<td class="muted-col">${formatNumber(row.apiRequestCount)}</td>
|
|
508
|
+
.map((row) => `
|
|
509
|
+
<tr>
|
|
510
|
+
<td>${escapeHtml(row.week)}</td>
|
|
511
|
+
<td>${escapeHtml(row.personKey)}</td>
|
|
512
|
+
<td>${formatNumber(row.userMessageCount)}</td>
|
|
513
|
+
<td>${formatTokensM(row.inputTokens)}</td>
|
|
514
|
+
<td>${formatTokensM(row.outputTokens)}</td>
|
|
515
|
+
<td>${formatTokensM(row.cacheReadInputTokens)}</td>
|
|
516
|
+
<td>${escapeHtml(statValue(row.fiveHourPeakUsagePct))}</td>
|
|
517
|
+
<td>${escapeHtml(statValue(row.fiveHourLatestUsagePct))}</td>
|
|
518
|
+
<td>${escapeHtml(statValue(row.sevenDayPeakUsagePct))}</td>
|
|
519
|
+
<td>${escapeHtml(statValue(row.sevenDayLatestUsagePct))}</td>
|
|
520
|
+
<td class="muted-col">${formatNumber(row.apiRequestCount)}</td>
|
|
521
521
|
</tr>`)
|
|
522
522
|
.join("");
|
|
523
|
-
return `
|
|
524
|
-
<section class="panel table-panel">
|
|
525
|
-
<div class="panel-header">
|
|
526
|
-
<div>
|
|
527
|
-
<p class="eyebrow">Weekly Rollup</p>
|
|
528
|
-
<h2>按周聚合</h2>
|
|
529
|
-
</div>
|
|
530
|
-
<p class="muted">直接来源于每个 bundle 的 weeklySummary。</p>
|
|
531
|
-
</div>
|
|
532
|
-
<div class="table-wrap">
|
|
533
|
-
<table>
|
|
534
|
-
<thead>
|
|
535
|
-
<tr>
|
|
536
|
-
<th>周起始</th>
|
|
537
|
-
<th>personKey</th>
|
|
538
|
-
<th>消息</th>
|
|
539
|
-
<th>Input tokens</th>
|
|
540
|
-
<th>Output tokens</th>
|
|
541
|
-
<th>Cache read tokens</th>
|
|
542
|
-
<th>5h Peak</th>
|
|
543
|
-
<th>5h Latest</th>
|
|
544
|
-
<th>7d Peak</th>
|
|
545
|
-
<th>7d Latest</th>
|
|
546
|
-
<th class="muted-col">API 请求</th>
|
|
547
|
-
</tr>
|
|
548
|
-
</thead>
|
|
549
|
-
<tbody>${rows}</tbody>
|
|
550
|
-
</table>
|
|
551
|
-
</div>
|
|
552
|
-
</section>
|
|
523
|
+
return `
|
|
524
|
+
<section class="panel table-panel">
|
|
525
|
+
<div class="panel-header">
|
|
526
|
+
<div>
|
|
527
|
+
<p class="eyebrow">Weekly Rollup</p>
|
|
528
|
+
<h2>按周聚合</h2>
|
|
529
|
+
</div>
|
|
530
|
+
<p class="muted">直接来源于每个 bundle 的 weeklySummary。</p>
|
|
531
|
+
</div>
|
|
532
|
+
<div class="table-wrap">
|
|
533
|
+
<table>
|
|
534
|
+
<thead>
|
|
535
|
+
<tr>
|
|
536
|
+
<th>周起始</th>
|
|
537
|
+
<th>personKey</th>
|
|
538
|
+
<th>消息</th>
|
|
539
|
+
<th>Input tokens</th>
|
|
540
|
+
<th>Output tokens</th>
|
|
541
|
+
<th>Cache read tokens</th>
|
|
542
|
+
<th>5h Peak</th>
|
|
543
|
+
<th>5h Latest</th>
|
|
544
|
+
<th>7d Peak</th>
|
|
545
|
+
<th>7d Latest</th>
|
|
546
|
+
<th class="muted-col">API 请求</th>
|
|
547
|
+
</tr>
|
|
548
|
+
</thead>
|
|
549
|
+
<tbody>${rows}</tbody>
|
|
550
|
+
</table>
|
|
551
|
+
</div>
|
|
552
|
+
</section>
|
|
553
553
|
`;
|
|
554
554
|
}
|
|
555
555
|
/**
|
|
@@ -566,209 +566,209 @@ function buildAggregateDashboardHtml(detailRows, dailyRows, weeklyRows, generate
|
|
|
566
566
|
const peakUsage = maxOrNull(people.map((person) => person.fiveHourPeakUsagePct));
|
|
567
567
|
const peakSevenDay = maxOrNull(people.map((person) => person.sevenDayPeakUsagePct));
|
|
568
568
|
const rangeLabel = overall.startDate && overall.endDate ? `${overall.startDate} → ${overall.endDate}` : "--";
|
|
569
|
-
return `<!doctype html>
|
|
570
|
-
<html lang="zh-CN">
|
|
571
|
-
<head>
|
|
572
|
-
<meta charset="utf-8" />
|
|
573
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
574
|
-
<title>ccus team dashboard</title>
|
|
575
|
-
<style>
|
|
576
|
-
:root {
|
|
577
|
-
--bg: #0a0d12;
|
|
578
|
-
--panel: rgba(16, 21, 31, 0.84);
|
|
579
|
-
--panel-border: rgba(120, 141, 173, 0.18);
|
|
580
|
-
--text: #ecf3ff;
|
|
581
|
-
--muted: #91a0b8;
|
|
582
|
-
--accent: #5eead4;
|
|
583
|
-
--accent-strong: #22c55e;
|
|
584
|
-
--warning: #f59e0b;
|
|
585
|
-
--grid: rgba(145, 160, 184, 0.15);
|
|
586
|
-
--shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
|
|
587
|
-
}
|
|
588
|
-
* { box-sizing: border-box; }
|
|
589
|
-
body {
|
|
590
|
-
margin: 0;
|
|
591
|
-
font-family: Georgia, "Times New Roman", serif;
|
|
592
|
-
color: var(--text);
|
|
593
|
-
background:
|
|
594
|
-
radial-gradient(circle at top left, rgba(34, 197, 94, 0.18), transparent 30%),
|
|
595
|
-
radial-gradient(circle at top right, rgba(94, 234, 212, 0.16), transparent 28%),
|
|
596
|
-
linear-gradient(160deg, #06080c 0%, #0a0d12 48%, #101520 100%);
|
|
597
|
-
min-height: 100vh;
|
|
598
|
-
}
|
|
599
|
-
.shell {
|
|
600
|
-
max-width: 1240px;
|
|
601
|
-
margin: 0 auto;
|
|
602
|
-
padding: 40px 24px 64px;
|
|
603
|
-
}
|
|
604
|
-
.hero {
|
|
605
|
-
display: grid;
|
|
606
|
-
gap: 16px;
|
|
607
|
-
padding: 28px 0 18px;
|
|
608
|
-
}
|
|
609
|
-
.eyebrow {
|
|
610
|
-
margin: 0 0 8px;
|
|
611
|
-
color: var(--accent);
|
|
612
|
-
text-transform: uppercase;
|
|
613
|
-
letter-spacing: 0.14em;
|
|
614
|
-
font-size: 12px;
|
|
615
|
-
}
|
|
616
|
-
h1, h2, p { margin: 0; }
|
|
617
|
-
h1 {
|
|
618
|
-
font-size: clamp(36px, 5vw, 60px);
|
|
619
|
-
line-height: 0.95;
|
|
620
|
-
font-weight: 600;
|
|
621
|
-
}
|
|
622
|
-
.subtitle {
|
|
623
|
-
max-width: 820px;
|
|
624
|
-
color: var(--muted);
|
|
625
|
-
font-size: 16px;
|
|
626
|
-
line-height: 1.6;
|
|
627
|
-
}
|
|
628
|
-
.hero-meta {
|
|
629
|
-
display: flex;
|
|
630
|
-
flex-wrap: wrap;
|
|
631
|
-
gap: 12px;
|
|
632
|
-
color: var(--muted);
|
|
633
|
-
font-size: 14px;
|
|
634
|
-
}
|
|
635
|
-
.hero-chip {
|
|
636
|
-
padding: 8px 12px;
|
|
637
|
-
border-radius: 999px;
|
|
638
|
-
border: 1px solid var(--panel-border);
|
|
639
|
-
background: rgba(9, 12, 18, 0.56);
|
|
640
|
-
}
|
|
641
|
-
.stats {
|
|
642
|
-
display: grid;
|
|
643
|
-
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
644
|
-
gap: 16px;
|
|
645
|
-
margin: 24px 0 28px;
|
|
646
|
-
}
|
|
647
|
-
.panel {
|
|
648
|
-
background: var(--panel);
|
|
649
|
-
backdrop-filter: blur(18px);
|
|
650
|
-
border: 1px solid var(--panel-border);
|
|
651
|
-
border-radius: 24px;
|
|
652
|
-
box-shadow: var(--shadow);
|
|
653
|
-
}
|
|
654
|
-
.stat-card { padding: 20px; min-height: 128px; }
|
|
655
|
-
.stat-card h2 { font-size: 14px; color: var(--muted); font-weight: 500; }
|
|
656
|
-
.stat-value { margin-top: 16px; font-size: 36px; line-height: 0.95; }
|
|
657
|
-
.stat-note { margin-top: 12px; color: var(--muted); font-size: 13px; }
|
|
658
|
-
.panel-header {
|
|
659
|
-
display: flex;
|
|
660
|
-
align-items: end;
|
|
661
|
-
justify-content: space-between;
|
|
662
|
-
gap: 16px;
|
|
663
|
-
padding: 24px 24px 0;
|
|
664
|
-
}
|
|
665
|
-
.panel-header h2 { font-size: 28px; line-height: 1; }
|
|
666
|
-
.muted { color: var(--muted); font-size: 14px; }
|
|
667
|
-
.chart-panel { padding-bottom: 22px; margin-top: 22px; }
|
|
668
|
-
.chart { width: 100%; height: auto; display: block; padding: 12px 20px 6px; }
|
|
669
|
-
.chart-grid { stroke: var(--grid); stroke-width: 1; }
|
|
670
|
-
.chart-axis { fill: var(--muted); font-size: 11px; }
|
|
671
|
-
.chart-axis-line { stroke: var(--grid); stroke-width: 1; }
|
|
672
|
-
.legend {
|
|
673
|
-
display: flex;
|
|
674
|
-
flex-wrap: wrap;
|
|
675
|
-
gap: 10px 18px;
|
|
676
|
-
padding: 14px 24px 0;
|
|
677
|
-
color: var(--muted);
|
|
678
|
-
font-size: 13px;
|
|
679
|
-
}
|
|
680
|
-
.legend-chip { display: inline-flex; align-items: center; gap: 8px; }
|
|
681
|
-
.legend-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
|
|
682
|
-
.legend-line { display: inline-block; width: 22px; height: 0; border-top-width: 2px; border-top-style: solid; border-top-color: var(--muted); }
|
|
683
|
-
.legend-line-dashed { border-top-style: dashed; }
|
|
684
|
-
.table-panel { margin-top: 22px; }
|
|
685
|
-
.table-wrap { overflow: auto; padding: 16px 20px 22px; }
|
|
686
|
-
table { width: 100%; border-collapse: collapse; min-width: 760px; }
|
|
687
|
-
th, td {
|
|
688
|
-
text-align: left;
|
|
689
|
-
padding: 12px 10px;
|
|
690
|
-
border-bottom: 1px solid rgba(145, 160, 184, 0.12);
|
|
691
|
-
vertical-align: top;
|
|
692
|
-
}
|
|
693
|
-
th {
|
|
694
|
-
color: var(--muted);
|
|
695
|
-
font-size: 12px;
|
|
696
|
-
text-transform: uppercase;
|
|
697
|
-
letter-spacing: 0.08em;
|
|
698
|
-
font-weight: 500;
|
|
699
|
-
}
|
|
700
|
-
td { font-size: 14px; }
|
|
701
|
-
td.rank { color: var(--muted); width: 32px; }
|
|
702
|
-
.muted-col { color: var(--muted); font-size: 12px; }
|
|
703
|
-
.bar-label { fill: var(--text); font-size: 13px; }
|
|
704
|
-
.bar-value { fill: var(--accent); font-size: 13px; font-weight: 600; }
|
|
705
|
-
.bar-track { fill: rgba(145, 160, 184, 0.14); }
|
|
706
|
-
.bar-fill { fill: var(--accent); }
|
|
707
|
-
table.matrix th, table.matrix td { text-align: center; padding: 8px 10px; }
|
|
708
|
-
table.matrix th.row-head { text-align: left; }
|
|
709
|
-
table.matrix .cell-primary { display: block; color: var(--text); }
|
|
710
|
-
table.matrix .cell-secondary { display: block; color: var(--muted); font-size: 11px; }
|
|
711
|
-
table.matrix .muted-cell { color: rgba(145, 160, 184, 0.45); }
|
|
712
|
-
@media (max-width: 720px) {
|
|
713
|
-
.shell { padding-inline: 16px; }
|
|
714
|
-
.panel-header { flex-direction: column; align-items: start; }
|
|
715
|
-
}
|
|
716
|
-
</style>
|
|
717
|
-
</head>
|
|
718
|
-
<body>
|
|
719
|
-
<main class="shell">
|
|
720
|
-
<section class="hero">
|
|
721
|
-
<div>
|
|
722
|
-
<p class="eyebrow">Claude Code · Team Surface</p>
|
|
723
|
-
<h1>ccus team dashboard</h1>
|
|
724
|
-
</div>
|
|
725
|
-
<p class="subtitle">把目录里所有 export bundle 聚合到一起,看团队里每个人的 Claude Code 使用节奏:消息数、API 请求数、token 用量,以及 5 小时与 7 天额度使用率。</p>
|
|
726
|
-
<div class="hero-meta">
|
|
727
|
-
<span class="hero-chip">人数:${overall.personCount}</span>
|
|
728
|
-
<span class="hero-chip">时间范围:${escapeHtml(rangeLabel)}</span>
|
|
729
|
-
<span class="hero-chip">生成时间:${escapeHtml(generatedAt.toISOString())}</span>
|
|
730
|
-
</div>
|
|
731
|
-
</section>
|
|
732
|
-
<section class="stats">
|
|
733
|
-
<article class="panel stat-card">
|
|
734
|
-
<h2>Total user messages</h2>
|
|
735
|
-
<p class="stat-value">${formatNumber(overall.totalUserMessageCount)}</p>
|
|
736
|
-
<p class="stat-note">所有人发给 Claude 的非 meta 消息</p>
|
|
737
|
-
</article>
|
|
738
|
-
<article class="panel stat-card">
|
|
739
|
-
<h2>Total tokens (in+out)</h2>
|
|
740
|
-
<p class="stat-value">${formatTokensM(totalTokens)}</p>
|
|
741
|
-
<p class="stat-note">${formatTokensM(overall.totalInputTokens)} input / ${formatTokensM(overall.totalOutputTokens)} output</p>
|
|
742
|
-
</article>
|
|
743
|
-
<article class="panel stat-card">
|
|
744
|
-
<h2>Cache read tokens</h2>
|
|
745
|
-
<p class="stat-value">${formatTokensM(overall.totalCacheReadInputTokens)}</p>
|
|
746
|
-
<p class="stat-note">来自 assistant usage 的 cache_read_input_tokens</p>
|
|
747
|
-
</article>
|
|
748
|
-
<article class="panel stat-card">
|
|
749
|
-
<h2>Total API requests</h2>
|
|
750
|
-
<p class="stat-value">${formatNumber(overall.totalApiRequestCount)}</p>
|
|
751
|
-
<p class="stat-note">所有人 API 请求数合计(次要参考)</p>
|
|
752
|
-
</article>
|
|
753
|
-
<article class="panel stat-card">
|
|
754
|
-
<h2>Peak 5h usage</h2>
|
|
755
|
-
<p class="stat-value">${escapeHtml((0, time_1.roundNumber)(peakUsage, 1) === null ? "--" : statValue((0, time_1.roundNumber)(peakUsage, 1)))}</p>
|
|
756
|
-
<p class="stat-note">团队内观测到的 5 小时使用率峰值</p>
|
|
757
|
-
</article>
|
|
758
|
-
<article class="panel stat-card">
|
|
759
|
-
<h2>Peak 7d usage</h2>
|
|
760
|
-
<p class="stat-value">${escapeHtml((0, time_1.roundNumber)(peakSevenDay, 1) === null ? "--" : statValue((0, time_1.roundNumber)(peakSevenDay, 1)))}</p>
|
|
761
|
-
<p class="stat-note">团队内观测到的 7 天额度峰值</p>
|
|
762
|
-
</article>
|
|
763
|
-
</section>
|
|
764
|
-
${renderPeopleLeaderboard(people)}
|
|
765
|
-
${renderSevenDayPeakChart(people)}
|
|
766
|
-
${renderFiveHourUsageChart(people, detailRows)}
|
|
767
|
-
${renderDailyUserRequestChart(people, dailyIndex, dateAxis)}
|
|
768
|
-
${renderDailyMatrix(people, dailyIndex, dateAxis)}
|
|
769
|
-
${renderWeeklyTable(weeklyRows)}
|
|
770
|
-
</main>
|
|
771
|
-
</body>
|
|
569
|
+
return `<!doctype html>
|
|
570
|
+
<html lang="zh-CN">
|
|
571
|
+
<head>
|
|
572
|
+
<meta charset="utf-8" />
|
|
573
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
574
|
+
<title>ccus team dashboard</title>
|
|
575
|
+
<style>
|
|
576
|
+
:root {
|
|
577
|
+
--bg: #0a0d12;
|
|
578
|
+
--panel: rgba(16, 21, 31, 0.84);
|
|
579
|
+
--panel-border: rgba(120, 141, 173, 0.18);
|
|
580
|
+
--text: #ecf3ff;
|
|
581
|
+
--muted: #91a0b8;
|
|
582
|
+
--accent: #5eead4;
|
|
583
|
+
--accent-strong: #22c55e;
|
|
584
|
+
--warning: #f59e0b;
|
|
585
|
+
--grid: rgba(145, 160, 184, 0.15);
|
|
586
|
+
--shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
|
|
587
|
+
}
|
|
588
|
+
* { box-sizing: border-box; }
|
|
589
|
+
body {
|
|
590
|
+
margin: 0;
|
|
591
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
592
|
+
color: var(--text);
|
|
593
|
+
background:
|
|
594
|
+
radial-gradient(circle at top left, rgba(34, 197, 94, 0.18), transparent 30%),
|
|
595
|
+
radial-gradient(circle at top right, rgba(94, 234, 212, 0.16), transparent 28%),
|
|
596
|
+
linear-gradient(160deg, #06080c 0%, #0a0d12 48%, #101520 100%);
|
|
597
|
+
min-height: 100vh;
|
|
598
|
+
}
|
|
599
|
+
.shell {
|
|
600
|
+
max-width: 1240px;
|
|
601
|
+
margin: 0 auto;
|
|
602
|
+
padding: 40px 24px 64px;
|
|
603
|
+
}
|
|
604
|
+
.hero {
|
|
605
|
+
display: grid;
|
|
606
|
+
gap: 16px;
|
|
607
|
+
padding: 28px 0 18px;
|
|
608
|
+
}
|
|
609
|
+
.eyebrow {
|
|
610
|
+
margin: 0 0 8px;
|
|
611
|
+
color: var(--accent);
|
|
612
|
+
text-transform: uppercase;
|
|
613
|
+
letter-spacing: 0.14em;
|
|
614
|
+
font-size: 12px;
|
|
615
|
+
}
|
|
616
|
+
h1, h2, p { margin: 0; }
|
|
617
|
+
h1 {
|
|
618
|
+
font-size: clamp(36px, 5vw, 60px);
|
|
619
|
+
line-height: 0.95;
|
|
620
|
+
font-weight: 600;
|
|
621
|
+
}
|
|
622
|
+
.subtitle {
|
|
623
|
+
max-width: 820px;
|
|
624
|
+
color: var(--muted);
|
|
625
|
+
font-size: 16px;
|
|
626
|
+
line-height: 1.6;
|
|
627
|
+
}
|
|
628
|
+
.hero-meta {
|
|
629
|
+
display: flex;
|
|
630
|
+
flex-wrap: wrap;
|
|
631
|
+
gap: 12px;
|
|
632
|
+
color: var(--muted);
|
|
633
|
+
font-size: 14px;
|
|
634
|
+
}
|
|
635
|
+
.hero-chip {
|
|
636
|
+
padding: 8px 12px;
|
|
637
|
+
border-radius: 999px;
|
|
638
|
+
border: 1px solid var(--panel-border);
|
|
639
|
+
background: rgba(9, 12, 18, 0.56);
|
|
640
|
+
}
|
|
641
|
+
.stats {
|
|
642
|
+
display: grid;
|
|
643
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
644
|
+
gap: 16px;
|
|
645
|
+
margin: 24px 0 28px;
|
|
646
|
+
}
|
|
647
|
+
.panel {
|
|
648
|
+
background: var(--panel);
|
|
649
|
+
backdrop-filter: blur(18px);
|
|
650
|
+
border: 1px solid var(--panel-border);
|
|
651
|
+
border-radius: 24px;
|
|
652
|
+
box-shadow: var(--shadow);
|
|
653
|
+
}
|
|
654
|
+
.stat-card { padding: 20px; min-height: 128px; }
|
|
655
|
+
.stat-card h2 { font-size: 14px; color: var(--muted); font-weight: 500; }
|
|
656
|
+
.stat-value { margin-top: 16px; font-size: 36px; line-height: 0.95; }
|
|
657
|
+
.stat-note { margin-top: 12px; color: var(--muted); font-size: 13px; }
|
|
658
|
+
.panel-header {
|
|
659
|
+
display: flex;
|
|
660
|
+
align-items: end;
|
|
661
|
+
justify-content: space-between;
|
|
662
|
+
gap: 16px;
|
|
663
|
+
padding: 24px 24px 0;
|
|
664
|
+
}
|
|
665
|
+
.panel-header h2 { font-size: 28px; line-height: 1; }
|
|
666
|
+
.muted { color: var(--muted); font-size: 14px; }
|
|
667
|
+
.chart-panel { padding-bottom: 22px; margin-top: 22px; }
|
|
668
|
+
.chart { width: 100%; height: auto; display: block; padding: 12px 20px 6px; }
|
|
669
|
+
.chart-grid { stroke: var(--grid); stroke-width: 1; }
|
|
670
|
+
.chart-axis { fill: var(--muted); font-size: 11px; }
|
|
671
|
+
.chart-axis-line { stroke: var(--grid); stroke-width: 1; }
|
|
672
|
+
.legend {
|
|
673
|
+
display: flex;
|
|
674
|
+
flex-wrap: wrap;
|
|
675
|
+
gap: 10px 18px;
|
|
676
|
+
padding: 14px 24px 0;
|
|
677
|
+
color: var(--muted);
|
|
678
|
+
font-size: 13px;
|
|
679
|
+
}
|
|
680
|
+
.legend-chip { display: inline-flex; align-items: center; gap: 8px; }
|
|
681
|
+
.legend-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
|
|
682
|
+
.legend-line { display: inline-block; width: 22px; height: 0; border-top-width: 2px; border-top-style: solid; border-top-color: var(--muted); }
|
|
683
|
+
.legend-line-dashed { border-top-style: dashed; }
|
|
684
|
+
.table-panel { margin-top: 22px; }
|
|
685
|
+
.table-wrap { overflow: auto; padding: 16px 20px 22px; }
|
|
686
|
+
table { width: 100%; border-collapse: collapse; min-width: 760px; }
|
|
687
|
+
th, td {
|
|
688
|
+
text-align: left;
|
|
689
|
+
padding: 12px 10px;
|
|
690
|
+
border-bottom: 1px solid rgba(145, 160, 184, 0.12);
|
|
691
|
+
vertical-align: top;
|
|
692
|
+
}
|
|
693
|
+
th {
|
|
694
|
+
color: var(--muted);
|
|
695
|
+
font-size: 12px;
|
|
696
|
+
text-transform: uppercase;
|
|
697
|
+
letter-spacing: 0.08em;
|
|
698
|
+
font-weight: 500;
|
|
699
|
+
}
|
|
700
|
+
td { font-size: 14px; }
|
|
701
|
+
td.rank { color: var(--muted); width: 32px; }
|
|
702
|
+
.muted-col { color: var(--muted); font-size: 12px; }
|
|
703
|
+
.bar-label { fill: var(--text); font-size: 13px; }
|
|
704
|
+
.bar-value { fill: var(--accent); font-size: 13px; font-weight: 600; }
|
|
705
|
+
.bar-track { fill: rgba(145, 160, 184, 0.14); }
|
|
706
|
+
.bar-fill { fill: var(--accent); }
|
|
707
|
+
table.matrix th, table.matrix td { text-align: center; padding: 8px 10px; }
|
|
708
|
+
table.matrix th.row-head { text-align: left; }
|
|
709
|
+
table.matrix .cell-primary { display: block; color: var(--text); }
|
|
710
|
+
table.matrix .cell-secondary { display: block; color: var(--muted); font-size: 11px; }
|
|
711
|
+
table.matrix .muted-cell { color: rgba(145, 160, 184, 0.45); }
|
|
712
|
+
@media (max-width: 720px) {
|
|
713
|
+
.shell { padding-inline: 16px; }
|
|
714
|
+
.panel-header { flex-direction: column; align-items: start; }
|
|
715
|
+
}
|
|
716
|
+
</style>
|
|
717
|
+
</head>
|
|
718
|
+
<body>
|
|
719
|
+
<main class="shell">
|
|
720
|
+
<section class="hero">
|
|
721
|
+
<div>
|
|
722
|
+
<p class="eyebrow">Claude Code · Team Surface</p>
|
|
723
|
+
<h1>ccus team dashboard</h1>
|
|
724
|
+
</div>
|
|
725
|
+
<p class="subtitle">把目录里所有 export bundle 聚合到一起,看团队里每个人的 Claude Code 使用节奏:消息数、API 请求数、token 用量,以及 5 小时与 7 天额度使用率。</p>
|
|
726
|
+
<div class="hero-meta">
|
|
727
|
+
<span class="hero-chip">人数:${overall.personCount}</span>
|
|
728
|
+
<span class="hero-chip">时间范围:${escapeHtml(rangeLabel)}</span>
|
|
729
|
+
<span class="hero-chip">生成时间:${escapeHtml(generatedAt.toISOString())}</span>
|
|
730
|
+
</div>
|
|
731
|
+
</section>
|
|
732
|
+
<section class="stats">
|
|
733
|
+
<article class="panel stat-card">
|
|
734
|
+
<h2>Total user messages</h2>
|
|
735
|
+
<p class="stat-value">${formatNumber(overall.totalUserMessageCount)}</p>
|
|
736
|
+
<p class="stat-note">所有人发给 Claude 的非 meta 消息</p>
|
|
737
|
+
</article>
|
|
738
|
+
<article class="panel stat-card">
|
|
739
|
+
<h2>Total tokens (in+out)</h2>
|
|
740
|
+
<p class="stat-value">${formatTokensM(totalTokens)}</p>
|
|
741
|
+
<p class="stat-note">${formatTokensM(overall.totalInputTokens)} input / ${formatTokensM(overall.totalOutputTokens)} output</p>
|
|
742
|
+
</article>
|
|
743
|
+
<article class="panel stat-card">
|
|
744
|
+
<h2>Cache read tokens</h2>
|
|
745
|
+
<p class="stat-value">${formatTokensM(overall.totalCacheReadInputTokens)}</p>
|
|
746
|
+
<p class="stat-note">来自 assistant usage 的 cache_read_input_tokens</p>
|
|
747
|
+
</article>
|
|
748
|
+
<article class="panel stat-card">
|
|
749
|
+
<h2>Total API requests</h2>
|
|
750
|
+
<p class="stat-value">${formatNumber(overall.totalApiRequestCount)}</p>
|
|
751
|
+
<p class="stat-note">所有人 API 请求数合计(次要参考)</p>
|
|
752
|
+
</article>
|
|
753
|
+
<article class="panel stat-card">
|
|
754
|
+
<h2>Peak 5h usage</h2>
|
|
755
|
+
<p class="stat-value">${escapeHtml((0, time_1.roundNumber)(peakUsage, 1) === null ? "--" : statValue((0, time_1.roundNumber)(peakUsage, 1)))}</p>
|
|
756
|
+
<p class="stat-note">团队内观测到的 5 小时使用率峰值</p>
|
|
757
|
+
</article>
|
|
758
|
+
<article class="panel stat-card">
|
|
759
|
+
<h2>Peak 7d usage</h2>
|
|
760
|
+
<p class="stat-value">${escapeHtml((0, time_1.roundNumber)(peakSevenDay, 1) === null ? "--" : statValue((0, time_1.roundNumber)(peakSevenDay, 1)))}</p>
|
|
761
|
+
<p class="stat-note">团队内观测到的 7 天额度峰值</p>
|
|
762
|
+
</article>
|
|
763
|
+
</section>
|
|
764
|
+
${renderPeopleLeaderboard(people)}
|
|
765
|
+
${renderSevenDayPeakChart(people)}
|
|
766
|
+
${renderFiveHourUsageChart(people, detailRows)}
|
|
767
|
+
${renderDailyUserRequestChart(people, dailyIndex, dateAxis)}
|
|
768
|
+
${renderDailyMatrix(people, dailyIndex, dateAxis)}
|
|
769
|
+
${renderWeeklyTable(weeklyRows)}
|
|
770
|
+
</main>
|
|
771
|
+
</body>
|
|
772
772
|
</html>`;
|
|
773
773
|
}
|
|
774
774
|
//# sourceMappingURL=aggregate-dashboard.js.map
|