diff --git a/ui/litellm-dashboard/src/components/model_hub_table.test.tsx b/ui/litellm-dashboard/src/components/model_hub_table.test.tsx
new file mode 100644
index 00000000000..be0b9a113f4
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/model_hub_table.test.tsx
@@ -0,0 +1,69 @@
+import * as networking from "@/components/networking";
+import { render, screen, waitFor } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import ModelHubTable from "./model_hub_table";
+
+vi.mock("@/components/networking", () => ({
+ getUiConfig: vi.fn(),
+ modelHubPublicModelsCall: vi.fn(),
+ modelHubCall: vi.fn(),
+ getConfigFieldSetting: vi.fn(),
+ getProxyBaseUrl: vi.fn(() => "http://localhost:4000"),
+ getAgentsList: vi.fn(),
+ fetchMCPServers: vi.fn(),
+}));
+
+vi.mock("next/navigation", () => ({
+ useRouter: () => ({
+ replace: vi.fn(),
+ }),
+}));
+
+vi.mock("./public_model_hub", () => ({
+ default: () =>
Public Model Hub
,
+}));
+
+describe("ModelHubTable", () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("should render", async () => {
+ vi.mocked(networking.modelHubCall).mockResolvedValue({
+ data: [],
+ });
+ vi.mocked(networking.getConfigFieldSetting).mockResolvedValue({
+ field_value: false,
+ });
+ vi.mocked(networking.getAgentsList).mockResolvedValue({
+ agents: [],
+ });
+ vi.mocked(networking.fetchMCPServers).mockResolvedValue([]);
+
+ render();
+
+ await waitFor(() => {
+ expect(screen.getByText("AI Hub")).toBeInTheDocument();
+ });
+ });
+
+ it("should call getUiConfig before modelHubPublicModelsCall when publicPage is true", async () => {
+ const getUiConfigMock = vi.mocked(networking.getUiConfig);
+ const modelHubPublicModelsCallMock = vi.mocked(networking.modelHubPublicModelsCall);
+
+ getUiConfigMock.mockResolvedValue({ server_root_path: "/", proxy_base_url: "http://localhost:4000" });
+ modelHubPublicModelsCallMock.mockResolvedValue([]);
+
+ render();
+
+ await waitFor(() => {
+ expect(getUiConfigMock).toHaveBeenCalled();
+ expect(modelHubPublicModelsCallMock).toHaveBeenCalled();
+ });
+
+ const getUiConfigCallOrder = getUiConfigMock.mock.invocationCallOrder[0];
+ const modelHubPublicModelsCallOrder = modelHubPublicModelsCallMock.mock.invocationCallOrder[0];
+
+ expect(getUiConfigCallOrder).toBeLessThan(modelHubPublicModelsCallOrder);
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/model_hub_table.tsx b/ui/litellm-dashboard/src/components/model_hub_table.tsx
index 87a7dd2bd69..7d48bf68aed 100644
--- a/ui/litellm-dashboard/src/components/model_hub_table.tsx
+++ b/ui/litellm-dashboard/src/components/model_hub_table.tsx
@@ -1,25 +1,32 @@
-import React, { useEffect, useState, useRef, useCallback } from "react";
+import { CopyOutlined } from "@ant-design/icons";
+import { Table as TableInstance } from "@tanstack/react-table";
+import { Badge, Button, Card, Tab, TabGroup, TabList, TabPanel, TabPanels, Text, Title } from "@tremor/react";
+import { Modal } from "antd";
+import { Copy } from "lucide-react";
import { useRouter } from "next/navigation";
-import { modelHubCall, modelHubPublicModelsCall, getAgentsList, getProxyBaseUrl, fetchMCPServers } from "./networking";
-import { getConfigFieldSetting } from "./networking";
-import { ModelDataTable } from "./model_dashboard/table";
-import { modelHubColumns } from "./model_hub_table_columns";
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
+import { isAdminRole } from "../utils/roles";
import { agentHubColumns, AgentHubData } from "./agent_hub_table_columns";
-import { mcpHubColumns, MCPServerData } from "./mcp_hub_table_columns";
-import PublicModelHub from "./public_model_hub";
-import MakeModelPublicForm from "./make_model_public_form";
import MakeAgentPublicForm from "./make_agent_public_form";
import MakeMCPPublicForm from "./make_mcp_public_form";
+import MakeModelPublicForm from "./make_model_public_form";
+import { mcpHubColumns, MCPServerData } from "./mcp_hub_table_columns";
+import { ModelDataTable } from "./model_dashboard/table";
import ModelFilters from "./model_filters";
-import UsefulLinksManagement from "./useful_links_management";
-import { Card, Text, Title, Button, Badge, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react";
-import { Modal } from "antd";
-import { CopyOutlined } from "@ant-design/icons";
-import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
-import { Table as TableInstance } from "@tanstack/react-table";
-import { Copy } from "lucide-react";
-import { isAdminRole } from "../utils/roles";
+import { modelHubColumns } from "./model_hub_table_columns";
import NotificationsManager from "./molecules/notifications_manager";
+import {
+ fetchMCPServers,
+ getAgentsList,
+ getConfigFieldSetting,
+ getProxyBaseUrl,
+ getUiConfig,
+ modelHubCall,
+ modelHubPublicModelsCall,
+} from "./networking";
+import PublicModelHub from "./public_model_hub";
+import UsefulLinksManagement from "./useful_links_management";
interface ModelHubTableProps {
accessToken: string | null;
@@ -101,6 +108,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage,
const fetchPublicData = async () => {
try {
setLoading(true);
+ await getUiConfig();
const _modelHubData = await modelHubPublicModelsCall();
console.log("ModelHubData:", _modelHubData);
console.log("First model structure:", _modelHubData[0]);
@@ -384,12 +392,10 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage,
{/* Header with Make Public Button */}
{publicPage == false && isAdminRole(userRole || "") && (
-
+
)}
-
+
{/* Filters */}
@@ -416,9 +422,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage,
{/* Header with Make Public Button */}
{publicPage == false && isAdminRole(userRole || "") && (
-
+
)}
@@ -445,9 +449,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage,
{/* Header with Make Public Button */}
{publicPage == false && isAdminRole(userRole || "") && (
-
+
)}
@@ -836,13 +838,13 @@ print(response.choices[0].message.content)`}
Status:
-
{selectedMcpServer.status || "unknown"}
@@ -864,9 +866,7 @@ print(response.choices[0].message.content)`}
URL:
-
- {selectedMcpServer.url}
-
+
{selectedMcpServer.url}
copyToClipboard(selectedMcpServer.url)}
className="cursor-pointer text-gray-500 hover:text-blue-500 flex-shrink-0"
@@ -876,9 +876,7 @@ print(response.choices[0].message.content)`}
{selectedMcpServer.command && (
Command:
-
- {selectedMcpServer.command}
-
+ {selectedMcpServer.command}
)}
@@ -940,31 +938,23 @@ print(response.choices[0].message.content)`}
Created At:
-
- {new Date(selectedMcpServer.created_at).toLocaleString()}
-
+ {new Date(selectedMcpServer.created_at).toLocaleString()}
Updated At:
-
- {new Date(selectedMcpServer.updated_at).toLocaleString()}
-
+ {new Date(selectedMcpServer.updated_at).toLocaleString()}
{selectedMcpServer.last_health_check && (
Last Health Check:
-
- {new Date(selectedMcpServer.last_health_check).toLocaleString()}
-
+ {new Date(selectedMcpServer.last_health_check).toLocaleString()}
)}
{selectedMcpServer.health_check_error && (
Health Check Error:
-
- {selectedMcpServer.health_check_error}
-
+ {selectedMcpServer.health_check_error}
)}