From cc287a7d8f484dc3eb063aff33999068f733b00c Mon Sep 17 00:00:00 2001
From: "devin-ai-integration[bot]"
<158243242+devin-ai-integration[bot]@users.noreply.github.com>
Date: Mon, 7 Sep 2026 12:23:47 -0700
Subject: [PATCH] fix(ui): hide the Create Vector Store flow from non proxy
admins (#40148)
* fix(ui): hide the Create Vector Store flow from non proxy admins
The vector stores page rendered the Create Vector Store tab, the
+ Add Vector Store button and a GET /credentials call for every role,
while the proxy only lets proxy admins call POST /vector_store/new and
GET /credentials. Internal users landed on the create form and got an
Only proxy admin error toast. Gate all three on isProxyAdminRole and
default everyone else to the Manage tab, matching the Indexes tab and
the Add Model gating.
Resolves LIT-7131
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(ui): exclude view-only admin sessions from the vector store create flow
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: yassin
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---
.../vector-stores/_components/index.test.tsx | 51 ++++++++++++++++---
.../vector-stores/_components/index.tsx | 35 ++++++++-----
.../app/(dashboard)/vector-stores/page.tsx | 6 ++-
3 files changed, 70 insertions(+), 22 deletions(-)
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.test.tsx
index 7137da201d8..3224496b13a 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.test.tsx
@@ -45,7 +45,7 @@ describe("VectorStoreManagement loading state", () => {
it("should resolve the loading state when accessToken is null instead of showing the skeleton forever", async () => {
const user = userEvent.setup();
- render();
+ render();
await openManageTab(user);
expect(await screen.findByText("table-loaded")).toBeInTheDocument();
expect(mockVectorStoreListCall).not.toHaveBeenCalled();
@@ -59,7 +59,7 @@ describe("VectorStoreManagement loading state", () => {
resolveFetch = resolve;
}),
);
- render();
+ render();
await openManageTab(user);
expect(screen.getByText("table-loading")).toBeInTheDocument();
@@ -69,6 +69,43 @@ describe("VectorStoreManagement loading state", () => {
});
});
+describe("VectorStoreManagement create flow visibility", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockVectorStoreListCall.mockResolvedValue({ data: [] });
+ mockCredentialListCall.mockResolvedValue({ credentials: [] });
+ });
+
+ it.each([
+ { label: "Internal User", userRole: "Internal User", isViewOnly: false },
+ { label: "Internal Viewer", userRole: "Internal Viewer", isViewOnly: true },
+ { label: "proxy_admin_viewer session (userRole Admin, isViewOnly)", userRole: "Admin", isViewOnly: true },
+ { label: "Org Admin", userRole: "Org Admin", isViewOnly: false },
+ ])(
+ "should hide the Create Vector Store tab and button and skip /credentials for $label",
+ async ({ userRole, isViewOnly }) => {
+ render(
+ ,
+ );
+ await waitFor(() => expect(mockVectorStoreListCall).toHaveBeenCalledWith("sk-test"));
+ expect(screen.queryByRole("tab", { name: "Create Vector Store" })).not.toBeInTheDocument();
+ expect(screen.getByRole("tab", { name: "Manage Vector Stores" })).toHaveAttribute("aria-selected", "true");
+ expect(await screen.findByText("table-loaded")).toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: "+ Add Vector Store" })).not.toBeInTheDocument();
+ expect(mockCredentialListCall).not.toHaveBeenCalled();
+ },
+ );
+
+ it("should keep the Create Vector Store tab and button and fetch /credentials for a proxy admin", async () => {
+ const user = userEvent.setup();
+ render();
+ await waitFor(() => expect(mockCredentialListCall).toHaveBeenCalledWith("sk-test"));
+ expect(screen.getByRole("tab", { name: "Create Vector Store" })).toHaveAttribute("aria-selected", "true");
+ await openManageTab(user);
+ expect(screen.getByRole("button", { name: "+ Add Vector Store" })).toBeInTheDocument();
+ });
+});
+
describe("VectorStoreManagement Indexes tab", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -88,7 +125,7 @@ describe("VectorStoreManagement Indexes tab", () => {
},
],
});
- render();
+ render();
await user.click(screen.getByRole("tab", { name: "Indexes" }));
expect(await screen.findByText("support-docs-index")).toBeInTheDocument();
expect(screen.getByText("support-docs-store")).toBeInTheDocument();
@@ -96,7 +133,7 @@ describe("VectorStoreManagement Indexes tab", () => {
});
it("should not render the Indexes tab for an Admin Viewer", async () => {
- render();
+ render();
await waitFor(() => expect(mockVectorStoreListCall).toHaveBeenCalledWith("sk-test"));
expect(screen.getByRole("tab", { name: "Manage Vector Stores" })).toBeInTheDocument();
expect(screen.queryByRole("tab", { name: "Indexes" })).not.toBeInTheDocument();
@@ -125,7 +162,7 @@ describe("VectorStoreManagement Indexes tab", () => {
},
],
});
- render();
+ render();
await user.click(screen.getByRole("tab", { name: "Indexes" }));
await user.click(await screen.findByRole("button", { name: "support-docs-store" }));
expect(await screen.findByTestId("vector-store-info-view")).toHaveTextContent("vs-1");
@@ -135,7 +172,7 @@ describe("VectorStoreManagement Indexes tab", () => {
it("should link to the feature docs and a GitHub issue for unsupported providers on the Indexes tab", async () => {
const user = userEvent.setup();
mockIndexesListCall.mockResolvedValue({ object: "list", data: [] });
- render();
+ render();
await user.click(screen.getByRole("tab", { name: "Indexes" }));
expect(screen.getByRole("link", { name: "vector store index docs" })).toHaveAttribute(
"href",
@@ -149,7 +186,7 @@ describe("VectorStoreManagement Indexes tab", () => {
});
it("should not call indexesListCall until the Indexes tab is clicked", async () => {
- render();
+ render();
await waitFor(() => expect(mockVectorStoreListCall).toHaveBeenCalledWith("sk-test"));
expect(screen.getByRole("tab", { name: "Indexes" })).toBeInTheDocument();
expect(mockIndexesListCall).not.toHaveBeenCalled();
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx
index 1745c710c51..285a96520bd 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx
@@ -24,9 +24,10 @@ interface VectorStoreProps {
accessToken: string | null;
userID: string | null;
userRole: string | null;
+ isViewOnly: boolean;
}
-const VectorStoreManagement: React.FC = ({ accessToken, userID, userRole }) => {
+const VectorStoreManagement: React.FC = ({ accessToken, userID, userRole, isViewOnly }) => {
const [vectorStores, setVectorStores] = useState([]);
const [isLoadingVectorStores, setIsLoadingVectorStores] = useState(true);
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
@@ -37,7 +38,9 @@ const VectorStoreManagement: React.FC = ({ accessToken, userID
const [selectedVectorStoreId, setSelectedVectorStoreId] = useState(null);
const [editVectorStore, setEditVectorStore] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
- const { onTabChange, hasVisited } = useVisitedTabs("create");
+ const canCreateVectorStores = isProxyAdminRole(userRole || "") && !isViewOnly;
+ const defaultTab = canCreateVectorStores ? "create" : "manage";
+ const { onTabChange, hasVisited } = useVisitedTabs(defaultTab);
const fetchVectorStores = async () => {
if (!accessToken) {
@@ -56,7 +59,7 @@ const VectorStoreManagement: React.FC = ({ accessToken, userID
};
const fetchCredentials = async () => {
- if (!accessToken) return;
+ if (!accessToken || !canCreateVectorStores) return;
try {
const response = await credentialListCall(accessToken);
setCredentials(response.credentials || []);
@@ -153,11 +156,13 @@ const VectorStoreManagement: React.FC = ({ accessToken, userID
You can use vector stores to store and retrieve LLM embeddings.
-
+
-
- Create Vector Store
-
+ {canCreateVectorStores && (
+
+ Create Vector Store
+
+ )}
Manage Vector Stores
@@ -171,14 +176,18 @@ const VectorStoreManagement: React.FC = ({ accessToken, userID
)}
-
-
-
+ {canCreateVectorStores && (
+
+
+
+ )}
-
+ {canCreateVectorStores && (
+
+ )}
;
+ const { accessToken, userRole, userId, isViewOnly } = useAuthorized();
+ return (
+
+ );
}