temp commit for branch switching

This commit is contained in:
yuneng-jiang 2026-01-16 18:13:14 -08:00
parent d2a40c8456
commit 13aebafff4
5 changed files with 471 additions and 14 deletions

View file

@ -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,
});
};

View file

@ -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}
/>
);
}

View file

@ -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>
);
}

View file

@ -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");

View file

@ -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>