mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
refactor(ui): replace success-view divs in regenerate key modal with antd
Use Flex, Typography.Paragraph (with copyable), and Typography.Text
instead of raw divs + code block + CopyToClipboard wrapper. Drops the
direct react-copy-to-clipboard dependency in this component in favor
of antd's native copyable support.
Also fixes two test issues surfaced when running the e2e locally:
- RegenerateKeyModal.test.tsx no longer mocks react-copy-to-clipboard
(the component no longer imports it), removing the CJS require()
inside an ESM mock factory flagged by Greptile.
- keys.spec.ts scopes the Regenerate and Copy lookups to the modal.
The Regenerate button has an icon whose aria-label ("sync") is
concatenated into the button's accessible name, so an exact-match
lookup on "Regenerate" failed; and the new Paragraph copyable
renders a generic "Copy" button that collided with the other
copyable fields on the key info view.
This commit is contained in:
parent
e42baeb5ab
commit
15f7cc9134
3 changed files with 39 additions and 85 deletions
|
|
@ -61,11 +61,16 @@ test.describe("Proxy Admin - Keys", () => {
|
|||
await expect(page.getByText("Back to Keys")).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
await page.getByRole("button", { name: "Regenerate Key" }).click();
|
||||
await page.getByRole("button", { name: "Regenerate", exact: true }).click();
|
||||
|
||||
// Scope to the modal — the Regenerate button has an icon whose aria-label
|
||||
// ("sync") is concatenated into the button's accessible name, and the
|
||||
// "Regenerate Key" button is still in the DOM behind the modal.
|
||||
const modal = page.locator(".ant-modal:visible");
|
||||
await modal.getByRole("button", { name: /Regenerate/ }).click();
|
||||
|
||||
// Success view shows the warning banner and a Copy button for the regenerated key
|
||||
await expect(page.getByText("Save it now, you will not see it again")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByRole("button", { name: /Copy/ })).toBeVisible({ timeout: 10_000 });
|
||||
await expect(modal.getByText("Save it now, you will not see it again")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(modal.getByRole("button", { name: "Copy", exact: true })).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("Update key TPM and RPM limits", async ({ page }) => {
|
||||
|
|
|
|||
|
|
@ -10,14 +10,6 @@ vi.mock("../networking", () => ({
|
|||
regenerateKeyCall: (...args: unknown[]) => mockRegenerateKeyCall(...args),
|
||||
}));
|
||||
|
||||
// Mock CopyToClipboard to render a simple button
|
||||
vi.mock("react-copy-to-clipboard", () => ({
|
||||
CopyToClipboard: ({ children, onCopy }: { children: React.ReactElement; onCopy: () => void }) => {
|
||||
const React = require("react");
|
||||
return React.cloneElement(children, { onClick: onCopy });
|
||||
},
|
||||
}));
|
||||
|
||||
const makeToken = (overrides: Partial<KeyResponse> = {}): KeyResponse =>
|
||||
({
|
||||
token: "token-hash-123",
|
||||
|
|
@ -71,12 +63,7 @@ describe("RegenerateKeyModal", () => {
|
|||
});
|
||||
|
||||
it("should display 'Never' when token has no expires", () => {
|
||||
renderWithProviders(
|
||||
<RegenerateKeyModal
|
||||
{...defaultProps}
|
||||
selectedToken={makeToken({ expires: undefined })}
|
||||
/>,
|
||||
);
|
||||
renderWithProviders(<RegenerateKeyModal {...defaultProps} selectedToken={makeToken({ expires: undefined })} />);
|
||||
expect(screen.getByText("Current expiry: Never")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
@ -119,9 +106,7 @@ describe("RegenerateKeyModal", () => {
|
|||
|
||||
it("should display grace period recommendation text", () => {
|
||||
renderWithProviders(<RegenerateKeyModal {...defaultProps} />);
|
||||
expect(
|
||||
screen.getByText("Recommended: 24h to 72h for production keys"),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText("Recommended: 24h to 72h for production keys")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call regenerateKeyCall and show success view on successful regeneration", async () => {
|
||||
|
|
@ -222,12 +207,7 @@ describe("RegenerateKeyModal", () => {
|
|||
token: "new-token-hash",
|
||||
});
|
||||
|
||||
renderWithProviders(
|
||||
<RegenerateKeyModal
|
||||
{...defaultProps}
|
||||
selectedToken={makeToken({ key_alias: undefined })}
|
||||
/>,
|
||||
);
|
||||
renderWithProviders(<RegenerateKeyModal {...defaultProps} selectedToken={makeToken({ key_alias: undefined })} />);
|
||||
await user.click(screen.getByRole("button", { name: /Regenerate/ }));
|
||||
|
||||
await waitFor(() => {
|
||||
|
|
@ -237,9 +217,7 @@ describe("RegenerateKeyModal", () => {
|
|||
|
||||
it("should not call regenerateKeyCall when selectedToken is null", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(
|
||||
<RegenerateKeyModal {...defaultProps} selectedToken={null} />,
|
||||
);
|
||||
renderWithProviders(<RegenerateKeyModal {...defaultProps} selectedToken={null} />);
|
||||
|
||||
// The form shouldn't even be populated, but we check the button doesn't trigger a call
|
||||
const regenerateBtn = screen.queryByRole("button", { name: /Regenerate/ });
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { CopyOutlined, SyncOutlined } from "@ant-design/icons";
|
||||
import { Alert, Button, Col, Form, Input, InputNumber, Modal, Row, Space, Typography } from "antd";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Alert, Button, Col, Flex, Form, Input, InputNumber, Modal, Row, Space, Typography } from "antd";
|
||||
import { add } from "date-fns";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
import { KeyResponse } from "../key_team_helpers/key_list";
|
||||
import NotificationManager from "../molecules/notifications_manager";
|
||||
import { regenerateKeyCall } from "../networking";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
||||
interface RegenerateKeyModalProps {
|
||||
selectedToken: KeyResponse | null;
|
||||
|
|
@ -174,54 +171,27 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
}
|
||||
>
|
||||
{regeneratedKey ? (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
banner
|
||||
message="Save it now, you will not see it again"
|
||||
/>
|
||||
<Flex vertical gap="middle">
|
||||
<Alert type="warning" showIcon message="Save it now, you will not see it again" />
|
||||
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: "#8c8c8c", marginBottom: 2 }}>Key Alias</div>
|
||||
<div style={{ fontSize: 14, color: "#595959" }}>
|
||||
{selectedToken?.key_alias || "No alias set"}
|
||||
</div>
|
||||
</div>
|
||||
<Flex vertical gap={2}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Key Alias
|
||||
</Text>
|
||||
<Text>{selectedToken?.key_alias || "No alias set"}</Text>
|
||||
</Flex>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
background: "#f5f5f5",
|
||||
border: "1px solid #d9d9d9",
|
||||
borderRadius: 6,
|
||||
padding: "10px 12px",
|
||||
<Paragraph
|
||||
code
|
||||
copyable={{
|
||||
text: regeneratedKey,
|
||||
onCopy: () => NotificationManager.success("Virtual Key copied to clipboard"),
|
||||
}}
|
||||
style={{ marginBottom: 0, wordBreak: "break-all" }}
|
||||
>
|
||||
<code
|
||||
style={{
|
||||
flex: 1,
|
||||
fontFamily: "SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace",
|
||||
fontSize: 13,
|
||||
color: "#262626",
|
||||
wordBreak: "break-all",
|
||||
lineHeight: 1.5,
|
||||
}}
|
||||
>
|
||||
{regeneratedKey}
|
||||
</code>
|
||||
<CopyToClipboard
|
||||
text={regeneratedKey}
|
||||
onCopy={() => NotificationManager.success("Virtual Key copied to clipboard")}
|
||||
>
|
||||
<Button type="primary" icon={<CopyOutlined />} size="small">
|
||||
Copy
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
{regeneratedKey}
|
||||
</Paragraph>
|
||||
</Flex>
|
||||
) : (
|
||||
<Form
|
||||
form={form}
|
||||
|
|
@ -261,16 +231,17 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
name="duration"
|
||||
label="Expire Key"
|
||||
extra={
|
||||
<>
|
||||
<Flex vertical gap={2}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Current expiry: {selectedToken?.expires ? new Date(selectedToken.expires).toLocaleString() : "Never"}
|
||||
Current expiry:{" "}
|
||||
{selectedToken?.expires ? new Date(selectedToken.expires).toLocaleString() : "Never"}
|
||||
</Text>
|
||||
{newExpiryTime && (
|
||||
<div>
|
||||
<Text style={{ fontSize: 12, color: "#52c41a" }}>New expiry: {newExpiryTime}</Text>
|
||||
</div>
|
||||
<Text type="success" style={{ fontSize: 12 }}>
|
||||
New expiry: {newExpiryTime}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<Input placeholder="e.g. 30s, 30h, 30d" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue