+
•
{resetWord} in {countdown}
diff --git a/src/app/(dashboard)/dashboard/usage/components/SystemQuotaOverview.js b/src/app/(dashboard)/dashboard/usage/components/SystemQuotaOverview.js
new file mode 100644
index 00000000..c541ff0c
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/usage/components/SystemQuotaOverview.js
@@ -0,0 +1,260 @@
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+import { AI_PROVIDERS } from "@/shared/constants/providers";
+import { Button, Card, CardSkeleton } from "@/shared/components";
+import ProviderIcon from "@/shared/components/ProviderIcon";
+import { formatResetTime, REFRESH_INTERVAL_MS } from "./ProviderLimits/utils";
+
+function getProviderInfo(providerId) {
+ return AI_PROVIDERS[providerId] || {
+ name: providerId,
+ color: "#6b7280",
+ };
+}
+
+function formatUpdatedAt(value) {
+ if (!value) return "Not updated yet";
+
+ const date = new Date(value);
+ if (!Number.isFinite(date.getTime())) return "Not updated yet";
+
+ return `Updated ${date.toLocaleTimeString(undefined, {
+ hour: "2-digit",
+ minute: "2-digit",
+ })}`;
+}
+
+function formatResetAt(value) {
+ if (!value) return null;
+
+ const date = new Date(value);
+ if (!Number.isFinite(date.getTime())) return null;
+
+ const now = new Date();
+ const isToday = date.toDateString() === now.toDateString();
+ const isTomorrow = date.toDateString() === new Date(now.getTime() + 86400000).toDateString();
+ const time = date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
+
+ if (isToday) return `today at ${time}`;
+ if (isTomorrow) return `tomorrow at ${time}`;
+ return date.toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
+}
+
+function getQuotaTone(percentage) {
+ if (percentage > 70) {
+ return { bar: "bg-green-500", dot: "bg-green-500", text: "text-green-500" };
+ }
+ if (percentage >= 30) {
+ return { bar: "bg-yellow-500", dot: "bg-yellow-500", text: "text-yellow-500" };
+ }
+ return { bar: "bg-red-500", dot: "bg-red-500", text: "text-red-500" };
+}
+
+function QuotaListRow({ quota }) {
+ const tone = getQuotaTone(quota.remainingPercentage);
+ const resetIn = formatResetTime(quota.resetAt);
+ const resetAt = formatResetAt(quota.resetAt);
+ const resetLabel = quota.recurring === false ? "Expires" : "Resets";
+
+ return (
+
+
+ {quota.name}
+
+
+ {quota.remainingPercentage}%
+
+
+
+ {(resetIn !== "-" || resetAt) && (
+
+ {resetIn !== "-" && {resetLabel} in {resetIn} }
+ {resetIn !== "-" && resetAt && · }
+ {resetAt && {resetAt} }
+
+ )}
+
+ );
+}
+
+function ProviderQuotaListItem({ provider }) {
+ const providerInfo = getProviderInfo(provider.provider);
+ const providerName = providerInfo.name || provider.provider;
+ const hasQuotaData = provider.quotas.length > 0;
+
+ return (
+
+
+
+
+
+
{providerName}
+
+ {provider.accountCount} {provider.accountCount === 1 ? "account" : "accounts"} connected
+
+
+
+
+
+ {hasQuotaData ? (
+
+ {provider.quotas.map((quota) => )}
+
+ ) : (
+
+ {provider.failedAccountCount > 0
+ ? provider.errorMessage || "Quota data is temporarily unavailable for this provider."
+ : "This provider does not currently report quota data."}
+
+ )}
+
+ {provider.failedAccountCount > 0 && hasQuotaData && (
+
+ Some account quota checks could not be completed.
+
+ )}
+
+ );
+}
+
+function OverviewSkeleton() {
+ return (
+
+ {[1, 2, 3].map((key) => (
+
+
+
+ ))}
+
+ );
+}
+
+export default function SystemQuotaOverview() {
+ const [data, setData] = useState(null);
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [refreshing, setRefreshing] = useState(false);
+
+ const loadQuota = useCallback(async (forceRefresh = false) => {
+ if (forceRefresh) setRefreshing(true);
+ setError(null);
+
+ try {
+ const response = await fetch(
+ `/api/usage/system-quota${forceRefresh ? "?refresh=true" : ""}`,
+ { cache: "no-store" },
+ );
+ const payload = await response.json().catch(() => ({}));
+
+ if (!response.ok) {
+ throw new Error(payload.error || "Failed to load system quota");
+ }
+
+ setData(payload);
+ } catch (loadError) {
+ setError(loadError.message || "Failed to load system quota");
+ } finally {
+ setLoading(false);
+ setRefreshing(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ const initialLoadId = window.setTimeout(() => loadQuota(), 0);
+ const intervalId = window.setInterval(() => loadQuota(true), REFRESH_INTERVAL_MS);
+ return () => {
+ window.clearTimeout(initialLoadId);
+ window.clearInterval(intervalId);
+ };
+ }, [loadQuota]);
+
+ if (loading) return
;
+
+ if (error) {
+ return (
+
+ error
+
+
Unable to load system quota
+
{error}
+
+ loadQuota(true)}>
+ Try again
+
+
+ );
+ }
+
+ const providers = data?.providers || [];
+
+ return (
+
+
+
+
+ data_usage
+ System quota
+
+
Available provider capacity
+
+
+ {formatUpdatedAt(data?.updatedAt)}
+ loadQuota(true)}
+ >
+ Refresh
+
+
+
+
+ {providers.length === 0 ? (
+
+ cloud_off
+
+
No quota-capable providers connected
+
+ System quota will appear when an administrator connects a provider that supports usage tracking.
+
+
+
+ ) : (
+
+ {providers.map((provider, index) => (
+ 0 ? "border-t border-border-subtle" : ""}>
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/app/api/usage/system-quota/route.js b/src/app/api/usage/system-quota/route.js
new file mode 100644
index 00000000..c7e9fe41
--- /dev/null
+++ b/src/app/api/usage/system-quota/route.js
@@ -0,0 +1,221 @@
+// Ensure proxyFetch is loaded to patch globalThis.fetch
+import "open-sse/index.js";
+
+import { getProviderConnections } from "@/lib/localDb";
+import { requireUsageDashboardUser } from "@/lib/auth/currentUser";
+import { resolveConnectionProxyConfig } from "@/lib/network/connectionProxy";
+import { USAGE_APIKEY_PROVIDERS, USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
+import { refreshAndUpdateCredentials } from "@/app/api/usage/[connectionId]/route";
+import { getUsageForProvider } from "open-sse/services/usage.js";
+import {
+ getRemainingPercentage,
+ parseQuotaData,
+} from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils";
+
+export const dynamic = "force-dynamic";
+
+const CACHE_TTL_MS = 60 * 1000;
+let cachedSystemQuota = null;
+
+function isUsageEligible(connection) {
+ const isApiKey = connection.authType === "apikey" || connection.authType === "api_key";
+ return USAGE_SUPPORTED_PROVIDERS.includes(connection.provider) && (
+ connection.authType === "oauth" || (isApiKey && USAGE_APIKEY_PROVIDERS.includes(connection.provider))
+ );
+}
+
+function getProxyOptions(proxyConfig) {
+ return {
+ connectionProxyEnabled: proxyConfig.connectionProxyEnabled === true,
+ connectionProxyUrl: proxyConfig.connectionProxyUrl || "",
+ connectionNoProxy: proxyConfig.connectionNoProxy || "",
+ vercelRelayUrl: proxyConfig.vercelRelayUrl || "",
+ strictProxy: false,
+ };
+}
+
+function isAuthenticationError(usage) {
+ const message = usage?.message || usage?.error || "";
+ return /expired|authentication|unauthorized|401|403|re-authorize/i.test(message);
+}
+
+function getEarliestFutureReset(currentResetAt, nextResetAt) {
+ const currentTime = currentResetAt ? new Date(currentResetAt).getTime() : Number.POSITIVE_INFINITY;
+ const nextTime = nextResetAt ? new Date(nextResetAt).getTime() : Number.POSITIVE_INFINITY;
+ const now = Date.now();
+
+ if (nextTime > now && nextTime < currentTime) return nextResetAt;
+ if (currentTime > now) return currentResetAt;
+ return nextResetAt || currentResetAt || null;
+}
+
+function sanitizeProviderError(error) {
+ const message = String(error?.message || error || "");
+ if (/re-authorize|refresh credentials|no codex access token|unauthorized|authentication/i.test(message)) {
+ return "An administrator needs to re-authorize this provider connection.";
+ }
+ if (/temporarily unavailable|rate limit|timeout|fetch/i.test(message)) {
+ return "The provider quota service is temporarily unavailable.";
+ }
+ return "Quota data is temporarily unavailable for this provider.";
+}
+
+function buildSystemQuotaResponse(connections, results) {
+ const providerGroups = new Map();
+
+ for (const connection of connections) {
+ if (!providerGroups.has(connection.provider)) {
+ providerGroups.set(connection.provider, {
+ provider: connection.provider,
+ accountCount: 0,
+ quotaAccountCount: 0,
+ failedAccountCount: 0,
+ errorMessage: null,
+ quotaGroups: new Map(),
+ });
+ }
+ providerGroups.get(connection.provider).accountCount += 1;
+ }
+
+ for (const result of results) {
+ const group = providerGroups.get(result.provider);
+ if (!group) continue;
+
+ if (result.status !== "fulfilled" || result.value.length === 0) {
+ group.failedAccountCount += 1;
+ group.errorMessage ||= result.errorMessage || "Quota data is temporarily unavailable for this provider.";
+ continue;
+ }
+
+ group.quotaAccountCount += 1;
+ for (const quota of result.value) {
+ if (!quota?.name) continue;
+
+ if (!group.quotaGroups.has(quota.name)) {
+ group.quotaGroups.set(quota.name, {
+ name: quota.name,
+ percentageTotal: 0,
+ accountCount: 0,
+ resetAt: null,
+ recurring: quota.recurring !== false,
+ });
+ }
+
+ const quotaGroup = group.quotaGroups.get(quota.name);
+ quotaGroup.percentageTotal += getRemainingPercentage(quota);
+ quotaGroup.accountCount += 1;
+ quotaGroup.resetAt = getEarliestFutureReset(quotaGroup.resetAt, quota.resetAt);
+ quotaGroup.recurring = quotaGroup.recurring && quota.recurring !== false;
+ }
+ }
+
+ const providers = Array.from(providerGroups.values())
+ .map((group) => ({
+ provider: group.provider,
+ accountCount: group.accountCount,
+ quotaAccountCount: group.quotaAccountCount,
+ failedAccountCount: group.failedAccountCount,
+ errorMessage: group.errorMessage,
+ quotas: Array.from(group.quotaGroups.values()).map((quota) => ({
+ name: quota.name,
+ remainingPercentage: Math.round(quota.percentageTotal / quota.accountCount),
+ accountCount: quota.accountCount,
+ resetAt: quota.resetAt,
+ recurring: quota.recurring,
+ })),
+ }))
+ .sort((a, b) => a.provider.localeCompare(b.provider));
+
+ return {
+ providers,
+ updatedAt: new Date().toISOString(),
+ };
+}
+
+async function fetchConnectionQuota(connection) {
+ const proxyConfig = await resolveConnectionProxyConfig(connection.providerSpecificData);
+ const proxyOptions = getProxyOptions(proxyConfig);
+ let usableConnection = connection;
+
+ if (connection.authType === "oauth") {
+ const refreshed = await refreshAndUpdateCredentials(connection, false, proxyOptions);
+ usableConnection = refreshed.connection;
+ }
+
+ let usage = await getUsageForProvider(usableConnection, proxyOptions);
+
+ // Match the existing per-connection quota endpoint: a provider can reject an
+ // otherwise unexpired OAuth access token, in which case a forced refresh and
+ // a single retry is required before declaring the aggregate unavailable.
+ if (connection.authType === "oauth" && isAuthenticationError(usage) && connection.refreshToken) {
+ const refreshed = await refreshAndUpdateCredentials(usableConnection, true, proxyOptions);
+ usableConnection = refreshed.connection;
+ usage = await getUsageForProvider(usableConnection, proxyOptions);
+ }
+
+ if (usage?.message || usage?.error) {
+ throw new Error(usage.message || usage.error);
+ }
+
+ return parseQuotaData(connection.provider, usage).filter((quota) => (
+ Number.isFinite(getRemainingPercentage(quota))
+ ));
+}
+
+/**
+ * GET /api/usage/system-quota
+ *
+ * Returns provider-level average quota availability across every eligible system
+ * connection. It deliberately excludes all connection, credential, and owner data.
+ */
+export async function GET(request) {
+ try {
+ await requireUsageDashboardUser();
+
+ const { searchParams } = new URL(request.url);
+ const forceRefresh = searchParams.get("refresh") === "true";
+ const cacheIsFresh = cachedSystemQuota && Date.now() - cachedSystemQuota.cachedAt < CACHE_TTL_MS;
+
+ if (!forceRefresh && cacheIsFresh) {
+ return Response.json({ ...cachedSystemQuota.data, cached: true });
+ }
+
+ const connections = (await getProviderConnections({})).filter(isUsageEligible);
+ const results = await Promise.allSettled(
+ connections.map(async (connection) => ({
+ provider: connection.provider,
+ quotas: await fetchConnectionQuota(connection),
+ })),
+ );
+
+ const normalizedResults = results.map((result, index) => {
+ if (result.status === "fulfilled") {
+ return {
+ status: result.status,
+ provider: result.value.provider,
+ value: result.value.quotas,
+ };
+ }
+
+ console.warn(`[System quota] ${connections[index].provider}: ${result.reason?.message || "quota fetch failed"}`);
+ return {
+ status: result.status,
+ provider: connections[index].provider,
+ value: [],
+ errorMessage: sanitizeProviderError(result.reason),
+ };
+ });
+
+ const data = buildSystemQuotaResponse(connections, normalizedResults);
+ cachedSystemQuota = { data, cachedAt: Date.now() };
+
+ return Response.json({ ...data, cached: false });
+ } catch (error) {
+ if (error?.message === "Unauthorized") {
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ console.error("[System quota] Failed to build aggregate quota:", error);
+ return Response.json({ error: "Failed to load system quota" }, { status: 500 });
+ }
+}