feat: Token Limits UI - Phase 2 (Dashboard + User Management)
Token Usage Dashboard (/admin/token-usage): - Add Monthly Limit column with edit functionality - Add % Used column with progress bars - Color-coded alerts (green/yellow/orange/red) - Real-time limit updates via API - Alert banners for users >80% and >100% User Management (/admin/users): - Add Token Limit column - Show percentage used with progress indicator - Color-coded badges for usage levels Admin Dashboard (/admin): - Add AI Token Usage card - Display total tokens, requests, and cost - Link to detailed token usage page All changes are fully responsive and dark-mode compatible. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -9,15 +9,24 @@ import {
|
||||
Zap,
|
||||
Server,
|
||||
Clock,
|
||||
ShieldAlert
|
||||
ShieldAlert,
|
||||
Gauge,
|
||||
TrendingUp
|
||||
} from "lucide-react";
|
||||
|
||||
interface TokenStats {
|
||||
total_tokens: number;
|
||||
total_requests: number;
|
||||
total_cost_usd: number;
|
||||
}
|
||||
|
||||
export default function AdminDashboard() {
|
||||
const [stats, setStats] = useState({
|
||||
orgs: 0,
|
||||
users: 0,
|
||||
courses: 0
|
||||
});
|
||||
const [tokenStats, setTokenStats] = useState<TokenStats | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -25,9 +34,14 @@ export default function AdminDashboard() {
|
||||
try {
|
||||
// In a real app we'd have a specific stats endpoint,
|
||||
// but for now we'll calculate from lists
|
||||
const [org, users] = await Promise.all([
|
||||
const [org, users, tokenResp] = await Promise.all([
|
||||
cmsApi.getOrganization(),
|
||||
cmsApi.getAllUsers()
|
||||
cmsApi.getAllUsers(),
|
||||
fetch(`${process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:3001'}/admin/token-usage`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
})
|
||||
]);
|
||||
|
||||
setStats({
|
||||
@@ -35,6 +49,16 @@ export default function AdminDashboard() {
|
||||
users: users.length,
|
||||
courses: 0 // We'd need a global courses count
|
||||
});
|
||||
|
||||
// Load token stats
|
||||
if (tokenResp.ok) {
|
||||
const tokenData = await tokenResp.json();
|
||||
setTokenStats({
|
||||
total_tokens: tokenData.stats?.total_tokens || 0,
|
||||
total_requests: tokenData.stats?.total_requests || 0,
|
||||
total_cost_usd: tokenData.stats?.total_cost_usd || 0,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load admin stats", err);
|
||||
} finally {
|
||||
@@ -79,7 +103,7 @@ export default function AdminDashboard() {
|
||||
</div>
|
||||
|
||||
{/* Stat Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{cards.map((card) => (
|
||||
<div key={card.label} className="p-8 rounded-3xl bg-white dark:bg-white/[0.02] border border-slate-200 dark:border-white/5 flex flex-col gap-4 shadow-sm dark:shadow-none">
|
||||
<div className={`w-12 h-12 rounded-xl ${card.bg} flex items-center justify-center ${card.color}`}>
|
||||
@@ -92,6 +116,25 @@ export default function AdminDashboard() {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* AI Token Usage Card */}
|
||||
<div className="p-8 rounded-3xl bg-white dark:bg-white/[0.02] border border-slate-200 dark:border-white/5 flex flex-col gap-4 shadow-sm dark:shadow-none">
|
||||
<div className="w-12 h-12 rounded-xl bg-indigo-500/10 flex items-center justify-center text-indigo-600 dark:text-indigo-400">
|
||||
<Gauge size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[10px] font-black uppercase tracking-[0.2em] text-slate-400 dark:text-gray-500 mb-1">AI Token Usage</div>
|
||||
<div className="text-4xl font-black text-slate-900 dark:text-white">
|
||||
{loading ? "..." : tokenStats ? new Intl.NumberFormat('en-US', { notation: 'compact', compactDisplay: 'short' }).format(tokenStats.total_tokens) : 'N/A'}
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 dark:text-gray-500 mt-2">
|
||||
{tokenStats ? `${new Intl.NumberFormat('en-US').format(tokenStats.total_requests)} requests • $${tokenStats.total_cost_usd.toFixed(2)}` : 'Loading...'}
|
||||
</p>
|
||||
</div>
|
||||
<a href="/admin/token-usage" className="text-xs font-bold text-indigo-600 dark:text-indigo-400 hover:text-indigo-500 flex items-center gap-1 mt-2">
|
||||
View Details <TrendingUp className="w-3 h-3" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* System Health */}
|
||||
|
||||
Reference in New Issue
Block a user