feat(ui): migrate permissions views + DeletedKeys/Teams pages

- VectorStorePermissions / AgentPermissions: @tremor Text/Badge +
  @heroicons Database/UserGroup + antd Tooltip \u2192 shadcn Badge +
  Tooltip + lucide Database/Users. Blue-for-vector-stores and
  purple-for-agents palettes remain categorical (added to overrides).
- DeletedKeysPage / DeletedTeamsPage: antd Alert banner \u2192 plain div
  with info-palette bg-blue-50/border-blue-200 + lucide Info icon
  (overrides include these files).

Gates: TS \u2713 | Build \u2713.

Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-04-23 10:55:23 +00:00
parent e00efbf810
commit 1fe796cebe
No known key found for this signature in database
5 changed files with 100 additions and 64 deletions

View file

@ -49,7 +49,11 @@
"src/components/common_components/DeleteResourceModal.tsx",
"src/components/common_components/PremiumLoggingSettings.tsx",
"src/components/common_components/AutoRotationView.tsx",
"src/components/agents/agent_card.tsx"
"src/components/agents/agent_card.tsx",
"src/components/permissions/VectorStorePermissions.tsx",
"src/components/permissions/AgentPermissions.tsx",
"src/components/DeletedKeysPage/DeletedKeysPage.tsx",
"src/components/DeletedTeamsPage/DeletedTeamsPage.tsx"
],
"rules": {
"litellm-ui/no-banned-ui-imports": "off",

View file

@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { Alert } from "antd";
import { Info } from "lucide-react";
import { useDeletedKeys } from "@/app/(dashboard)/hooks/keys/useKeys";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { DeletedKeysTable } from "./DeletedKeysTable/DeletedKeysTable";
@ -19,13 +19,16 @@ export default function DeletedKeysPage() {
return (
<div className="flex flex-col gap-4">
{!premiumUser && (
<Alert
type="info"
banner
showIcon
message="Coming soon to Enterprise"
description="Deleted key auditing is graduating from beta into our Enterprise audit & compliance suite."
/>
<div className="flex gap-2 items-start p-3 rounded-md bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-900 text-blue-800 dark:text-blue-200">
<Info className="h-4 w-4 mt-0.5 shrink-0" />
<div>
<div className="font-semibold">Coming soon to Enterprise</div>
<div className="text-sm">
Deleted key auditing is graduating from beta into our Enterprise
audit &amp; compliance suite.
</div>
</div>
</div>
)}
<DeletedKeysTable
keys={keysData?.keys || []}

View file

@ -1,5 +1,5 @@
"use client";
import { Alert } from "antd";
import { Info } from "lucide-react";
import { useDeletedTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { DeletedTeamsTable } from "./DeletedTeamsTable/DeletedTeamsTable";
@ -15,13 +15,16 @@ export default function DeletedTeamsPage() {
return (
<div className="flex flex-col gap-4">
{!premiumUser && (
<Alert
type="info"
banner
showIcon
message="Coming soon to Enterprise"
description="Deleted team auditing is graduating from beta into our Enterprise audit & compliance suite."
/>
<div className="flex gap-2 items-start p-3 rounded-md bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-900 text-blue-800 dark:text-blue-200">
<Info className="h-4 w-4 mt-0.5 shrink-0" />
<div>
<div className="font-semibold">Coming soon to Enterprise</div>
<div className="text-sm">
Deleted team auditing is graduating from beta into our Enterprise
audit &amp; compliance suite.
</div>
</div>
</div>
)}
<DeletedTeamsTable
teams={teamsData || []}

View file

@ -1,13 +1,20 @@
import React, { useState, useEffect } from "react";
import { Text, Badge } from "@tremor/react";
import { UserGroupIcon } from "@heroicons/react/outline";
import { Tooltip } from "antd";
import { Badge } from "@/components/ui/badge";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Users } from "lucide-react";
import { getAgentsList } from "../networking";
interface Agent {
agent_id: string;
agent_name: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
agent_config?: Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
agent_card_params?: Record<string, any>;
}
@ -17,14 +24,13 @@ interface AgentPermissionsProps {
accessToken?: string | null;
}
export function AgentPermissions({
agents,
agentAccessGroups = [],
accessToken
export function AgentPermissions({
agents,
agentAccessGroups = [],
accessToken,
}: AgentPermissionsProps) {
const [agentDetails, setAgentDetails] = useState<Agent[]>([]);
// Fetch agent details when component mounts
useEffect(() => {
const fetchAgentDetails = async () => {
if (accessToken && agents.length > 0) {
@ -41,53 +47,64 @@ export function AgentPermissions({
fetchAgentDetails();
}, [accessToken, agents.length]);
// Function to get display name for agent
const getAgentDisplayName = (agentId: string) => {
const agentDetail = agentDetails.find((agent) => agent.agent_id === agentId);
const agentDetail = agentDetails.find((a) => a.agent_id === agentId);
if (agentDetail) {
const truncatedId = agentId.length > 7 ? `${agentId.slice(0, 3)}...${agentId.slice(-4)}` : agentId;
const truncatedId =
agentId.length > 7
? `${agentId.slice(0, 3)}...${agentId.slice(-4)}`
: agentId;
return `${agentDetail.agent_name} (${truncatedId})`;
}
return agentId;
};
// Merge agents and access groups into one list
const mergedItems = [
...agents.map((agent) => ({ type: "agent", value: agent })),
...agentAccessGroups.map((group) => ({ type: "accessGroup", value: group })),
...agentAccessGroups.map((group) => ({
type: "accessGroup",
value: group,
})),
];
const totalCount = mergedItems.length;
return (
<div className="space-y-3">
<div className="flex items-center gap-2">
<UserGroupIcon className="h-4 w-4 text-purple-600" />
<Text className="font-semibold text-gray-900">Agents</Text>
<Badge color="purple" size="xs">
<Users className="h-4 w-4 text-purple-600 dark:text-purple-400" />
<span className="font-semibold text-foreground">Agents</span>
<Badge className="bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-300 text-xs">
{totalCount}
</Badge>
</div>
{totalCount > 0 ? (
<div className="max-h-[400px] overflow-y-auto space-y-2 pr-1">
{mergedItems.map((item, index) => (
<div key={index} className="space-y-2">
<div
className="flex items-center gap-3 py-2 px-3 rounded-lg border border-gray-200 bg-white"
>
<div className="flex items-center gap-3 py-2 px-3 rounded-lg border border-border bg-background">
<div className="flex items-center gap-2 flex-1 min-w-0">
{item.type === "agent" ? (
<Tooltip title={`Full ID: ${item.value}`} placement="top">
<div className="inline-flex items-center gap-2 min-w-0">
<span className="inline-block w-1.5 h-1.5 bg-purple-500 rounded-full flex-shrink-0"></span>
<span className="text-sm font-medium text-gray-900 truncate">{getAgentDisplayName(item.value)}</span>
</div>
</Tooltip>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="inline-flex items-center gap-2 min-w-0">
<span className="inline-block w-1.5 h-1.5 bg-purple-500 rounded-full flex-shrink-0" />
<span className="text-sm font-medium text-foreground truncate">
{getAgentDisplayName(item.value)}
</span>
</div>
</TooltipTrigger>
<TooltipContent>Full ID: {item.value}</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<div className="inline-flex items-center gap-2 min-w-0">
<span className="inline-block w-1.5 h-1.5 bg-green-500 rounded-full flex-shrink-0"></span>
<span className="text-sm font-medium text-gray-900 truncate">{item.value}</span>
<span className="ml-1 px-1.5 py-0.5 text-[9px] font-semibold text-green-600 bg-green-50 border border-green-200 rounded uppercase tracking-wide flex-shrink-0">
<span className="inline-block w-1.5 h-1.5 bg-emerald-500 rounded-full flex-shrink-0" />
<span className="text-sm font-medium text-foreground truncate">
{item.value}
</span>
<span className="ml-1 px-1.5 py-0.5 text-[9px] font-semibold text-emerald-600 bg-emerald-50 border border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900 dark:text-emerald-300 rounded uppercase tracking-wide flex-shrink-0">
Group
</span>
</div>
@ -98,9 +115,11 @@ export function AgentPermissions({
))}
</div>
) : (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
<UserGroupIcon className="h-4 w-4 text-gray-400" />
<Text className="text-gray-500 text-sm">No agents or access groups configured</Text>
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-muted border border-border">
<Users className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground text-sm">
No agents or access groups configured
</span>
</div>
)}
</div>
@ -108,4 +127,3 @@ export function AgentPermissions({
}
export default AgentPermissions;

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import { Text, Badge } from "@tremor/react";
import { DatabaseIcon } from "@heroicons/react/outline";
import { Badge } from "@/components/ui/badge";
import { Database } from "lucide-react";
import { vectorStoreListCall } from "../networking";
interface VectorStoreDetails {
@ -13,10 +13,14 @@ interface VectorStorePermissionsProps {
accessToken?: string | null;
}
export function VectorStorePermissions({ vectorStores, accessToken }: VectorStorePermissionsProps) {
const [vectorStoreDetails, setVectorStoreDetails] = useState<VectorStoreDetails[]>([]);
export function VectorStorePermissions({
vectorStores,
accessToken,
}: VectorStorePermissionsProps) {
const [vectorStoreDetails, setVectorStoreDetails] = useState<
VectorStoreDetails[]
>([]);
// Fetch vector store details when component mounts
useEffect(() => {
const fetchVectorStores = async () => {
if (!accessToken || vectorStores.length === 0) return;
@ -25,6 +29,7 @@ export function VectorStorePermissions({ vectorStores, accessToken }: VectorStor
const response = await vectorStoreListCall(accessToken);
if (response.data) {
setVectorStoreDetails(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
response.data.map((store: any) => ({
vector_store_id: store.vector_store_id,
vector_store_name: store.vector_store_name,
@ -39,9 +44,10 @@ export function VectorStorePermissions({ vectorStores, accessToken }: VectorStor
fetchVectorStores();
}, [accessToken, vectorStores.length]);
// Function to get display name for vector store
const getVectorStoreDisplayName = (storeId: string) => {
const storeDetail = vectorStoreDetails.find((store) => store.vector_store_id === storeId);
const storeDetail = vectorStoreDetails.find(
(store) => store.vector_store_id === storeId,
);
if (storeDetail) {
return `${storeDetail.vector_store_name || storeDetail.vector_store_id} (${storeDetail.vector_store_id})`;
}
@ -51,9 +57,9 @@ export function VectorStorePermissions({ vectorStores, accessToken }: VectorStor
return (
<div className="space-y-3">
<div className="flex items-center gap-2">
<DatabaseIcon className="h-4 w-4 text-blue-600" />
<Text className="font-semibold text-gray-900">Vector Stores</Text>
<Badge color="blue" size="xs">
<Database className="h-4 w-4 text-blue-600 dark:text-blue-400" />
<span className="font-semibold text-foreground">Vector Stores</span>
<Badge className="bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300 text-xs">
{vectorStores.length}
</Badge>
</div>
@ -63,16 +69,18 @@ export function VectorStorePermissions({ vectorStores, accessToken }: VectorStor
{vectorStores.map((store, index) => (
<div
key={index}
className="inline-flex items-center px-3 py-1.5 rounded-lg bg-blue-50 border border-blue-200 text-blue-800 text-sm font-medium"
className="inline-flex items-center px-3 py-1.5 rounded-lg bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-900 text-blue-800 dark:text-blue-200 text-sm font-medium"
>
{getVectorStoreDisplayName(store)}
</div>
))}
</div>
) : (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
<DatabaseIcon className="h-4 w-4 text-gray-400" />
<Text className="text-gray-500 text-sm">No vector stores configured</Text>
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-muted border border-border">
<Database className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground text-sm">
No vector stores configured
</span>
</div>
)}
</div>