mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
ui - add team indicator to ui
This commit is contained in:
parent
b7843b9113
commit
045026462f
2 changed files with 226 additions and 74 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|||
>
|
||||
<Users className="h-3 w-3 flex-shrink-0" />
|
||||
{hasIssues && <span className="flex-shrink-0">{getStatusIcon()}</span>}
|
||||
<span className="truncate">{data ? `${data.total_users_used}/${data.total_users}` : "Usage"}</span>
|
||||
<div className="flex items-center gap-1 truncate">
|
||||
{data && data.total_users !== null && (
|
||||
<span className="flex-shrink-0">U:{data.total_users_used}/{data.total_users}</span>
|
||||
)}
|
||||
{data && data.total_teams !== null && (
|
||||
<span className="flex-shrink-0">T:{data.total_teams_used}/{data.total_teams}</span>
|
||||
)}
|
||||
{!data || (data.total_users === null && data.total_teams === null) && (
|
||||
<span className="truncate">Usage</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -191,33 +239,78 @@ export default function UsageIndicator({accessToken, width = 220}: UsageIndicato
|
|||
|
||||
{/* Expanded details - simple and compact */}
|
||||
{isExpanded && (
|
||||
<div className="mt-2 pl-7 text-xs text-gray-600">
|
||||
<div className="mb-1">
|
||||
<span className="font-medium">{data.total_users_used}/{data.total_users}</span>
|
||||
<span className="text-gray-500"> users</span>
|
||||
</div>
|
||||
|
||||
{/* Simple progress bar */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-1 mb-1">
|
||||
<div
|
||||
className={cn(
|
||||
"h-1 rounded-full transition-all duration-300",
|
||||
isOverLimit && "bg-red-500",
|
||||
isNearLimit && "bg-yellow-500",
|
||||
!isOverLimit && !isNearLimit && "bg-green-500",
|
||||
)}
|
||||
style={{ width: `${Math.min(usagePercentage, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 pl-7 text-xs text-gray-600 space-y-3">
|
||||
{/* Users section */}
|
||||
{data.total_users !== null && (
|
||||
<div>
|
||||
<div className="mb-1 flex items-center gap-1">
|
||||
<Users className="h-3 w-3" />
|
||||
<span className="font-medium">{data.total_users_used}/{data.total_users}</span>
|
||||
<span className="text-gray-500">users</span>
|
||||
</div>
|
||||
|
||||
{/* User progress bar */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-1 mb-1">
|
||||
<div
|
||||
className={cn(
|
||||
"h-1 rounded-full transition-all duration-300",
|
||||
userMetrics.isOverLimit && "bg-red-500",
|
||||
userMetrics.isNearLimit && "bg-yellow-500",
|
||||
!userMetrics.isOverLimit && !userMetrics.isNearLimit && "bg-green-500",
|
||||
)}
|
||||
style={{ width: `${Math.min(userMetrics.usagePercentage, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(isOverLimit || isNearLimit) && (
|
||||
<div className={cn(
|
||||
"flex items-center gap-1 text-xs",
|
||||
isOverLimit && "text-red-600",
|
||||
isNearLimit && "text-yellow-600"
|
||||
)}>
|
||||
{getStatusIcon()}
|
||||
<span className="truncate">{getStatusText()}</span>
|
||||
{(userMetrics.isOverLimit || userMetrics.isNearLimit) && (
|
||||
<div className={cn(
|
||||
"flex items-center gap-1 text-xs",
|
||||
userMetrics.isOverLimit && "text-red-600",
|
||||
userMetrics.isNearLimit && "text-yellow-600"
|
||||
)}>
|
||||
{userMetrics.isOverLimit ? <AlertTriangle className="h-3 w-3" /> : <TrendingUp className="h-3 w-3" />}
|
||||
<span className="truncate">
|
||||
Users {userMetrics.isOverLimit ? "Over Limit" : "Near Limit"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Teams section */}
|
||||
{data.total_teams !== null && (
|
||||
<div>
|
||||
<div className="mb-1 flex items-center gap-1">
|
||||
<UserCheck className="h-3 w-3" />
|
||||
<span className="font-medium">{data.total_teams_used}/{data.total_teams}</span>
|
||||
<span className="text-gray-500">teams</span>
|
||||
</div>
|
||||
|
||||
{/* Team progress bar */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-1 mb-1">
|
||||
<div
|
||||
className={cn(
|
||||
"h-1 rounded-full transition-all duration-300",
|
||||
teamMetrics.isOverLimit && "bg-red-500",
|
||||
teamMetrics.isNearLimit && "bg-yellow-500",
|
||||
!teamMetrics.isOverLimit && !teamMetrics.isNearLimit && "bg-green-500",
|
||||
)}
|
||||
style={{ width: `${Math.min(teamMetrics.usagePercentage, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(teamMetrics.isOverLimit || teamMetrics.isNearLimit) && (
|
||||
<div className={cn(
|
||||
"flex items-center gap-1 text-xs",
|
||||
teamMetrics.isOverLimit && "text-red-600",
|
||||
teamMetrics.isNearLimit && "text-yellow-600"
|
||||
)}>
|
||||
{teamMetrics.isOverLimit ? <AlertTriangle className="h-3 w-3" /> : <TrendingUp className="h-3 w-3" />}
|
||||
<span className="truncate">
|
||||
Teams {teamMetrics.isOverLimit ? "Over Limit" : "Near Limit"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -243,11 +336,17 @@ export default function UsageIndicator({accessToken, width = 220}: UsageIndicato
|
|||
<div className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4 flex-shrink-0" />
|
||||
{hasIssues && <span className="flex-shrink-0">{getStatusIcon()}</span>}
|
||||
{data && (
|
||||
<span className="text-sm font-medium truncate">
|
||||
{data.total_users_used}/{data.total_users}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-sm font-medium truncate">
|
||||
{data && data.total_users !== null && (
|
||||
<span className="flex-shrink-0">U: {data.total_users_used}/{data.total_users}</span>
|
||||
)}
|
||||
{data && data.total_teams !== null && (
|
||||
<span className="flex-shrink-0">T: {data.total_teams_used}/{data.total_teams}</span>
|
||||
)}
|
||||
{!data || (data.total_users === null && data.total_teams === null) && (
|
||||
<span className="truncate">Usage</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
|
|
@ -314,45 +413,95 @@ export default function UsageIndicator({accessToken, width = 220}: UsageIndicato
|
|||
</div>
|
||||
|
||||
{/* Compact stats optimized for 220px */}
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Used:</span>
|
||||
<span className="font-medium text-right">{data.total_users_used}/{data.total_users}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Remaining:</span>
|
||||
<span className={cn(
|
||||
"font-medium text-right",
|
||||
isOverLimit && "text-red-600",
|
||||
isNearLimit && "text-yellow-600"
|
||||
)}>
|
||||
{data.total_users_remaining}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Usage:</span>
|
||||
<span className="font-medium text-right">{Math.round(usagePercentage)}%</span>
|
||||
</div>
|
||||
<div className="space-y-3 text-sm">
|
||||
{/* Users section */}
|
||||
{data.total_users !== null && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-1 text-xs text-gray-600 mb-1">
|
||||
<Users className="h-3 w-3" />
|
||||
<span className="font-medium">Users</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Used:</span>
|
||||
<span className="font-medium text-right">{data.total_users_used}/{data.total_users}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Remaining:</span>
|
||||
<span className={cn(
|
||||
"font-medium text-right",
|
||||
userMetrics.isOverLimit && "text-red-600",
|
||||
userMetrics.isNearLimit && "text-yellow-600"
|
||||
)}>
|
||||
{data.total_users_remaining}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Usage:</span>
|
||||
<span className="font-medium text-right">{Math.round(userMetrics.usagePercentage)}%</span>
|
||||
</div>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-2 mt-3">
|
||||
<div
|
||||
className={cn(
|
||||
"h-2 rounded-full transition-all duration-300",
|
||||
isOverLimit && "bg-red-500",
|
||||
isNearLimit && "bg-yellow-500",
|
||||
!isOverLimit && !isNearLimit && "bg-green-500",
|
||||
)}
|
||||
style={{ width: `${Math.min(usagePercentage, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
{/* User progress bar */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className={cn(
|
||||
"h-2 rounded-full transition-all duration-300",
|
||||
userMetrics.isOverLimit && "bg-red-500",
|
||||
userMetrics.isNearLimit && "bg-yellow-500",
|
||||
!userMetrics.isOverLimit && !userMetrics.isNearLimit && "bg-green-500",
|
||||
)}
|
||||
style={{ width: `${Math.min(userMetrics.usagePercentage, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Teams section */}
|
||||
{data.total_teams !== null && (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-1 text-xs text-gray-600 mb-1">
|
||||
<UserCheck className="h-3 w-3" />
|
||||
<span className="font-medium">Teams</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Used:</span>
|
||||
<span className="font-medium text-right">{data.total_teams_used}/{data.total_teams}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Remaining:</span>
|
||||
<span className={cn(
|
||||
"font-medium text-right",
|
||||
teamMetrics.isOverLimit && "text-red-600",
|
||||
teamMetrics.isNearLimit && "text-yellow-600"
|
||||
)}>
|
||||
{data.total_teams_remaining}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 text-xs">Usage:</span>
|
||||
<span className="font-medium text-right">{Math.round(teamMetrics.usagePercentage)}%</span>
|
||||
</div>
|
||||
|
||||
{/* Team progress bar */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className={cn(
|
||||
"h-2 rounded-full transition-all duration-300",
|
||||
teamMetrics.isOverLimit && "bg-red-500",
|
||||
teamMetrics.isNearLimit && "bg-yellow-500",
|
||||
!teamMetrics.isOverLimit && !teamMetrics.isNearLimit && "bg-green-500",
|
||||
)}
|
||||
style={{ width: `${Math.min(teamMetrics.usagePercentage, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue