mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-08-29 00:41:25 +08:00
fix(quota): clear usage after reported reset
This commit is contained in:
@@ -350,24 +350,20 @@ function Lane({ lane, span, now, mode, cells, nowPercent, resolvedTheme }: LaneP
|
||||
title={`${lane.displayName}\n${formatDay(window.startMs)} ${formatTime(
|
||||
window.startMs
|
||||
)} → ${formatDay(window.endMs)} ${formatTime(window.endMs)}${
|
||||
window.state === 'live' && lane.remaining !== null
|
||||
? `\n${lane.remaining}% remaining`
|
||||
: ''
|
||||
window.remaining !== null ? `\n${window.remaining}% remaining` : ''
|
||||
}`}
|
||||
>
|
||||
{/* Consumed portion of the *current* window only — a past or
|
||||
future window has no meaningful fill. */}
|
||||
{window.state === 'live' && lane.remaining !== null && (
|
||||
{/* Only the API-reported current window has meaningful usage;
|
||||
projected windows intentionally have no fill. */}
|
||||
{window.remaining !== null && (
|
||||
<span
|
||||
className={styles.windowFill}
|
||||
style={{ width: `${100 - lane.remaining}%` }}
|
||||
style={{ width: `${100 - window.remaining}%` }}
|
||||
/>
|
||||
)}
|
||||
{showLabel && (
|
||||
<span className={styles.windowLabel}>
|
||||
{window.state === 'live' && lane.remaining !== null
|
||||
? `${lane.remaining}% · `
|
||||
: ''}
|
||||
{window.remaining !== null ? `${window.remaining}% · ` : ''}
|
||||
{endText}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface TimelineLane {
|
||||
anchorMs: number | null;
|
||||
/** Window length in hours. */
|
||||
periodHours: number | null;
|
||||
/** Remaining percent for the window being drawn, or null when unknown. */
|
||||
/** Remaining percent reported for the window ending at `anchorMs`. */
|
||||
remaining: number | null;
|
||||
limits: TimelineLimit[];
|
||||
}
|
||||
@@ -55,6 +55,8 @@ export interface TimelineWindow {
|
||||
leftPercent: number;
|
||||
widthPercent: number;
|
||||
state: 'past' | 'live' | 'next';
|
||||
/** Remaining percent only when this is the API-reported current window. */
|
||||
remaining: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,6 +166,9 @@ export function projectLane(
|
||||
leftPercent: left,
|
||||
widthPercent: right - left,
|
||||
state,
|
||||
// `lane.remaining` belongs to the current payload window ending at the
|
||||
// anchor. Once that reset passes, projected windows must not reuse it.
|
||||
remaining: state === 'live' && window.endMs === lane.anchorMs ? lane.remaining : null,
|
||||
};
|
||||
})
|
||||
.filter((window): window is TimelineWindow => window !== null);
|
||||
|
||||
@@ -121,10 +121,21 @@ describe('projectLane', () => {
|
||||
expect(live.length).toBe(1);
|
||||
expect(live[0].startMs).toBeLessThanOrEqual(now);
|
||||
expect(live[0].endMs).toBeGreaterThan(now);
|
||||
expect(live[0].remaining).toBe(40);
|
||||
expect(windows.filter((w) => w.state === 'past').every((w) => w.endMs <= now)).toBe(true);
|
||||
expect(windows.filter((w) => w.state === 'next').every((w) => w.startMs > now)).toBe(true);
|
||||
});
|
||||
|
||||
test('does not carry stale remaining usage past the reported reset', () => {
|
||||
const nowAfterReset = at(2026, 6, 30, 12);
|
||||
const windows = projectLane(lane(), span.startMs, span.endMs, nowAfterReset, 'weekly');
|
||||
const live = windows.find((window) => window.state === 'live');
|
||||
|
||||
expect(live).toBeDefined();
|
||||
expect(live?.startMs).toBe(at(2026, 6, 29, 20));
|
||||
expect(live?.remaining).toBeNull();
|
||||
});
|
||||
|
||||
test('clips bars to the visible span', () => {
|
||||
const windows = projectLane(lane(), span.startMs, span.endMs, at(2026, 6, 29, 12), 'weekly');
|
||||
for (const window of windows) {
|
||||
|
||||
Reference in New Issue
Block a user