From 045026462f6a1abe1bed48bc7aa121694f92ba09 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 21 Jun 2025 14:55:09 -0700 Subject: [PATCH] ui - add team indicator to ui --- .../src/components/networking.tsx | 7 +- .../src/components/usage_indicator.tsx | 293 +++++++++++++----- 2 files changed, 226 insertions(+), 74 deletions(-) diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index a7190d49a3b..3e96b84f8fc 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -5740,9 +5740,12 @@ export const uiAuditLogsCall = async ( export const getRemainingUsers = async ( accessToken: string ): Promise<{ - total_users: number; + total_users: number | null; total_users_used: number; - total_users_remaining: number; + total_users_remaining: number | null; + total_teams: number | null; + total_teams_used: number; + total_teams_remaining: number | null; } | null> => { try { const url = proxyBaseUrl diff --git a/ui/litellm-dashboard/src/components/usage_indicator.tsx b/ui/litellm-dashboard/src/components/usage_indicator.tsx index dc5be0571d2..093caf85565 100644 --- a/ui/litellm-dashboard/src/components/usage_indicator.tsx +++ b/ui/litellm-dashboard/src/components/usage_indicator.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react" import { Badge } from "@tremor/react" -import { AlertTriangle, Users, TrendingUp, Loader2, ChevronDown, ChevronUp, Minus } from "lucide-react" +import { AlertTriangle, Users, TrendingUp, Loader2, ChevronDown, ChevronUp, Minus, UserCheck } from "lucide-react" import { getRemainingUsers } from "./networking" // Simple utility function to combine class names @@ -17,6 +17,9 @@ interface UsageData { total_users: number | null total_users_used: number total_users_remaining: number | null + total_teams: number | null + total_teams_used: number + total_teams_remaining: number | null } export default function UsageIndicator({accessToken, width = 220}: UsageIndicatorProps) { @@ -54,18 +57,53 @@ export default function UsageIndicator({accessToken, width = 220}: UsageIndicato return { isOverLimit: false, isNearLimit: false, - usagePercentage: 0 + usagePercentage: 0, + userMetrics: { + isOverLimit: false, + isNearLimit: false, + usagePercentage: 0 + }, + teamMetrics: { + isOverLimit: false, + isNearLimit: false, + usagePercentage: 0 + } } } - const isOverLimit = data.total_users_remaining ? data.total_users_remaining <= 0 : false - const isNearLimit = data.total_users_remaining ? data.total_users_remaining <= 5 && data.total_users_remaining > 0 : false - const usagePercentage = data.total_users ? (data.total_users_used / data.total_users) * 100 : 0 + // User metrics + const userIsOverLimit = data.total_users_remaining ? data.total_users_remaining <= 0 : false + const userIsNearLimit = data.total_users_remaining ? data.total_users_remaining <= 5 && data.total_users_remaining > 0 : false + const userUsagePercentage = data.total_users ? (data.total_users_used / data.total_users) * 100 : 0 - return { isOverLimit, isNearLimit, usagePercentage } + // Team metrics + const teamIsOverLimit = data.total_teams_remaining ? data.total_teams_remaining <= 0 : false + const teamIsNearLimit = data.total_teams_remaining ? data.total_teams_remaining <= 5 && data.total_teams_remaining > 0 : false + const teamUsagePercentage = data.total_teams ? (data.total_teams_used / data.total_teams) * 100 : 0 + + // Combined status (worst case scenario) + const isOverLimit = userIsOverLimit || teamIsOverLimit + const isNearLimit = (userIsNearLimit || teamIsNearLimit) && !isOverLimit + const usagePercentage = Math.max(userUsagePercentage, teamUsagePercentage) + + return { + isOverLimit, + isNearLimit, + usagePercentage, + userMetrics: { + isOverLimit: userIsOverLimit, + isNearLimit: userIsNearLimit, + usagePercentage: userUsagePercentage + }, + teamMetrics: { + isOverLimit: teamIsOverLimit, + isNearLimit: teamIsNearLimit, + usagePercentage: teamUsagePercentage + } + } } - const { isOverLimit, isNearLimit, usagePercentage } = getUsageMetrics(data) + const { isOverLimit, isNearLimit, usagePercentage, userMetrics, teamMetrics } = getUsageMetrics(data) const getStatusColor = () => { if (isOverLimit) return "red" @@ -105,7 +143,17 @@ export default function UsageIndicator({accessToken, width = 220}: UsageIndicato > {hasIssues && {getStatusIcon()}} - {data ? `${data.total_users_used}/${data.total_users}` : "Usage"} +
+ {data && data.total_users !== null && ( + U:{data.total_users_used}/{data.total_users} + )} + {data && data.total_teams !== null && ( + T:{data.total_teams_used}/{data.total_teams} + )} + {!data || (data.total_users === null && data.total_teams === null) && ( + Usage + )} +
) @@ -191,33 +239,78 @@ export default function UsageIndicator({accessToken, width = 220}: UsageIndicato {/* Expanded details - simple and compact */} {isExpanded && ( -
-
- {data.total_users_used}/{data.total_users} - users -
- - {/* Simple progress bar */} -
-
-
+
+ {/* Users section */} + {data.total_users !== null && ( +
+
+ + {data.total_users_used}/{data.total_users} + users +
+ + {/* User progress bar */} +
+
+
- {(isOverLimit || isNearLimit) && ( -
- {getStatusIcon()} - {getStatusText()} + {(userMetrics.isOverLimit || userMetrics.isNearLimit) && ( +
+ {userMetrics.isOverLimit ? : } + + Users {userMetrics.isOverLimit ? "Over Limit" : "Near Limit"} + +
+ )} +
+ )} + + {/* Teams section */} + {data.total_teams !== null && ( +
+
+ + {data.total_teams_used}/{data.total_teams} + teams +
+ + {/* Team progress bar */} +
+
+
+ + {(teamMetrics.isOverLimit || teamMetrics.isNearLimit) && ( +
+ {teamMetrics.isOverLimit ? : } + + Teams {teamMetrics.isOverLimit ? "Over Limit" : "Near Limit"} + +
+ )}
)}
@@ -243,11 +336,17 @@ export default function UsageIndicator({accessToken, width = 220}: UsageIndicato
{hasIssues && {getStatusIcon()}} - {data && ( - - {data.total_users_used}/{data.total_users} - - )} +
+ {data && data.total_users !== null && ( + U: {data.total_users_used}/{data.total_users} + )} + {data && data.total_teams !== null && ( + T: {data.total_teams_used}/{data.total_teams} + )} + {!data || (data.total_users === null && data.total_teams === null) && ( + Usage + )} +
) @@ -314,45 +413,95 @@ export default function UsageIndicator({accessToken, width = 220}: UsageIndicato
{/* Compact stats optimized for 220px */} -
-
- Used: - {data.total_users_used}/{data.total_users} -
-
- Remaining: - - {data.total_users_remaining} - -
-
- Usage: - {Math.round(usagePercentage)}% -
+
+ {/* Users section */} + {data.total_users !== null && ( +
+
+ + Users +
+
+ Used: + {data.total_users_used}/{data.total_users} +
+
+ Remaining: + + {data.total_users_remaining} + +
+
+ Usage: + {Math.round(userMetrics.usagePercentage)}% +
- {/* Progress bar */} -
-
-
+ {/* User progress bar */} +
+
+
+
+ )} + + {/* Teams section */} + {data.total_teams !== null && ( +
+
+ + Teams +
+
+ Used: + {data.total_teams_used}/{data.total_teams} +
+
+ Remaining: + + {data.total_teams_remaining} + +
+
+ Usage: + {Math.round(teamMetrics.usagePercentage)}% +
+ + {/* Team progress bar */} +
+
+
+
+ )}
) } - // Don't render anything if no access token or if total_users is null - if (!accessToken || data?.total_users === null) { + // Don't render anything if no access token or if both total_users and total_teams are null + if (!accessToken || (data?.total_users === null && data?.total_teams === null)) { return null }