Extract dashboard-usage-rows.ts (UsageRow, formatResetRemaining, mergeClaudeDashboardAccounts, buildClaudeUsageRows, extractCodexUsageRows) and codex-usage-collector.ts (fetchCodexUsage, applyCodexUsageToAccount, buildCodexUsageRowsFromState, refresh functions returning data instead of mutating module state). unified-dashboard.ts drops from 926 to 563 lines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
import { getClaudeProfile, type ClaudeAccountUsage } from './claude-usage.js';
|
|
import type { StatusSnapshot } from './status-dashboard.js';
|
|
|
|
export type UsageRow = {
|
|
name: string;
|
|
h5pct: number;
|
|
h5reset: string;
|
|
d7pct: number;
|
|
d7reset: string;
|
|
};
|
|
|
|
export function formatResetRemaining(value: string | number): string {
|
|
if (value === '' || value == null) return '';
|
|
try {
|
|
const date =
|
|
typeof value === 'number' ? new Date(value * 1000) : new Date(value);
|
|
if (Number.isNaN(date.getTime())) return '';
|
|
const diffMs = date.getTime() - Date.now();
|
|
if (diffMs <= 0) return ' reset';
|
|
const hours = Math.floor(diffMs / 3_600_000);
|
|
const minutes = Math.floor((diffMs % 3_600_000) / 60_000);
|
|
if (hours >= 24) {
|
|
const days = Math.floor(hours / 24);
|
|
const remH = hours % 24;
|
|
return `${String(days).padStart(2)}d ${String(remH).padStart(2)}h`;
|
|
}
|
|
return `${String(hours).padStart(2)}h ${String(minutes).padStart(2)}m`;
|
|
} catch {
|
|
return String(value).padStart(6);
|
|
}
|
|
}
|
|
|
|
export function mergeClaudeDashboardAccounts(
|
|
liveAccounts: ClaudeAccountUsage[] | null | undefined,
|
|
cachedAccounts: ClaudeAccountUsage[],
|
|
): ClaudeAccountUsage[] {
|
|
if (!liveAccounts) return cachedAccounts;
|
|
|
|
const cachedByIndex = new Map(
|
|
cachedAccounts.map((account) => [account.index, account]),
|
|
);
|
|
|
|
return liveAccounts.map((account) => ({
|
|
...account,
|
|
usage: account.usage || cachedByIndex.get(account.index)?.usage || null,
|
|
}));
|
|
}
|
|
|
|
export function buildClaudeUsageRows(
|
|
claudeAccounts: ClaudeAccountUsage[],
|
|
): UsageRow[] {
|
|
const isMultiAccount = claudeAccounts.length > 1;
|
|
|
|
return claudeAccounts.map((account) => {
|
|
const usage = account.usage;
|
|
const h5 = usage?.five_hour;
|
|
const d7 = usage?.seven_day;
|
|
const profile = getClaudeProfile(account.index);
|
|
const planSuffix = profile ? ` ${profile.planType}` : '';
|
|
const label = isMultiAccount
|
|
? `Claude${account.index + 1}${account.isActive ? '*' : ''}${account.isRateLimited ? '!' : ''}${planSuffix}`
|
|
: `Claude${account.isActive ? '*' : ''}${account.isRateLimited ? '!' : ''}${planSuffix}`;
|
|
|
|
return {
|
|
name: label,
|
|
h5pct: h5
|
|
? h5.utilization > 1
|
|
? Math.round(h5.utilization)
|
|
: Math.round(h5.utilization * 100)
|
|
: -1,
|
|
h5reset: h5 ? formatResetRemaining(h5.resets_at) : '',
|
|
d7pct: d7
|
|
? d7.utilization > 1
|
|
? Math.round(d7.utilization)
|
|
: Math.round(d7.utilization * 100)
|
|
: -1,
|
|
d7reset: d7 ? formatResetRemaining(d7.resets_at) : '',
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Extract Codex usage rows from a snapshot, applying staleness check.
|
|
* Returns real rows if usageRowsFetchedAt is within maxAgeMs, otherwise a
|
|
* single degraded row. Returns empty array if no usage data present.
|
|
*/
|
|
export function extractCodexUsageRows(
|
|
snapshot: StatusSnapshot | undefined,
|
|
maxAgeMs: number,
|
|
now: number = Date.now(),
|
|
): UsageRow[] {
|
|
if (!snapshot?.usageRows || snapshot.usageRows.length === 0) return [];
|
|
|
|
const fetchedAt = snapshot.usageRowsFetchedAt
|
|
? new Date(snapshot.usageRowsFetchedAt).getTime()
|
|
: 0;
|
|
const usageAge = now - fetchedAt;
|
|
if (usageAge <= maxAgeMs) {
|
|
return [...snapshot.usageRows];
|
|
}
|
|
// Usage data is stale — return degraded indicator
|
|
return [{ name: 'Codex', h5pct: -1, h5reset: '', d7pct: -1, d7reset: '' }];
|
|
}
|