mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-19 00:01:29 +00:00
fix(ui): surface the master key lockout message in credential, model and key toasts
This commit is contained in:
parent
a9e8e196c0
commit
5b7690cd2f
7 changed files with 47 additions and 14 deletions
|
|
@ -424,8 +424,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
onSuccess();
|
||||
} catch (error) {
|
||||
console.error("Error creating agent:", error);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
toast.error(errorMessage ? `Failed to create agent: ${errorMessage}` : "Failed to create agent");
|
||||
toast.fromError(error);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,6 @@ export const handleAddAutoRouterSubmit = async (
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to add auto router:", error);
|
||||
toast.fromError("Failed to add auto router: " + error);
|
||||
toast.fromError(error);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -224,6 +224,6 @@ export const handleAddModelSubmit = async (values: any, accessToken: string, for
|
|||
callback && callback();
|
||||
form.resetFields();
|
||||
} catch (error) {
|
||||
toast.fromError("Failed to add model: " + error);
|
||||
toast.fromError(error);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import userEvent from "@testing-library/user-event";
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { CredentialItem, credentialCreateCall, credentialUpdateCall } from "@/components/networking";
|
||||
import { ApiError } from "@/lib/http/client";
|
||||
import { toast } from "@/lib/toast";
|
||||
|
||||
import CredentialsPanel from "./CredentialsPanel";
|
||||
|
|
@ -171,13 +172,44 @@ describe("CredentialsPanel", () => {
|
|||
await user.click(screen.getByTestId("credential-modal-add-submit"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith("Failed to add credential");
|
||||
expect(toast.fromError).toHaveBeenCalledWith(expect.objectContaining({ message: "network down" }));
|
||||
});
|
||||
// The modal stays open so the user can retry, and no success toast fired.
|
||||
expect(screen.getByTestId("credential-modal-add-submit")).toBeInTheDocument();
|
||||
expect(toast.success).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("surfaces the proxy lockout message when credential creation is rejected with a 403", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Admin" });
|
||||
mockUseCredentials.mockReturnValue({ data: { credentials: [] }, isLoading: false, refetch: vi.fn() });
|
||||
const lockout = new ApiError(
|
||||
"This functionality is unavailable until the master key has been set. Set LITELLM_MASTER_KEY (or general_settings.master_key) to a strong random key and restart the proxy.",
|
||||
403,
|
||||
{
|
||||
error: {
|
||||
message:
|
||||
"This functionality is unavailable until the master key has been set. Set LITELLM_MASTER_KEY (or general_settings.master_key) to a strong random key and restart the proxy.",
|
||||
type: "auth_error",
|
||||
param: "master_key",
|
||||
code: "403",
|
||||
},
|
||||
},
|
||||
);
|
||||
vi.mocked(credentialCreateCall).mockRejectedValueOnce(lockout);
|
||||
|
||||
renderPanel();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /add credential/i }));
|
||||
await user.click(screen.getByTestId("credential-modal-add-submit"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.fromError).toHaveBeenCalledWith(lockout);
|
||||
});
|
||||
expect(screen.getByTestId("credential-modal-add-submit")).toBeInTheDocument();
|
||||
expect(toast.success).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("drops the masked api key from the update payload while keeping the edited api base", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Admin" });
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export default function CredentialsPanel() {
|
|||
setIsUpdateModalOpen(false);
|
||||
await refetchCredentials();
|
||||
} catch (error) {
|
||||
toast.error("Failed to update credential");
|
||||
toast.fromError(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ export default function CredentialsPanel() {
|
|||
setIsAddModalOpen(false);
|
||||
await refetchCredentials();
|
||||
} catch (error) {
|
||||
toast.error("Failed to add credential");
|
||||
toast.fromError(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ export default function CredentialsPanel() {
|
|||
toast.success("Credential deleted successfully");
|
||||
await refetchCredentials();
|
||||
} catch (error) {
|
||||
toast.error("Failed to delete credential");
|
||||
toast.fromError(error);
|
||||
} finally {
|
||||
setCredentialToDelete(null);
|
||||
setIsDeleteModalOpen(false);
|
||||
|
|
|
|||
|
|
@ -293,8 +293,12 @@ export default function ModelInfoView({
|
|||
},
|
||||
};
|
||||
toast.info("Storing credential..");
|
||||
let credentialResponse = await credentialCreateCall(accessToken, credentialItem);
|
||||
toast.success("Credential stored successfully");
|
||||
try {
|
||||
await credentialCreateCall(accessToken, credentialItem);
|
||||
toast.success("Credential stored successfully");
|
||||
} catch (error) {
|
||||
toast.fromError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelUpdate = async (
|
||||
|
|
@ -458,7 +462,7 @@ export default function ModelInfoView({
|
|||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating model:", error);
|
||||
toast.fromError("Failed to update model settings");
|
||||
toast.fromError(error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ import CreatedKeyDisplay from "../shared/CreatedKeyDisplay";
|
|||
import NumericalInput from "../shared/numerical_input";
|
||||
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
|
||||
import { buildKeyCreatePayload, type KeyCreateInput } from "./createKeyPayload";
|
||||
import { simplifyKeyGenerateError } from "./utils";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
|
||||
const KEY_TYPE_OPTIONS = [
|
||||
|
|
@ -482,8 +481,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
|
|||
setBudgetFallbacksKey((k) => k + 1);
|
||||
localStorage.removeItem("userData" + userID);
|
||||
} catch (error) {
|
||||
const simplifiedError = simplifyKeyGenerateError(error);
|
||||
toast.fromError(simplifiedError);
|
||||
toast.fromError(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue