mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-13 20:12:57 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
771506a58d |
No files matched your search
@@ -19,7 +19,7 @@
|
||||
|
||||
import { formatCount, formatCredits, formatDollarsFromMicrocents, usageIsCredits } from './credits.js';
|
||||
import { isOrgSeat } from './orgSeat.js';
|
||||
import { addonCreditsRemaining, meterPercent, monthlyUsageOrThrow, usageBudget } from './usageBudget.js';
|
||||
import { addonCreditsBudget, addonCreditsRemaining, meterPercent, monthlyUsageOrThrow, usageBudget } from './usageBudget.js';
|
||||
import { getMonthlyUsage, getStorageSpace } from './usageRequests.js';
|
||||
|
||||
// Whether the server reported credits — decides whether the usage surfaces
|
||||
@@ -77,10 +77,23 @@ const TabUsage = {
|
||||
<div class="usage-progbar-wrapper">
|
||||
<div class="usage-progbar" style="width: 0;"></div>
|
||||
</div>
|
||||
<p class="usage-addon-credits" style="display: none;">
|
||||
<span class="usage-addon-left"></span>
|
||||
<span class="usage-addon-note">· ${i18n('addon_credits_note')}</span>
|
||||
</p>
|
||||
<div class="usage-addon" style="display: none;">
|
||||
<div class="driver-usage-header">
|
||||
<h3 id="usage-addon-title" style="margin:0; font-size: 14px; flex-grow: 1; font-weight: 500;">${i18n('addon_credits')}</h3>
|
||||
<div style="font-size: 13px; margin-bottom: 3px; opacity:0.85;">
|
||||
<span class="usage-addon-percent">--%</span>
|
||||
<span> · </span>
|
||||
${i18n('used_of', { used: '<span class="usage-addon-used">--</span>', available: '<span class="usage-addon-capacity">--</span>' }, false)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="usage-progbar-wrapper" role="progressbar" aria-labelledby="usage-addon-title" aria-valuemin="0" aria-valuemax="100">
|
||||
<div class="usage-addon-bar" style="width: 0;"></div>
|
||||
</div>
|
||||
<p class="usage-addon-credits">
|
||||
<span class="usage-addon-left"></span>
|
||||
<span class="usage-addon-note">· ${i18n('addon_credits_note')}</span>
|
||||
</p>
|
||||
</div>
|
||||
<h3 style="margin:15px 0 10px 0; font-size: 14px; font-weight: 500;">${i18n('usage_details')}</h3>
|
||||
<div class="driver-usage-details-content visible">
|
||||
</div>
|
||||
@@ -330,8 +343,20 @@ async function update_usage_details ($el_window) {
|
||||
'background-color': window.usage_bar_color(budget.barPercent),
|
||||
});
|
||||
|
||||
$('.usage-addon-credits').toggle(addonLeft !== null);
|
||||
if ( addonLeft !== null ) {
|
||||
// Its own meter under the monthly one, for an account that has ever had any.
|
||||
const addon = addonCreditsBudget(res.allowanceInfo);
|
||||
$('.usage-addon').toggle(addon !== null);
|
||||
if ( addon !== null ) {
|
||||
$('.usage-addon-used').html(amount(addon.used));
|
||||
$('.usage-addon-capacity').html(amount(addon.capacity));
|
||||
$('.usage-addon-percent').html(`${addon.percent}%`);
|
||||
$('.usage-addon-bar').css({
|
||||
width: `${addon.barPercent}%`,
|
||||
'background-color': window.usage_bar_color(addon.barPercent),
|
||||
}).parent().attr({
|
||||
'aria-valuenow': addon.percent,
|
||||
'aria-valuetext': `${addon.percent}%`,
|
||||
});
|
||||
$('.usage-addon-left').html(i18n('addon_credits_available', { amount: amount(addonLeft) }));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ export const meterPercent = (share) => (share < 100 ? Math.min(99, Math.round(sh
|
||||
|
||||
/**
|
||||
* @typedef {Object} UsageBudget
|
||||
* @property {number} used - Allowance-charged spend, in the server's units.
|
||||
* @property {number} capacity - The monthly plan allowance.
|
||||
* @property {number} used - Spend charged to it, in the server's units.
|
||||
* @property {number} capacity - What the meter is out of: the monthly plan allowance, or the add-on credit bought.
|
||||
* @property {number} percent - `used` as a whole-number share of `capacity`.
|
||||
* @property {number} barPercent - The share clamped to 0-100, for a bar width.
|
||||
*/
|
||||
@@ -83,6 +83,28 @@ export const addonCreditsRemaining = (allowanceInfo) => {
|
||||
return Math.max(0, purchased - nonNegative(addons.consumedPurchaseCredits));
|
||||
};
|
||||
|
||||
/**
|
||||
* The add-on credit meter: what has been spent of all the add-on credit the
|
||||
* account ever bought. Null when it has never had any, as there is no pool
|
||||
* to meter.
|
||||
*
|
||||
* @param {Object | null | undefined} allowanceInfo
|
||||
* @returns {UsageBudget | null}
|
||||
*/
|
||||
export const addonCreditsBudget = (allowanceInfo) => {
|
||||
const left = addonCreditsRemaining(allowanceInfo);
|
||||
if ( left === null ) return null;
|
||||
const capacity = nonNegative(allowanceInfo.addons.purchasedCredits);
|
||||
const used = capacity - left;
|
||||
const share = (used / capacity) * 100;
|
||||
return {
|
||||
used,
|
||||
capacity,
|
||||
percent: meterPercent(share),
|
||||
barPercent: Math.max(0, Math.min(100, share)),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* `getMonthlyUsage()` resolves with whatever body the server answered, an
|
||||
* error included. Throws unless that body is a usage report, so a failed
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { addonCreditsRemaining, meterPercent, monthlyUsageOrThrow, usageBudget } from './usageBudget.js';
|
||||
import { addonCreditsBudget, addonCreditsRemaining, meterPercent, monthlyUsageOrThrow, usageBudget } from './usageBudget.js';
|
||||
|
||||
const info = (allowance, addons = {}) => ({
|
||||
monthUsageAllowance: allowance,
|
||||
@@ -148,6 +148,41 @@ describe('addonCreditsRemaining', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('addonCreditsBudget', () => {
|
||||
it('has no meter for an account that never had add-on credit', () => {
|
||||
expect(addonCreditsBudget(info(900_000_000))).toBeNull();
|
||||
expect(addonCreditsBudget(info(900_000_000, { purchasedCredits: 0 }))).toBeNull();
|
||||
expect(addonCreditsBudget(undefined)).toBeNull();
|
||||
});
|
||||
|
||||
it('meters what was spent of the credit bought', () => {
|
||||
expect(addonCreditsBudget(info(900_000_000, {
|
||||
purchasedCredits: 2_500,
|
||||
consumedPurchaseCredits: 850,
|
||||
}))).toEqual({ used: 850, capacity: 2_500, percent: 34, barPercent: 34 });
|
||||
});
|
||||
|
||||
it('starts empty when nothing has been spent, or the spend is unreadable', () => {
|
||||
expect(addonCreditsBudget(info(0, { purchasedCredits: 1_000 })))
|
||||
.toEqual({ used: 0, capacity: 1_000, percent: 0, barPercent: 0 });
|
||||
expect(addonCreditsBudget(info(0, { purchasedCredits: 1_000, consumedPurchaseCredits: NaN })).used).toBe(0);
|
||||
});
|
||||
|
||||
it('reads full, never past it, once the pool is spent', () => {
|
||||
expect(addonCreditsBudget(info(0, {
|
||||
purchasedCredits: 1_000,
|
||||
consumedPurchaseCredits: 1_200,
|
||||
}))).toEqual({ used: 1_000, capacity: 1_000, percent: 100, barPercent: 100 });
|
||||
});
|
||||
|
||||
it('holds at 99% while any credit is left', () => {
|
||||
expect(addonCreditsBudget(info(0, {
|
||||
purchasedCredits: 100_000,
|
||||
consumedPurchaseCredits: 99_999,
|
||||
})).percent).toBe(99);
|
||||
});
|
||||
});
|
||||
|
||||
describe('monthlyUsageOrThrow', () => {
|
||||
it('passes a usage report through', () => {
|
||||
const res = { usage: { total: 0 }, allowanceInfo: null };
|
||||
|
||||
@@ -5037,17 +5037,21 @@ fieldset[name=number-code] {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
#storage-bar, #storage-bar-host, .usage-progbar {
|
||||
#storage-bar, #storage-bar-host, .usage-progbar, .usage-addon-bar {
|
||||
height: 100%;
|
||||
background-color: var(--dashboard-bento-usage-bar-fill, #ea580c);
|
||||
width: 0;
|
||||
transition: width 0.4s ease, background-color 0.4s ease;
|
||||
}
|
||||
|
||||
#storage-used-percent, .usage-progbar-percent {
|
||||
#storage-used-percent, .usage-progbar-percent, .usage-addon-percent {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.usage-addon {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.usage-addon-credits {
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -28,6 +28,7 @@ const en = {
|
||||
account_password: 'Verify Account Password',
|
||||
access_granted_to: 'Access Granted To',
|
||||
add_another_account: 'Add Another Account',
|
||||
addon_credits: 'Add-on credits',
|
||||
addon_credits_available: '+ {{amount}} add-on credits left',
|
||||
addon_credits_note: 'Used once your monthly credits run out.',
|
||||
add_app: 'Add an app',
|
||||
|
||||
Reference in new issue
Block a user