fix(dashboard): make quota windows first-class (#28)

This commit is contained in:
Eyejoker
2026-04-27 01:10:40 +09:00
committed by GitHub
parent 4261c7f875
commit 635f9851bc
2 changed files with 76 additions and 89 deletions

View File

@@ -140,12 +140,6 @@ function usagePeak(row: UsageRow): number {
return Math.max(row.h5pct, row.d7pct); return Math.max(row.h5pct, row.d7pct);
} }
function usageRemaining(row: UsageRow): number | null {
const peak = usagePeak(row);
if (peak < 0) return null;
return Math.max(0, 100 - peak);
}
function usageLimitWindow(row: UsageRow): UsageLimitWindow { function usageLimitWindow(row: UsageRow): UsageLimitWindow {
return row.d7pct >= row.h5pct ? 'd7' : 'h5'; return row.d7pct >= row.h5pct ? 'd7' : 'h5';
} }
@@ -187,8 +181,8 @@ function usageNameParts(row: UsageRow): {
return { account: cleaned, plan: null }; return { account: cleaned, plan: null };
} }
function usageLimitReset(row: UsageRow): string { function usageWindowReset(row: UsageRow, window: UsageLimitWindow): string {
return (usageLimitWindow(row) === 'd7' ? row.d7reset : row.h5reset).trim(); return (window === 'd7' ? row.d7reset : row.h5reset).trim();
} }
function usageBurnRate(row: UsageRow): number | null { function usageBurnRate(row: UsageRow): number | null {
@@ -929,50 +923,36 @@ function RoomPanel({
); );
} }
function UsageRemainingMeter({ function UsageQuotaMeter({
row, row,
rowName, rowName,
window,
t, t,
}: { }: {
row: UsageRow; row: UsageRow;
rowName: string; rowName: string;
window: UsageLimitWindow;
t: Messages; t: Messages;
}) { }) {
const remaining = usageRemaining(row); const remaining = usageWindowRemaining(row, window);
const window = usageLimitWindow(row); const reset = usageWindowReset(row, window);
const h5Remaining = usageWindowRemaining(row, 'h5'); const tightest = usageLimitWindow(row) === window;
const d7Remaining = usageWindowRemaining(row, 'd7'); const label = t.usage.quota[window];
const reset = usageLimitReset(row);
return ( return (
<div className="usage-remaining"> <div className={`usage-quota ${tightest ? 'usage-quota-tight' : ''}`}>
<div> <div>
<span>{t.usage.remaining}</span> <span>{label}</span>
<strong>{remaining === null ? '-' : formatPct(remaining)}</strong> <strong>{remaining === null ? '-' : formatPct(remaining)}</strong>
</div> </div>
<progress <progress
aria-label={`${rowName} ${t.usage.remaining} ${ aria-label={`${rowName} ${label} ${
remaining === null ? '-' : formatPct(remaining) remaining === null ? '-' : formatPct(remaining)
}`} }`}
max={100} max={100}
value={remaining ?? 0} value={remaining ?? 0}
/> />
<div className="quota-pair" aria-label={`${rowName} quota windows`}> <small>{reset ? `${t.usage.reset} ${reset}` : t.usage.noReset}</small>
<span className={window === 'h5' ? 'is-tight' : undefined}>
<b>{t.usage.quota.h5}</b>
<em>{h5Remaining === null ? '-' : formatPct(h5Remaining)}</em>
<progress max={100} value={h5Remaining ?? 0} />
</span>
<span className={window === 'd7' ? 'is-tight' : undefined}>
<b>{t.usage.quota.d7}</b>
<em>{d7Remaining === null ? '-' : formatPct(d7Remaining)}</em>
<progress max={100} value={d7Remaining ?? 0} />
</span>
</div>
<small>
{remaining === null ? '-' : t.usage.limitBasis[window]}
{reset ? ` · ${t.usage.reset} ${reset}` : ` · ${t.usage.noReset}`}
</small>
</div> </div>
); );
} }
@@ -1017,8 +997,13 @@ function UsagePanel({
const focusValue = focusRows const focusValue = focusRows
.map((row) => { .map((row) => {
const { account } = usageNameParts(row); const { account } = usageNameParts(row);
const remaining = usageRemaining(row); const h5Remaining = usageWindowRemaining(row, 'h5');
return `${account} ${remaining === null ? '-' : formatPct(remaining)}`; const d7Remaining = usageWindowRemaining(row, 'd7');
return `${account} ${t.usage.quota.h5} ${
h5Remaining === null ? '-' : formatPct(h5Remaining)
} · ${t.usage.quota.d7} ${
d7Remaining === null ? '-' : formatPct(d7Remaining)
}`;
}) })
.join(' · '); .join(' · ');
const groups = [ const groups = [
@@ -1050,7 +1035,8 @@ function UsagePanel({
<div className="usage-matrix" role="table" aria-label={t.panels.usage}> <div className="usage-matrix" role="table" aria-label={t.panels.usage}>
<div className="usage-matrix-head" role="row"> <div className="usage-matrix-head" role="row">
<span>{t.usage.usage}</span> <span>{t.usage.usage}</span>
<span>{t.usage.remaining}</span> <span>{t.usage.quota.h5}</span>
<span>{t.usage.quota.d7}</span>
<span>{t.usage.speed}</span> <span>{t.usage.speed}</span>
</div> </div>
{groups.map((group) => ( {groups.map((group) => (
@@ -1077,7 +1063,18 @@ function UsagePanel({
) : null} ) : null}
</div> </div>
</div> </div>
<UsageRemainingMeter row={row} rowName={account} t={t} /> <UsageQuotaMeter
row={row}
rowName={account}
window="h5"
t={t}
/>
<UsageQuotaMeter
row={row}
rowName={account}
window="d7"
t={t}
/>
<UsageSpeed row={row} t={t} /> <UsageSpeed row={row} t={t} />
</section> </section>
); );

View File

@@ -622,7 +622,7 @@ td small {
} }
.usage-summary span, .usage-summary span,
.usage-remaining small { .usage-quota small {
color: var(--muted); color: var(--muted);
} }
@@ -642,10 +642,9 @@ td small {
.usage-matrix-head, .usage-matrix-head,
.usage-row { .usage-row {
display: grid; display: grid;
grid-template-columns: minmax(132px, 0.78fr) minmax(0, 1fr) minmax( grid-template-columns:
96px, minmax(132px, 0.82fr) minmax(82px, 0.42fr) minmax(82px, 0.42fr)
0.46fr minmax(84px, 0.34fr);
);
gap: 8px; gap: 8px;
align-items: center; align-items: center;
} }
@@ -693,7 +692,7 @@ td small {
} }
.usage-account, .usage-account,
.usage-remaining div { .usage-quota div {
display: flex; display: flex;
min-width: 0; min-width: 0;
gap: 8px; gap: 8px;
@@ -723,7 +722,7 @@ td small {
white-space: nowrap; white-space: nowrap;
} }
.usage-remaining span, .usage-quota span,
.usage-speed span { .usage-speed span {
color: var(--muted); color: var(--muted);
font-size: 11px; font-size: 11px;
@@ -732,57 +731,42 @@ td small {
text-transform: uppercase; text-transform: uppercase;
} }
.usage-remaining, .usage-quota,
.usage-speed { .usage-speed {
display: grid; display: grid;
min-width: 0; min-width: 0;
gap: 5px; gap: 5px;
} }
.quota-pair { .usage-quota {
display: grid; padding: 6px;
grid-template-columns: repeat(2, minmax(0, 1fr)); border-radius: 13px;
gap: 5px; background: rgba(43, 55, 38, 0.035);
} }
.quota-pair span { .usage-quota-tight {
display: grid; background: rgba(191, 95, 44, 0.1);
min-width: 0;
gap: 3px;
padding: 5px;
border-radius: 11px;
background: rgba(43, 55, 38, 0.045);
}
.quota-pair span.is-tight {
background: rgba(191, 95, 44, 0.11);
box-shadow: inset 0 0 0 1px rgba(191, 95, 44, 0.16); box-shadow: inset 0 0 0 1px rgba(191, 95, 44, 0.16);
} }
.quota-pair b, .usage-quota strong {
.quota-pair em { font-size: 17px;
line-height: 1;
}
.usage-quota small {
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
color: var(--muted);
font-size: 10px; font-size: 10px;
font-style: normal;
font-weight: 900;
line-height: 1;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.quota-pair b {
color: var(--muted);
}
.quota-pair progress {
height: 5px;
}
.usage-speed { .usage-speed {
min-height: 54px; min-height: 48px;
align-content: center; align-content: center;
padding: 8px; padding: 6px;
border-radius: 14px; border-radius: 14px;
background: rgba(43, 55, 38, 0.045); background: rgba(43, 55, 38, 0.045);
} }
@@ -1603,8 +1587,10 @@ progress::-moz-progress-bar {
} }
.usage-row { .usage-row {
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr) minmax(90px, 0.5fr); grid-template-columns:
gap: 6px; minmax(86px, 0.86fr) minmax(62px, 0.54fr) minmax(62px, 0.54fr)
minmax(58px, 0.4fr);
gap: 5px;
padding: 9px; padding: 9px;
} }
@@ -1613,30 +1599,26 @@ progress::-moz-progress-bar {
gap: 5px; gap: 5px;
} }
.usage-remaining, .usage-quota,
.usage-speed { .usage-speed {
gap: 4px; gap: 4px;
} }
.usage-remaining div { .usage-quota {
padding: 5px;
}
.usage-quota div {
gap: 6px; gap: 6px;
} }
.usage-remaining small { .usage-quota small {
overflow: hidden; overflow: hidden;
font-size: 11px; font-size: 10px;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.quota-pair {
gap: 4px;
}
.quota-pair span {
padding: 4px;
}
.record-card-grid { .record-card-grid {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
@@ -1732,6 +1714,14 @@ progress::-moz-progress-bar {
grid-column: 1 / -1; grid-column: 1 / -1;
} }
.usage-speed {
grid-column: 1 / -1;
display: flex;
min-height: auto;
align-items: center;
justify-content: space-between;
}
.skeleton-grid, .skeleton-grid,
.ops-strip { .ops-strip {
gap: 8px; gap: 8px;