keys table refactor pt2

This commit is contained in:
yuneng-jiang
2025-12-29 19:27:52 -08:00
parent 566eb35747
commit a08dae7da0
5 changed files with 103 additions and 499 deletions
@@ -0,0 +1,36 @@
import { keepPreviousData, useQuery, UseQueryResult } from "@tanstack/react-query";
import { createQueryKeys } from "../common/queryKeysFactory";
import { keyListCall } from "@/components/networking";
import { KeyResponse } from "@/components/key_team_helpers/key_list";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
const keyKeys = createQueryKeys("keys");
export interface KeysResponse {
keys: KeyResponse[];
total_count: number;
current_page: number;
total_pages: number;
}
export const useKeys = (page: number, pageSize: number): UseQueryResult<KeysResponse> => {
const { accessToken } = useAuthorized();
return useQuery<KeysResponse>({
queryKey: keyKeys.list({ page, limit: pageSize }),
queryFn: async () =>
await keyListCall(
accessToken!,
null, // organizationID
null, // teamID
null, // selectedKeyAlias
null, // userID
null, // keyHash
page,
pageSize,
),
enabled: Boolean(accessToken),
staleTime: 30000, // 30 seconds
placeholderData: keepPreviousData,
});
};
@@ -136,13 +136,7 @@ it("should render VirtualKeysTable component", () => {
keys: [mockKey],
setKeys: vi.fn(),
isLoading: false,
pagination: {
currentPage: 1,
totalPages: 1,
totalCount: 1,
},
onPageChange: vi.fn(),
pageSize: 50,
totalCount: 1,
teams: [mockTeam],
selectedTeam: null,
setSelectedTeam: vi.fn(),
@@ -166,13 +160,7 @@ it("should display key information correctly", async () => {
keys: [mockKey],
setKeys: vi.fn(),
isLoading: false,
pagination: {
currentPage: 1,
totalPages: 1,
totalCount: 1,
},
onPageChange: vi.fn(),
pageSize: 50,
totalCount: 1,
teams: [mockTeam],
selectedTeam: null,
setSelectedTeam: vi.fn(),
@@ -200,13 +188,7 @@ it("should display user email correctly", async () => {
keys: [mockKey],
setKeys: vi.fn(),
isLoading: false,
pagination: {
currentPage: 1,
totalPages: 1,
totalCount: 1,
},
onPageChange: vi.fn(),
pageSize: 50,
totalCount: 1,
teams: [mockTeam],
selectedTeam: null,
setSelectedTeam: vi.fn(),
@@ -1,12 +1,14 @@
"use client";
import { Setter } from "@/types";
import { formatNumberWithCommas, updateExistingKeys } from "@/utils/dataUtils";
import { useKeys } from "@/app/(dashboard)/hooks/keys/useKeys";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, SwitchVerticalIcon } from "@heroicons/react/outline";
import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
PaginationState,
SortingState,
useReactTable,
} from "@tanstack/react-table";
@@ -30,27 +32,10 @@ import { KeyResponse, Team } from "../key_team_helpers/key_list";
import FilterComponent, { FilterOption } from "../molecules/filter";
import { Organization } from "../networking";
import KeyInfoView from "../templates/key_info_view";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
interface VirtualKeysTableProps {
keys: KeyResponse[];
setKeys: (keys: KeyResponse[] | ((prev: KeyResponse[]) => KeyResponse[])) => void;
isLoading?: boolean;
pagination: {
currentPage: number;
totalPages: number;
totalCount: number;
};
onPageChange: (page: number) => void;
pageSize?: number;
teams: Team[] | null;
selectedTeam: Team | null;
setSelectedTeam: (team: Team | null) => void;
selectedKeyAlias: string | null;
setSelectedKeyAlias: Setter<string | null>;
organizations: Organization[] | null;
setCurrentOrg: React.Dispatch<React.SetStateAction<Organization | null>>;
refresh?: () => void;
onSortChange?: (sortBy: string, sortOrder: "asc" | "desc") => void;
currentSort?: {
sortBy: string;
@@ -63,21 +48,8 @@ interface VirtualKeysTableProps {
* The team selector and filtering have been removed so that all keys are shown.
*/
export function VirtualKeysTable({
keys,
setKeys,
isLoading = false,
pagination,
onPageChange,
pageSize = 50,
teams,
organizations,
refresh,
onSortChange,
currentSort,
}: VirtualKeysTableProps) {
const { accessToken, userId: userID, userRole, premiumUser } = useAuthorized();
const [selectedKeyId, setSelectedKeyId] = useState<string | null>(null);
export function VirtualKeysTable({ teams, organizations, onSortChange, currentSort }: VirtualKeysTableProps) {
const [selectedKey, setSelectedKey] = useState<KeyResponse | null>(null);
const [sorting, setSorting] = React.useState<SortingState>(() => {
if (currentSort) {
return [
@@ -94,22 +66,33 @@ export function VirtualKeysTable({
},
];
});
const [tablePagination, setTablePagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 100,
});
const {
data: keys,
isPending: isLoading,
refetch,
} = useKeys(tablePagination.pageIndex + 1, tablePagination.pageSize);
const totalCount = keys?.total_count || 0;
const [expandedAccordions, setExpandedAccordions] = useState<Record<string, boolean>>({});
// Use the filter logic hook
const { filters, filteredKeys, allKeyAliases, allTeams, allOrganizations, handleFilterChange, handleFilterReset } =
useFilterLogic({
keys,
keys: keys?.keys || [],
teams,
organizations,
});
// Add a useEffect to call refresh when a key is created
useEffect(() => {
if (refresh) {
if (refetch) {
const handleStorageChange = () => {
refresh();
refetch();
};
// Listen for storage events that might indicate a key was created
@@ -119,7 +102,7 @@ export function VirtualKeysTable({
window.removeEventListener("storage", handleStorageChange);
};
}
}, [refresh]);
}, [refetch]);
const columns: ColumnDef<KeyResponse>[] = [
{
@@ -145,7 +128,7 @@ export function VirtualKeysTable({
size="xs"
variant="light"
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
onClick={() => setSelectedKeyId(info.getValue() as string)}
onClick={() => setSelectedKey(info.row.original)}
>
{info.getValue() ? `${(info.getValue() as string).slice(0, 7)}...` : "-"}
</Button>
@@ -496,6 +479,7 @@ export function VirtualKeysTable({
columnResizeDirection: "ltr",
state: {
sorting,
pagination: tablePagination,
},
onSortingChange: (updaterOrValue) => {
const newSorting = typeof updaterOrValue === "function" ? updaterOrValue(sorting) : updaterOrValue;
@@ -514,10 +498,14 @@ export function VirtualKeysTable({
onSortChange?.(sortBy, sortOrder);
}
},
onPaginationChange: setTablePagination,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
enableSorting: true,
manualSorting: false,
manualPagination: true,
pageCount: Math.ceil(totalCount / tablePagination.pageSize),
});
// Update local sorting state when currentSort prop changes
@@ -534,26 +522,11 @@ export function VirtualKeysTable({
return (
<div className="w-full h-full overflow-hidden">
{selectedKeyId ? (
{selectedKey ? (
<KeyInfoView
keyId={selectedKeyId}
onClose={() => setSelectedKeyId(null)}
keyData={filteredKeys.find((k) => k.token === selectedKeyId)}
onKeyDataUpdate={(updatedKeyData) => {
setKeys((keys) =>
keys.map((key) => {
if (key.token === updatedKeyData.token) {
return updateExistingKeys(key, updatedKeyData);
}
return key;
}),
);
if (refresh) refresh(); // Minimal fix: refresh the full key list after an update
}}
onDelete={() => {
setKeys((keys) => keys.filter((key) => key.token !== selectedKeyId));
if (refresh) refresh(); // Minimal fix: refresh the full key list after a delete
}}
keyId={selectedKey.token}
onClose={() => setSelectedKey(null)}
keyData={selectedKey}
teams={allTeams}
/>
) : (
@@ -572,26 +545,30 @@ export function VirtualKeysTable({
Showing{" "}
{isLoading
? "..."
: `${(pagination.currentPage - 1) * pageSize + 1} - ${Math.min(pagination.currentPage * pageSize, pagination.totalCount)}`}{" "}
of {isLoading ? "..." : pagination.totalCount} results
: `${table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1} - ${Math.min(
(table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize,
totalCount,
)}`}{" "}
of {isLoading ? "..." : totalCount} results
</span>
<div className="inline-flex items-center gap-2">
<span className="text-sm text-gray-700">
Page {isLoading ? "..." : pagination.currentPage} of {isLoading ? "..." : pagination.totalPages}
Page {isLoading ? "..." : table.getState().pagination.pageIndex + 1} of{" "}
{isLoading ? "..." : table.getPageCount()}
</span>
<button
onClick={() => onPageChange(pagination.currentPage - 1)}
disabled={isLoading || pagination.currentPage === 1}
onClick={() => table.previousPage()}
disabled={isLoading || !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={() => onPageChange(pagination.currentPage + 1)}
disabled={isLoading || pagination.currentPage === pagination.totalPages}
onClick={() => table.nextPage()}
disabled={isLoading || !table.getCanNextPage()}
className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Next
@@ -1,14 +1,11 @@
"use client";
import { Setter } from "@/types";
import { Button, Col, Grid, Text, TextInput, Title } from "@tremor/react";
import { Form, InputNumber, Modal } from "antd";
import { Form } from "antd";
import { add } from "date-fns";
import React, { useEffect, useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { VirtualKeysTable } from "../VirtualKeysPage/VirtualKeysTable";
import useKeyList, { KeyResponse, Team } from "../key_team_helpers/key_list";
import NotificationManager from "../molecules/notifications_manager";
import { keyDeleteCall, Organization, regenerateKeyCall } from "../networking";
import { Organization } from "../networking";
// Define the props type
interface ViewKeyTableProps {
@@ -31,27 +28,17 @@ interface ViewKeyTableProps {
}
const ViewKeyTable: React.FC<ViewKeyTableProps> = ({
userID,
userRole,
accessToken,
selectedTeam,
setSelectedTeam,
data,
setData,
teams,
premiumUser,
currentOrg,
organizations,
setCurrentOrg,
selectedKeyAlias,
setSelectedKeyAlias,
createClicked,
setAccessToken,
}) => {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [keyToDelete, setKeyToDelete] = useState<string | null>(null);
const [deleteConfirmInput, setDeleteConfirmInput] = useState("");
const { keys, isLoading, error, pagination, refresh, setKeys } = useKeyList({
selectedTeam: selectedTeam || undefined,
currentOrg,
@@ -65,369 +52,9 @@ const ViewKeyTable: React.FC<ViewKeyTableProps> = ({
refresh({ page: newPage });
};
const [selectedToken, setSelectedToken] = useState<KeyResponse | null>(null);
const [regenerateDialogVisible, setRegenerateDialogVisible] = useState(false);
const [regeneratedKey, setRegeneratedKey] = useState<string | null>(null);
const [regenerateFormData, setRegenerateFormData] = useState<any>(null);
const [regenerateForm] = Form.useForm();
const [newExpiryTime, setNewExpiryTime] = useState<string | null>(null);
useEffect(() => {
const calculateNewExpiryTime = (duration: string | undefined) => {
if (!duration) {
return null;
}
try {
const now = new Date();
let newExpiry: Date;
if (duration.endsWith("s")) {
newExpiry = add(now, { seconds: parseInt(duration) });
} else if (duration.endsWith("h")) {
newExpiry = add(now, { hours: parseInt(duration) });
} else if (duration.endsWith("d")) {
newExpiry = add(now, { days: parseInt(duration) });
} else {
throw new Error("Invalid duration format");
}
return newExpiry.toLocaleString("en-US", {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: true,
});
} catch (error) {
return null;
}
};
console.log("in calculateNewExpiryTime for selectedToken", selectedToken);
// When a new duration is entered
if (regenerateFormData?.duration) {
setNewExpiryTime(calculateNewExpiryTime(regenerateFormData.duration));
} else {
setNewExpiryTime(null);
}
console.log("calculateNewExpiryTime:", newExpiryTime);
}, [selectedToken, regenerateFormData?.duration]);
const confirmDelete = async () => {
if (keyToDelete == null || keys == null) {
return;
}
try {
if (!accessToken) return;
await keyDeleteCall(accessToken, keyToDelete);
// Successfully completed the deletion. Update the state to trigger a rerender.
const filteredKeys = keys.filter((item) => item.token !== keyToDelete);
setKeys(filteredKeys);
} catch (error) {
NotificationManager.error({ description: "Error deleting the key" });
}
// Close the confirmation modal and reset the keyToDelete
setIsDeleteModalOpen(false);
setKeyToDelete(null);
setDeleteConfirmInput("");
};
const cancelDelete = () => {
// Close the confirmation modal and reset the keyToDelete
setIsDeleteModalOpen(false);
setKeyToDelete(null);
setDeleteConfirmInput("");
};
const handleRegenerateClick = (token: any) => {
setSelectedToken(token);
setNewExpiryTime(null);
regenerateForm.setFieldsValue({
key_alias: token.key_alias,
max_budget: token.max_budget,
tpm_limit: token.tpm_limit,
rpm_limit: token.rpm_limit,
duration: token.duration || "",
});
setRegenerateDialogVisible(true);
};
const handleRegenerateFormChange = (field: string, value: any) => {
setRegenerateFormData((prev: any) => ({
...prev,
[field]: value,
}));
};
const handleRegenerateKey = async () => {
if (!premiumUser) {
NotificationManager.warning({
description: "Regenerate Virtual Key is an Enterprise feature. Please upgrade to use this feature.",
});
return;
}
if (selectedToken == null) {
return;
}
try {
const formValues = await regenerateForm.validateFields();
if (!accessToken) return;
const response = await regenerateKeyCall(accessToken, selectedToken.token || selectedToken.token_id, formValues);
setRegeneratedKey(response.key);
// Update the data state with the new key_name
if (data) {
const updatedData = data.map((item) =>
item.token === selectedToken?.token ? { ...item, key_name: response.key_name, ...formValues } : item,
);
setData(updatedData);
}
setRegenerateDialogVisible(false);
regenerateForm.resetFields();
NotificationManager.success({ description: "Virtual Key regenerated successfully" });
} catch (error) {
console.error("Error regenerating key:", error);
NotificationManager.error({ description: "Failed to regenerate Virtual Key" });
}
};
return (
<div>
<VirtualKeysTable
keys={keys}
setKeys={setKeys}
isLoading={isLoading}
pagination={pagination}
onPageChange={handlePageChange}
pageSize={100}
teams={teams}
selectedTeam={selectedTeam}
setSelectedTeam={setSelectedTeam}
organizations={organizations}
setCurrentOrg={setCurrentOrg}
refresh={refresh}
selectedKeyAlias={selectedKeyAlias}
setSelectedKeyAlias={setSelectedKeyAlias}
/>
{isDeleteModalOpen &&
(() => {
const keyData = keys?.find((k) => k.token === keyToDelete);
const keyName = keyData?.key_alias || keyData?.token_id || keyToDelete;
const isValid = deleteConfirmInput === keyName;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl w-full max-w-2xl min-h-[380px] py-6 overflow-hidden transform transition-all flex flex-col justify-between">
<div>
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900">Delete Key</h3>
<button
onClick={() => {
cancelDelete();
setDeleteConfirmInput("");
}}
className="text-gray-400 hover:text-gray-500 focus:outline-none"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="px-6 py-4">
<div className="flex items-start gap-3 p-4 bg-red-50 border border-red-100 rounded-md mb-5">
<div className="text-red-500 mt-0.5">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.082 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<div>
<p className="text-base font-medium text-red-600">
Warning: You are about to delete this Virtual Key.
</p>
<p className="text-base text-red-600 mt-2">
This action is irreversible and will immediately revoke access for any applications using this
key.
</p>
</div>
</div>
<p className="text-base text-gray-600 mb-5">Are you sure you want to delete this Virtual Key?</p>
<div className="mb-5">
<label className="block text-base font-medium text-gray-700 mb-2">
{`Type `}
<span className="underline">{keyName}</span>
{` to confirm deletion:`}
</label>
<input
type="text"
value={deleteConfirmInput}
onChange={(e) => setDeleteConfirmInput(e.target.value)}
placeholder="Enter key name exactly"
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-base"
autoFocus
/>
</div>
</div>
</div>
<div className="px-6 py-4 bg-gray-50 flex justify-end gap-4">
<button
onClick={() => {
cancelDelete();
setDeleteConfirmInput("");
}}
className="px-5 py-3 bg-white border border-gray-300 rounded-md text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Cancel
</button>
<button
onClick={confirmDelete}
disabled={!isValid}
className={`px-5 py-3 rounded-md text-base font-medium text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 ${isValid ? "bg-red-600 hover:bg-red-700" : "bg-red-300 cursor-not-allowed"}`}
>
Delete Virtual Key
</button>
</div>
</div>
</div>
);
})()}
{/* Regenerate Key Form Modal */}
<Modal
title="Regenerate Virtual Key"
visible={regenerateDialogVisible}
onCancel={() => {
setRegenerateDialogVisible(false);
regenerateForm.resetFields();
}}
footer={[
<Button
key="cancel"
onClick={() => {
setRegenerateDialogVisible(false);
regenerateForm.resetFields();
}}
className="mr-2"
>
Cancel
</Button>,
<Button key="regenerate" onClick={handleRegenerateKey} disabled={!premiumUser}>
{premiumUser ? "Regenerate" : "Upgrade to Regenerate"}
</Button>,
]}
>
{premiumUser ? (
<Form
form={regenerateForm}
layout="vertical"
onValuesChange={(changedValues, allValues) => {
if ("duration" in changedValues) {
handleRegenerateFormChange("duration", changedValues.duration);
}
}}
>
<Form.Item name="key_alias" label="Key Alias">
<TextInput disabled={true} />
</Form.Item>
<Form.Item name="max_budget" label="Max Budget (USD)">
<InputNumber step={0.01} precision={2} style={{ width: "100%" }} />
</Form.Item>
<Form.Item name="tpm_limit" label="TPM Limit">
<InputNumber style={{ width: "100%" }} />
</Form.Item>
<Form.Item name="rpm_limit" label="RPM Limit">
<InputNumber style={{ width: "100%" }} />
</Form.Item>
<Form.Item name="duration" label="Expire Key (eg: 30s, 30h, 30d)" className="mt-8">
<TextInput placeholder="" />
</Form.Item>
<div className="mt-2 text-sm text-gray-500">
Current expiry:{" "}
{selectedToken?.expires != null ? new Date(selectedToken.expires).toLocaleString() : "Never"}
</div>
{newExpiryTime && <div className="mt-2 text-sm text-green-600">New expiry: {newExpiryTime}</div>}
</Form>
) : (
<div>
<p className="mb-2 text-gray-500 italic text-[12px]">Upgrade to use this feature</p>
<Button variant="primary" className="mb-2">
<a href="https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat" target="_blank">
Get Free Trial
</a>
</Button>
</div>
)}
</Modal>
{/* Regenerated Key Display Modal */}
{regeneratedKey && (
<Modal
visible={!!regeneratedKey}
onCancel={() => setRegeneratedKey(null)}
footer={[
<Button key="close" onClick={() => setRegeneratedKey(null)}>
Close
</Button>,
]}
>
<Grid numItems={1} className="gap-2 w-full">
<Title>Regenerated Key</Title>
<Col numColSpan={1}>
<p>
Please replace your old key with the new key generated. For security reasons,{" "}
<b>you will not be able to view it again</b> through your LiteLLM account. If you lose this secret key,
you will need to generate a new one.
</p>
</Col>
<Col numColSpan={1}>
<Text className="mt-3">Key Alias:</Text>
<div
style={{
background: "#f8f8f8",
padding: "10px",
borderRadius: "5px",
marginBottom: "10px",
}}
>
<pre style={{ wordWrap: "break-word", whiteSpace: "normal" }}>
{selectedToken?.key_alias || "No alias set"}
</pre>
</div>
<Text className="mt-3">New Virtual Key:</Text>
<div
style={{
background: "#f8f8f8",
padding: "10px",
borderRadius: "5px",
marginBottom: "10px",
}}
>
<pre style={{ wordWrap: "break-word", whiteSpace: "normal" }}>{regeneratedKey}</pre>
</div>
<CopyToClipboard
text={regeneratedKey}
onCopy={() => NotificationManager.success({ description: "Virtual Key copied to clipboard" })}
>
<Button className="mt-3">Copy Virtual Key</Button>
</CopyToClipboard>
</Col>
</Grid>
</Modal>
)}
<VirtualKeysTable teams={teams} organizations={organizations} />
</div>
);
};
@@ -1,23 +1,23 @@
"use client";
import React, { useState, useEffect } from "react";
import {
userInfoCall,
modelAvailableCall,
getProxyUISettings,
Organization,
keyInfoCall,
getProxyBaseUrl,
} from "./networking";
import { fetchTeams } from "./common_components/fetch_teams";
import { Grid, Col } from "@tremor/react";
import CreateKey from "./organisms/create_key_button";
import ViewKeyTable from "./templates/view_key_table";
import Onboarding from "../app/onboarding/page";
import { useSearchParams } from "next/navigation";
import { KeyResponse, Team } from "./key_team_helpers/key_list";
import { jwtDecode } from "jwt-decode";
import { Typography } from "antd";
import { clearTokenCookies } from "@/utils/cookieUtils";
import { Col, Grid } from "@tremor/react";
import { Typography } from "antd";
import { jwtDecode } from "jwt-decode";
import { useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react";
import Onboarding from "../app/onboarding/page";
import { fetchTeams } from "./common_components/fetch_teams";
import { KeyResponse, Team } from "./key_team_helpers/key_list";
import {
getProxyBaseUrl,
getProxyUISettings,
keyInfoCall,
modelAvailableCall,
Organization,
userInfoCall,
} from "./networking";
import CreateKey from "./organisms/create_key_button";
import { VirtualKeysTable } from "./VirtualKeysPage/VirtualKeysTable";
export interface ProxySettings {
PROXY_BASE_URL: string | null;
@@ -361,25 +361,7 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
addKey={addKey}
premiumUser={premiumUser}
/>
<ViewKeyTable
userID={userID}
userRole={userRole}
accessToken={accessToken}
selectedTeam={selectedTeam ? selectedTeam : null}
setSelectedTeam={setSelectedTeam}
selectedKeyAlias={selectedKeyAlias}
setSelectedKeyAlias={setSelectedKeyAlias}
data={keys}
setData={setKeys}
premiumUser={premiumUser}
teams={teams}
currentOrg={currentOrg}
setCurrentOrg={setCurrentOrg}
organizations={organizations}
createClicked={createClicked}
setAccessToken={setAccessToken}
/>
<VirtualKeysTable teams={teams} organizations={organizations} />
</Col>
</Grid>
</div>