mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
temp commit for branch switching
This commit is contained in:
parent
d2a40c8456
commit
13aebafff4
5 changed files with 471 additions and 14 deletions
|
|
@ -13,6 +13,20 @@ export interface KeysResponse {
|
|||
total_pages: number;
|
||||
}
|
||||
|
||||
export interface DeletedKeyResponse {
|
||||
token: string;
|
||||
token_id: string;
|
||||
key_name: string;
|
||||
key_alias: string;
|
||||
}
|
||||
|
||||
export interface DeletedKeysResponse {
|
||||
keys: DeletedKeyResponse[];
|
||||
total_count: number;
|
||||
current_page: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
export const useKeys = (page: number, pageSize: number): UseQueryResult<KeysResponse> => {
|
||||
const { accessToken } = useAuthorized();
|
||||
|
||||
|
|
@ -34,3 +48,17 @@ export const useKeys = (page: number, pageSize: number): UseQueryResult<KeysResp
|
|||
placeholderData: keepPreviousData,
|
||||
});
|
||||
};
|
||||
|
||||
export const deletedKeyKeys = createQueryKeys("deletedKeys");
|
||||
export const useDeletedKeys = (page: number, pageSize: number): UseQueryResult<KeysResponse> => {
|
||||
const { accessToken } = useAuthorized();
|
||||
|
||||
return useQuery<KeysResponse>({
|
||||
queryKey: deletedKeyKeys.list({ page, limit: pageSize }),
|
||||
queryFn: async () =>
|
||||
await keyListCall(accessToken!, null, null, null, null, null, page, pageSize, null, null, null, "deleted"),
|
||||
enabled: Boolean(accessToken),
|
||||
staleTime: 30000, // 30 seconds
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
"use client";
|
||||
import { useState } from "react";
|
||||
import { useDeletedKeys } from "@/app/(dashboard)/hooks/keys/useKeys";
|
||||
import { DeletedKeysTable } from "./DeletedKeysTable/DeletedKeysTable";
|
||||
|
||||
export default function DeletedKeysPage() {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize] = useState(50);
|
||||
|
||||
const {
|
||||
data: keysData,
|
||||
isPending: isLoading,
|
||||
isFetching,
|
||||
} = useDeletedKeys(pageIndex + 1, pageSize);
|
||||
|
||||
return (
|
||||
<DeletedKeysTable
|
||||
keys={keysData?.keys || []}
|
||||
totalCount={keysData?.total_count || 0}
|
||||
isLoading={isLoading}
|
||||
isFetching={isFetching}
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
onPageChange={setPageIndex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,397 @@
|
|||
"use client";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { ChevronDownIcon, ChevronUpIcon, SwitchVerticalIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
} from "@tremor/react";
|
||||
import { Tooltip } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { KeyResponse } from "../../key_team_helpers/key_list";
|
||||
|
||||
interface DeletedKeysTableProps {
|
||||
keys: KeyResponse[];
|
||||
totalCount: number;
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
onPageChange: (pageIndex: number) => void;
|
||||
}
|
||||
|
||||
export function DeletedKeysTable({
|
||||
keys,
|
||||
totalCount,
|
||||
isLoading,
|
||||
isFetching,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
onPageChange,
|
||||
}: DeletedKeysTableProps) {
|
||||
const [sorting, setSorting] = useState<SortingState>([
|
||||
{
|
||||
id: "deleted_at",
|
||||
desc: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const [tablePagination, setTablePagination] = useState<PaginationState>({
|
||||
pageIndex,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
// Sync pagination state when prop changes
|
||||
React.useEffect(() => {
|
||||
setTablePagination({ pageIndex, pageSize });
|
||||
}, [pageIndex, pageSize]);
|
||||
|
||||
const columns: ColumnDef<KeyResponse>[] = [
|
||||
{
|
||||
id: "token",
|
||||
accessorKey: "token",
|
||||
header: "Key ID",
|
||||
size: 150,
|
||||
cell: (info) => {
|
||||
const value = info.getValue() as string;
|
||||
return (
|
||||
<Tooltip title={value}>
|
||||
<span className="font-mono text-blue-500 text-xs truncate block">
|
||||
{value || "-"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "key_alias",
|
||||
accessorKey: "key_alias",
|
||||
header: "Key Alias",
|
||||
size: 150,
|
||||
cell: (info) => {
|
||||
const value = info.getValue() as string;
|
||||
return (
|
||||
<Tooltip title={value}>
|
||||
<span className="font-mono text-xs truncate block">
|
||||
{value ?? "-"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "team_alias",
|
||||
accessorKey: "team_alias",
|
||||
header: "Team Alias",
|
||||
size: 120,
|
||||
cell: (info) => {
|
||||
const value = info.getValue() as string;
|
||||
return (
|
||||
<span className="truncate block">
|
||||
{value || "-"}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "spend",
|
||||
accessorKey: "spend",
|
||||
header: "Spend (USD)",
|
||||
size: 100,
|
||||
cell: (info) => formatNumberWithCommas(info.getValue() as number, 4),
|
||||
},
|
||||
{
|
||||
id: "max_budget",
|
||||
accessorKey: "max_budget",
|
||||
header: "Budget (USD)",
|
||||
size: 110,
|
||||
cell: (info) => {
|
||||
const maxBudget = info.getValue() as number | null;
|
||||
if (maxBudget === null) {
|
||||
return "Unlimited";
|
||||
}
|
||||
return `$${formatNumberWithCommas(maxBudget)}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "user_email",
|
||||
accessorKey: "user_email",
|
||||
header: "User Email",
|
||||
size: 160,
|
||||
cell: (info) => {
|
||||
const value = info.getValue() as string;
|
||||
return (
|
||||
<Tooltip title={value}>
|
||||
<span className="font-mono text-xs truncate block">
|
||||
{value ?? "-"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "user_id",
|
||||
accessorKey: "user_id",
|
||||
header: "User ID",
|
||||
size: 120,
|
||||
cell: (info) => {
|
||||
const userId = info.getValue() as string | null;
|
||||
return (
|
||||
<Tooltip title={userId || undefined}>
|
||||
<span className="truncate block">
|
||||
{userId || "-"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "created_at",
|
||||
accessorKey: "created_at",
|
||||
header: "Created At",
|
||||
size: 120,
|
||||
cell: (info) => {
|
||||
const value = info.getValue();
|
||||
return value ? new Date(value as string).toLocaleDateString() : "-";
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "created_by",
|
||||
accessorKey: "created_by",
|
||||
header: "Created By",
|
||||
size: 120,
|
||||
cell: (info) => {
|
||||
const value = (info.row.original as any).created_by as string | null | undefined;
|
||||
return (
|
||||
<Tooltip title={value || undefined}>
|
||||
<span className="truncate block">
|
||||
{value || "-"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "deleted_at",
|
||||
accessorKey: "deleted_at",
|
||||
header: "Deleted At",
|
||||
size: 120,
|
||||
cell: (info) => {
|
||||
const value = (info.row.original as any).deleted_at as string | null | undefined;
|
||||
return value ? new Date(value).toLocaleDateString() : "-";
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "deleted_by",
|
||||
accessorKey: "deleted_by",
|
||||
header: "Deleted By",
|
||||
size: 120,
|
||||
cell: (info) => {
|
||||
const value = (info.row.original as any).deleted_by as string | null | undefined;
|
||||
return (
|
||||
<Tooltip title={value || undefined}>
|
||||
<span className="truncate block">
|
||||
{value || "-"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data: keys,
|
||||
columns,
|
||||
columnResizeMode: "onChange",
|
||||
columnResizeDirection: "ltr",
|
||||
state: {
|
||||
sorting,
|
||||
pagination: tablePagination,
|
||||
},
|
||||
onSortingChange: setSorting,
|
||||
onPaginationChange: (updater) => {
|
||||
const newPagination = typeof updater === "function" ? updater(tablePagination) : updater;
|
||||
setTablePagination(newPagination);
|
||||
onPageChange(newPagination.pageIndex);
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
enableSorting: true,
|
||||
manualSorting: false,
|
||||
manualPagination: true,
|
||||
pageCount: Math.ceil(totalCount / pageSize),
|
||||
});
|
||||
|
||||
const { pageIndex: currentPageIndex } = table.getState().pagination;
|
||||
const start = currentPageIndex * pageSize + 1;
|
||||
const end = Math.min((currentPageIndex + 1) * pageSize, totalCount);
|
||||
const rangeLabel = `${start} - ${end}`;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full overflow-hidden">
|
||||
<div className="border-b py-4 flex-1 overflow-hidden">
|
||||
<div className="flex items-center justify-between w-full mb-4">
|
||||
{isLoading || isFetching ? (
|
||||
<span className="inline-flex text-sm text-gray-700">Loading...</span>
|
||||
) : (
|
||||
<span className="inline-flex text-sm text-gray-700">
|
||||
Showing {rangeLabel} of {totalCount} results
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="inline-flex items-center gap-2">
|
||||
{isLoading || isFetching ? (
|
||||
<span className="text-sm text-gray-700">Loading...</span>
|
||||
) : (
|
||||
<span className="text-sm text-gray-700">
|
||||
Page {currentPageIndex + 1} of {table.getPageCount()}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={isLoading || isFetching || !table.getCanPreviousPage()}
|
||||
className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={isLoading || isFetching || !table.getCanNextPage()}
|
||||
className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[75vh] overflow-auto">
|
||||
<div className="rounded-lg custom-border relative">
|
||||
<div className="overflow-x-auto">
|
||||
<Table className="[&_td]:py-0.5 [&_th]:py-1" style={{ width: table.getCenterTotalSize() }}>
|
||||
<TableHead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHeaderCell
|
||||
key={header.id}
|
||||
data-header-id={header.id}
|
||||
className={`py-1 h-8 relative hover:bg-gray-50`}
|
||||
style={{
|
||||
width: header.getSize(),
|
||||
position: "relative",
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
const resizer = document.querySelector(`[data-header-id="${header.id}"] .resizer`);
|
||||
if (resizer) {
|
||||
(resizer as HTMLElement).style.opacity = "0.5";
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
const resizer = document.querySelector(`[data-header-id="${header.id}"] .resizer`);
|
||||
if (resizer && !header.column.getIsResizing()) {
|
||||
(resizer as HTMLElement).style.opacity = "0";
|
||||
}
|
||||
}}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center">
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</div>
|
||||
<div className="w-4">
|
||||
{header.column.getIsSorted() ? (
|
||||
{
|
||||
asc: <ChevronUpIcon className="h-4 w-4 text-blue-500" />,
|
||||
desc: <ChevronDownIcon className="h-4 w-4 text-blue-500" />,
|
||||
}[header.column.getIsSorted() as string]
|
||||
) : (
|
||||
<SwitchVerticalIcon className="h-4 w-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
onDoubleClick={() => header.column.resetSize()}
|
||||
onMouseDown={header.getResizeHandler()}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
className={`resizer ${table.options.columnResizeDirection} ${header.column.getIsResizing() ? "isResizing" : ""}`}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
height: "100%",
|
||||
width: "5px",
|
||||
background: header.column.getIsResizing() ? "#3b82f6" : "transparent",
|
||||
cursor: "col-resize",
|
||||
userSelect: "none",
|
||||
touchAction: "none",
|
||||
opacity: header.column.getIsResizing() ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TableHeaderCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading || isFetching ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>🚅 Loading keys...</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : keys.length > 0 ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id} className="h-8">
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
style={{
|
||||
width: cell.column.getSize(),
|
||||
maxWidth: "8-x",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>No deleted keys found</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ const defaultServerRootPath = "/";
|
|||
export let serverRootPath = defaultServerRootPath;
|
||||
export let proxyBaseUrl = defaultProxyBaseUrl;
|
||||
if (isLocal != true) {
|
||||
console.log = function () {};
|
||||
console.log = function () { };
|
||||
}
|
||||
|
||||
const getWindowLocation = () => {
|
||||
|
|
@ -3270,6 +3270,7 @@ export const keyListCall = async (
|
|||
sortBy: string | null = null,
|
||||
sortOrder: string | null = null,
|
||||
expand: string | null = null,
|
||||
status: string | null = null,
|
||||
) => {
|
||||
/**
|
||||
* Get all available teams on proxy
|
||||
|
|
@ -3319,6 +3320,10 @@ export const keyListCall = async (
|
|||
queryParams.append("expand", expand);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
queryParams.append("status", status);
|
||||
}
|
||||
|
||||
queryParams.append("return_full_object", "true");
|
||||
queryParams.append("include_team_keys", "true");
|
||||
queryParams.append("include_created_by_keys", "true");
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import AuditLogs from "./audit_logs";
|
|||
import { getTimeRangeDisplay } from "./logs_utils";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { truncateString } from "@/utils/textUtils";
|
||||
import DeletedKeysPage from "../DeletedKeysPage/DeletedKeysPage";
|
||||
|
||||
interface SpendLogsTableProps {
|
||||
accessToken: string | null;
|
||||
|
|
@ -355,7 +356,7 @@ export default function SpendLogsTable({
|
|||
sessionLogs.data?.data?.map((log) => ({
|
||||
...log,
|
||||
onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash),
|
||||
onSessionClick: (sessionId: string) => {},
|
||||
onSessionClick: (sessionId: string) => { },
|
||||
})) || [];
|
||||
|
||||
// Add this function to handle manual refresh
|
||||
|
|
@ -502,6 +503,7 @@ export default function SpendLogsTable({
|
|||
<TabList>
|
||||
<Tab>Request Logs</Tab>
|
||||
<Tab>Audit Logs</Tab>
|
||||
<Tab>Deleted Keys</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
|
|
@ -537,7 +539,7 @@ export default function SpendLogsTable({
|
|||
data={sessionData}
|
||||
renderSubComponent={RequestViewer}
|
||||
getRowCanExpand={() => true}
|
||||
// Optionally: add session-specific row expansion state
|
||||
// Optionally: add session-specific row expansion state
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -597,9 +599,8 @@ export default function SpendLogsTable({
|
|||
{quickSelectOptions.map((option) => (
|
||||
<button
|
||||
key={option.label}
|
||||
className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ${
|
||||
displayLabel === option.label ? "bg-blue-50 text-blue-600" : ""
|
||||
}`}
|
||||
className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ${displayLabel === option.label ? "bg-blue-50 text-blue-600" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setEndTime(moment().format("YYYY-MM-DDTHH:mm"));
|
||||
setStartTime(
|
||||
|
|
@ -617,9 +618,8 @@ export default function SpendLogsTable({
|
|||
))}
|
||||
<div className="border-t my-2" />
|
||||
<button
|
||||
className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ${
|
||||
isCustomDate ? "bg-blue-50 text-blue-600" : ""
|
||||
}`}
|
||||
className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ${isCustomDate ? "bg-blue-50 text-blue-600" : ""
|
||||
}`}
|
||||
onClick={() => setIsCustomDate(!isCustomDate)}
|
||||
>
|
||||
Custom Range
|
||||
|
|
@ -749,6 +749,7 @@ export default function SpendLogsTable({
|
|||
allTeams={allTeams}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel><DeletedKeysPage /></TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
|
|
@ -932,11 +933,10 @@ export function RequestViewer({ row }: { row: Row<LogEntry> }) {
|
|||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Status:</span>
|
||||
<span
|
||||
className={`px-2 py-1 rounded-md text-xs font-medium inline-block text-center w-16 ${
|
||||
(row.original.metadata?.status || "Success").toLowerCase() !== "failure"
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
}`}
|
||||
className={`px-2 py-1 rounded-md text-xs font-medium inline-block text-center w-16 ${(row.original.metadata?.status || "Success").toLowerCase() !== "failure"
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
}`}
|
||||
>
|
||||
{(row.original.metadata?.status || "Success").toLowerCase() !== "failure" ? "Success" : "Failure"}
|
||||
</span>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue