fix(ui): surface the master key lockout message in credential, model and key toasts

This commit is contained in:
Devin AI 2026-09-15 14:10:15 +00:00
parent a9e8e196c0
commit 5b7690cd2f
7 changed files with 47 additions and 14 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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