diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx index e71fed40209..3b4f59c057c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx @@ -424,8 +424,7 @@ const AddAgentForm: React.FC = ({ 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); } diff --git a/ui/litellm-dashboard/src/components/add_model/handle_add_auto_router_submit.tsx b/ui/litellm-dashboard/src/components/add_model/handle_add_auto_router_submit.tsx index 59d9ecf205e..c78483b85d6 100644 --- a/ui/litellm-dashboard/src/components/add_model/handle_add_auto_router_submit.tsx +++ b/ui/litellm-dashboard/src/components/add_model/handle_add_auto_router_submit.tsx @@ -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); } }; diff --git a/ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx b/ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx index 41133958c0a..ee87e0c07d5 100644 --- a/ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx +++ b/ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx @@ -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); } }; diff --git a/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.test.tsx b/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.test.tsx index 92885262dca..14868c4cab4 100644 --- a/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.test.tsx +++ b/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.test.tsx @@ -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" }); diff --git a/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.tsx b/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.tsx index 80a8216dbaf..9f5ba2abc9b 100644 --- a/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.tsx +++ b/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.tsx @@ -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); diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index 35afcdb2985..d1e7f8f8e23 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -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); } diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx index b5789101f77..94cfcef7b63 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx @@ -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 = ({ 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); } };