Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/components/config/ProviderModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface ProviderModalProps {
onCancel: () => void;
}

const BYTES_PER_GB = 1_073_741_824;
const BYTES_PER_GB = 1_000_000_000;

const defaultFormData: ProviderFormData = {
name: "",
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/system/ProviderChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,14 @@ export function ProviderChart() {
</div>
);

const formatDecimalBytes = (v: number) => formatBytes(v, 2, false, true);

return (
<ProviderAreaChart
icon={BarChart3}
iconClassName="text-info"
title="Data Usage Trends"
subtitle={`Total volume: ${formatBytes(totalUsage)} in the last ${days} days`}
subtitle={`Total volume: ${formatDecimalBytes(totalUsage)} in the last ${days} days`}
tabs={TABS}
days={days}
onDaysChange={setDays}
Expand All @@ -112,9 +114,9 @@ export function ProviderChart() {
providers={providers}
colorMap={colorMap}
gradientPrefix="color"
formatValue={formatBytes}
formatValue={formatDecimalBytes}
tooltipTotalClassName="text-info"
yAxisTickFormatter={formatBytes}
yAxisTickFormatter={formatDecimalBytes}
/>
);
}
41 changes: 27 additions & 14 deletions frontend/src/components/system/ProviderStatusTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useProviders } from "../../hooks/useProviders";
import {
formatBytes,
formatExpirationDate,
formatFutureTime,
formatRelativeTime,
getProviderBrandName,
} from "../../lib/utils";
Expand Down Expand Up @@ -476,20 +477,32 @@ export function ProviderStatusTable({
)}
</td>
<td>
{provider.byte_count > 0 ? (
<div className="flex min-w-[80px] flex-col">
<span className="font-bold font-mono text-base-content/80 text-xs">
{formatBytes(provider.byte_count)}
</span>
<span className="font-mono text-[9px] text-base-content/40">
{formatBytes(provider.byte_count_24h)} / 24h
</span>
</div>
) : (
<span className="min-w-[80px] font-mono text-base-content/30 text-xs">
-
</span>
)}
<div className="flex min-w-[80px] flex-col">
{provider.byte_count > 0 ? (
<>
<span className="font-bold font-mono text-base-content/80 text-xs">
{formatBytes(provider.byte_count, 2, false, true)}
</span>
<span className="font-mono text-[9px] text-base-content/40">
{formatBytes(provider.byte_count_24h, 2, false, true)} / 24h
</span>
</>
) : (
<span className="font-mono text-base-content/30 text-xs">-</span>
)}
{hasQuota && (
<div className="mt-1.5 border-base-200/60 border-t pt-1 text-[10px]">
<div className="font-semibold font-mono text-warning">
Quota: {formatBytes(provider.quota_used || 0, 2, false, true)} / {formatBytes(provider.quota_bytes || 0, 2, false, true)}
</div>
{provider.quota_reset_at && (
<div className="mt-0.5 font-mono text-base-content/40">
Resets: {formatFutureTime(provider.quota_reset_at)}
</div>
)}
</div>
)}
</div>
</td>
<td>
{(() => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ export async function copyToClipboard(text: string): Promise<boolean> {
}
}

export function formatBytes(bytes: number, decimals = 2, shortUnit = false) {
export function formatBytes(bytes: number, decimals = 2, shortUnit = false, useDecimal = false) {
const sizes = shortUnit
? ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
: ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];

if (bytes === 0) return `0 ${sizes[0]}`;

const k = 1024;
const k = useDecimal ? 1000 : 1024;
const dm = decimals < 0 ? 0 : decimals;

const i = Math.floor(Math.log(bytes) / Math.log(k));
Expand Down
34 changes: 27 additions & 7 deletions internal/pool/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,29 @@ func (m *manager) injectQuotaState(providers []nntppool.Provider) {
for i := range providers {
name := providerPoolName(providers[i])

if used, ok := quotaUsed[name]; ok && used > 0 {
providers[i].QuotaUsed = used
}
hasResetTime := false
var resetTime time.Time
if resetNano, ok := quotaResetAt[name]; ok && resetNano > 0 {
t := time.Unix(0, resetNano)
if t.After(time.Now()) {
providers[i].QuotaResetAt = t
resetTime = time.Unix(0, resetNano)
hasResetTime = true
}

if hasResetTime {
if resetTime.After(time.Now()) {
// The quota period is still active: restore the used bytes and the reset time
if used, ok := quotaUsed[name]; ok && used > 0 {
providers[i].QuotaUsed = used
}
providers[i].QuotaResetAt = resetTime
} else {
// The quota period has already expired: discard the old usage (it resets to 0)
providers[i].QuotaUsed = 0
providers[i].QuotaResetAt = time.Time{}
}
} else {
// No persisted reset time: restore whatever usage we have (fallback)
if used, ok := quotaUsed[name]; ok && used > 0 {
providers[i].QuotaUsed = used
}
}
}
Expand Down Expand Up @@ -567,7 +583,11 @@ func (m *manager) checkAndResetExpiredQuotas(ctx context.Context) {

now := time.Now()
for _, ps := range m.pool.Stats().Providers {
if ps.QuotaBytes == 0 || !ps.QuotaExceeded {
// Skip providers with no quota configured or no tracked usage to reset.
// Without the QuotaUsed guard, providers that simply have a quota period
// configured but have never downloaded anything would trigger a spurious
// reset + DB write on every tick once their reset time expires.
if ps.QuotaBytes == 0 || ps.QuotaUsed == 0 {
continue
}
if ps.QuotaResetAt.IsZero() || !ps.QuotaResetAt.Before(now) {
Expand Down
Loading