9router/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js
decolua 4ba546afe7 Enhance token refresh logic and improve MITM server handling
- Introduced a caching mechanism for in-flight token refresh requests to prevent race conditions and reduce unnecessary API calls.
- Added error handling for unrecoverable refresh errors, ensuring that the application can gracefully handle token reuse and invalidation scenarios.
- Updated the MITM server management to handle port 443 conflicts, allowing users to kill processes occupying the port before starting the server.
- Improved user feedback in the MitmServerCard component regarding port conflicts and admin privileges.
- Refactored the ComboList component to streamline the display of media provider combos.

This update aims to enhance the reliability and user experience of the token management and MITM functionalities.
2026-05-03 22:10:03 +07:00

171 lines
5.9 KiB
JavaScript

"use client";
import { formatResetTime, calculatePercentage } from "./utils";
/**
* Format reset time display (Today, 12:00 PM)
*/
function formatResetTimeDisplay(resetTime) {
if (!resetTime) return null;
try {
const date = new Date(resetTime);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
let dayStr = "";
if (date >= today && date < tomorrow) {
dayStr = "Today";
} else if (date >= tomorrow && date < new Date(tomorrow.getTime() + 24 * 60 * 60 * 1000)) {
dayStr = "Tomorrow";
} else {
dayStr = date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
const timeStr = date.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
hour12: true
});
return `${dayStr}, ${timeStr}`;
} catch {
return null;
}
}
/**
* Get color classes based on remaining percentage
*/
function getColorClasses(remainingPercentage) {
if (remainingPercentage > 70) {
return {
text: "text-green-600 dark:text-green-400",
bg: "bg-green-500",
bgLight: "bg-green-500/10",
emoji: "🟢"
};
}
if (remainingPercentage >= 30) {
return {
text: "text-yellow-600 dark:text-yellow-400",
bg: "bg-yellow-500",
bgLight: "bg-yellow-500/10",
emoji: "🟡"
};
}
// 0-29% including 0% (out of quota) - show red
return {
text: "text-red-600 dark:text-red-400",
bg: "bg-red-500",
bgLight: "bg-red-500/10",
emoji: "🔴"
};
}
/**
* Quota Table Component - Table-based display for quota data
*/
export default function QuotaTable({ quotas = [], compact = false }) {
if (!quotas || quotas.length === 0) {
return null;
}
const cellPad = compact ? "py-1 px-1.5" : "py-2 px-3";
const nameText = compact ? "text-[11px]" : "text-sm";
const resetPrimary = compact ? "text-[11px]" : "text-sm";
const resetSecondary = compact ? "text-[10px] leading-tight" : "text-xs";
return (
<div className="overflow-x-auto">
<table className="w-full table-fixed text-left">
<tbody>
{quotas.map((quota, index) => {
const remaining = quota.remainingPercentage !== undefined
? Math.round(quota.remainingPercentage)
: calculatePercentage(quota.used, quota.total);
const colors = getColorClasses(remaining);
const countdown = formatResetTime(quota.resetAt);
const resetDisplay = formatResetTimeDisplay(quota.resetAt);
return (
<tr
key={index}
className="border-b border-black/5 dark:border-white/5 hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors"
>
{/* Model Name with Status Emoji */}
<td className={`${cellPad} w-[30%]`}>
<div className="flex items-center gap-1.5 min-w-0">
<span className="text-[10px] shrink-0">{colors.emoji}</span>
<span className={`${nameText} font-medium text-text-primary truncate`}>
{quota.name}
</span>
</div>
</td>
{/* Limit (Progress + Numbers) */}
<td className={`${cellPad} w-[45%]`}>
<div className={compact ? "space-y-1" : "space-y-1.5"}>
{/* Progress bar - always show with border for visibility */}
<div className={`${compact ? "h-1" : "h-1.5"} rounded-full overflow-hidden border ${colors.bgLight} ${
remaining === 0 ? 'border-black/10 dark:border-white/10' : 'border-transparent'
}`}>
<div
className={`h-full transition-all duration-300 ${colors.bg}`}
style={{ width: `${Math.min(remaining, 100)}%` }}
/>
</div>
{/* Numbers */}
<div className={`flex items-center justify-between ${compact ? "text-[10px]" : "text-xs"}`}>
<span className="text-text-muted">
{quota.used.toLocaleString()} / {quota.total > 0 ? quota.total.toLocaleString() : "∞"}
</span>
<span className={`font-medium ${colors.text}`}>
{remaining}%
</span>
</div>
</div>
</td>
{/* Reset Time */}
<td className={`${cellPad} w-[25%]`}>
{countdown !== "-" || resetDisplay ? (
compact ? (
<div
className={`${resetPrimary} text-text-primary font-medium truncate`}
title={resetDisplay || ""}
>
{countdown !== "-" ? `in ${countdown}` : resetDisplay}
</div>
) : (
<div className="space-y-0.5">
{countdown !== "-" && (
<div className={`${resetPrimary} text-text-primary font-medium`}>
in {countdown}
</div>
)}
{resetDisplay && (
<div className={`${resetSecondary} text-text-muted`}>
{resetDisplay}
</div>
)}
</div>
)
) : (
<div className={`${resetPrimary} text-text-muted italic`}>N/A</div>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}