Merge pull request #26826 from BerriAI/litellm_health_status_pagination

Add pagination controls to model health status
This commit is contained in:
yuneng-jiang
2026-04-30 18:45:40 -07:00
committed by GitHub
4 changed files with 190 additions and 51 deletions
@@ -141,7 +141,9 @@ test.describe("Add Model", () => {
await page.waitForTimeout(1000);
// Verify the model appears in the results count (not "Showing 0 results")
await expect(page.getByText(/Showing \d+ - \d+ of \d+ results/)).toBeVisible({ timeout: 15_000 });
await expect(page.getByTestId("models-results-count")).toHaveText(/Showing \d+ - \d+ of \d+ results/, {
timeout: 15_000,
});
// Verify the model name appears in the table body
const tableBody = page.locator("table tbody");
@@ -181,7 +183,9 @@ test.describe("Add Model", () => {
await page.waitForTimeout(1000);
// Verify the model appears in the results count (not "Showing 0 results")
await expect(page.getByText(/Showing \d+ - \d+ of \d+ results/)).toBeVisible({ timeout: 15_000 });
await expect(page.getByTestId("models-results-count")).toHaveText(/Showing \d+ - \d+ of \d+ results/, {
timeout: 15_000,
});
// Verify the wildcard model appears in the table body (wildcard models show as "cohere/*")
const tableBody = page.locator("table tbody");
@@ -46,6 +46,8 @@ interface GlobalRetryPolicyObject {
[retryPolicyKey: string]: number;
}
const HEALTH_PAGE_SIZE = 50;
const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, teams }) => {
const { accessToken, token, userRole, userId: userID } = useAuthorized();
const [addModelForm] = Form.useForm();
@@ -62,6 +64,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
const [selectedModelId, setSelectedModelId] = useState<string | null>(null);
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const [healthCurrentPage, setHealthCurrentPage] = useState(1);
const [showMissingProviderBanner, setShowMissingProviderBanner] = useState(() => {
if (typeof window !== "undefined") {
return localStorage.getItem("hideMissingProviderBanner") !== "true";
@@ -71,6 +74,10 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
const queryClient = useQueryClient();
const { data: modelDataResponse, isLoading: isLoadingModels, refetch: refetchModels } = useModelsInfo();
const { data: healthModelDataResponse, isLoading: isLoadingHealthModels } = useModelsInfo(
healthCurrentPage,
HEALTH_PAGE_SIZE,
);
const { data: modelCostMapData, isLoading: isLoadingModelCostMap } = useModelCostMap();
const { data: credentialsResponse, isLoading: isLoadingCredentials } = useCredentials();
const credentialsList = credentialsResponse?.credentials || [];
@@ -104,12 +111,12 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
return modelDataResponse.data.map((model: any) => model.model_name);
}, [modelDataResponse?.data]);
const allModelIdsOnProxy = useMemo<string[]>(() => {
if (!modelDataResponse?.data) return [];
return modelDataResponse.data
const healthModelIdsOnProxy = useMemo<string[]>(() => {
if (!healthModelDataResponse?.data) return [];
return healthModelDataResponse.data
.map((model: any) => model.model_info?.id)
.filter((id: string | undefined): id is string => Boolean(id));
}, [modelDataResponse?.data]);
}, [healthModelDataResponse?.data]);
const getProviderFromModel = (model: string) => {
if (modelCostMapData !== null && modelCostMapData !== undefined) {
@@ -125,6 +132,20 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
return transformModelData(modelDataResponse, getProviderFromModel);
}, [modelDataResponse?.data, getProviderFromModel]);
const processedHealthModelData = useMemo(() => {
if (!healthModelDataResponse?.data) return { data: [] };
return transformModelData(healthModelDataResponse, getProviderFromModel);
}, [healthModelDataResponse?.data, getProviderFromModel]);
const healthPaginationMeta = useMemo(() => {
return {
total_count: healthModelDataResponse?.total_count ?? 0,
current_page: healthModelDataResponse?.current_page ?? healthCurrentPage,
total_pages: healthModelDataResponse?.total_pages ?? 1,
size: healthModelDataResponse?.size ?? HEALTH_PAGE_SIZE,
};
}, [healthModelDataResponse, healthCurrentPage]);
const isProxyAdmin = userRole && isProxyAdminRole(userRole);
const isInternalUser = userRole && internalUserRoles.includes(userRole);
const isUserTeamAdmin = userID && isUserTeamAdminForAnyTeam(teams, userID);
@@ -166,7 +187,8 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
const handleRefreshClick = () => {
const currentDate = new Date();
setLastRefreshed(currentDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
setLastRefreshed(currentDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }));
setHealthCurrentPage(1);
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
refetchModels();
};
@@ -441,11 +463,16 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
<TabPanel>
<HealthCheckComponent
accessToken={accessToken}
modelData={processedModelData}
all_models_on_proxy={allModelIdsOnProxy}
modelData={processedHealthModelData}
all_models_on_proxy={healthModelIdsOnProxy}
getDisplayModelName={getDisplayModelName}
setSelectedModelId={setSelectedModelId}
teams={teams}
isLoading={isLoadingHealthModels}
paginationMeta={healthPaginationMeta}
currentPage={healthCurrentPage}
pageSize={HEALTH_PAGE_SIZE}
onPageChange={setHealthCurrentPage}
/>
</TabPanel>
<ModelRetrySettingsTab
@@ -92,18 +92,62 @@ describe("HealthCheckComponent", () => {
expect(mockIndividualModelHealthCheckCall).not.toHaveBeenCalledWith("token-123", "gpt-4");
});
it("should show pagination controls and request the next page", async () => {
const onPageChange = vi.fn();
const modelData = {
data: [
{
model_name: "gpt-4",
model_info: { id: "deployment-1" },
litellm_model_name: "gpt-4",
},
],
};
await act(async () => {
render(
<HealthCheckComponent
accessToken="token"
modelData={modelData}
all_models_on_proxy={["deployment-1"]}
getDisplayModelName={getDisplayModelName}
paginationMeta={{
total_count: 75,
current_page: 1,
total_pages: 2,
size: 50,
}}
currentPage={1}
pageSize={50}
onPageChange={onPageChange}
/>,
);
});
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
expect(screen.getByTestId("health-results-count")).toHaveTextContent("Showing 1 - 50 of 75 results");
await act(async () => {
screen.getByRole("button", { name: "Next" }).click();
});
expect(onPageChange).toHaveBeenCalledWith(2);
});
describe("latest_health_checks keyed by model id", () => {
it("should show status from latest_health_checks when keys match model ids", async () => {
const modelData = {
data: [
{
model_name: "gpt-4",
model_info: { id: "id-alpha" },
litellm_model_name: "gpt-4",
{
model_name: "gpt-4",
model_info: { id: "id-alpha" },
litellm_model_name: "gpt-4",
},
{
model_name: "gpt-4",
model_info: { id: "id-beta" },
{
model_name: "gpt-4",
model_info: { id: "id-beta" },
litellm_model_name: "gpt-4",
},
],
@@ -26,6 +26,16 @@ interface HealthCheckComponentProps {
getDisplayModelName: (model: any) => string;
setSelectedModelId?: (modelId: string) => void;
teams?: Team[] | null;
isLoading?: boolean;
paginationMeta?: {
total_count: number;
current_page: number;
total_pages: number;
size: number;
};
currentPage?: number;
pageSize?: number;
onPageChange?: (page: number) => void;
}
const HealthCheckComponent: React.FC<HealthCheckComponentProps> = ({
@@ -35,6 +45,11 @@ const HealthCheckComponent: React.FC<HealthCheckComponentProps> = ({
getDisplayModelName,
setSelectedModelId,
teams,
isLoading = false,
paginationMeta,
currentPage = 1,
pageSize = 50,
onPageChange,
}) => {
const [modelHealthStatuses, setModelHealthStatuses] = useState<{ [key: string]: HealthStatus }>({});
const [selectedModelsForHealth, setSelectedModelsForHealth] = useState<string[]>([]);
@@ -95,19 +110,19 @@ const HealthCheckComponent: React.FC<HealthCheckComponentProps> = ({
const fullError = checkData.error_message || undefined;
healthStatusMap[modelId] = {
status: checkData.status || "unknown",
lastCheck: checkData.checked_at ? new Date(checkData.checked_at).toLocaleString() : "None",
lastSuccess:
checkData.status === "healthy"
? checkData.checked_at
? new Date(checkData.checked_at).toLocaleString()
: "None"
: "None",
loading: false,
error: fullError ? extractMeaningfulError(fullError) : undefined,
fullError: fullError,
successResponse: checkData.status === "healthy" ? checkData : undefined,
};
status: checkData.status || "unknown",
lastCheck: checkData.checked_at ? new Date(checkData.checked_at).toLocaleString() : "None",
lastSuccess:
checkData.status === "healthy"
? checkData.checked_at
? new Date(checkData.checked_at).toLocaleString()
: "None"
: "None",
loading: false,
error: fullError ? extractMeaningfulError(fullError) : undefined,
fullError: fullError,
successResponse: checkData.status === "healthy" ? checkData : undefined,
};
});
}
} catch (healthError) {
@@ -448,6 +463,13 @@ const HealthCheckComponent: React.FC<HealthCheckComponentProps> = ({
}
};
const handlePageChange = (page: number) => {
setSelectedModelsForHealth([]);
setAllModelsSelected(false);
setModelHealthStatuses({});
onPageChange?.(page);
};
const getStatusBadge = (status: string) => {
switch (status) {
case "healthy":
@@ -490,6 +512,36 @@ const HealthCheckComponent: React.FC<HealthCheckComponentProps> = ({
setSelectedSuccessDetails(null);
};
const healthTableData = (modelData?.data ?? []).map((model: any) => {
const modelId = model.model_info?.id;
const healthStatus = modelId ? modelHealthStatuses[modelId] : null;
const status = healthStatus || {
status: "none",
lastCheck: "None",
loading: false,
};
return {
model_name: model.model_name,
model_info: model.model_info,
provider: model.provider,
litellm_model_name: model.litellm_model_name,
health_status: status.status,
last_check: status.lastCheck,
last_success: status.lastSuccess || "None",
health_loading: status.loading,
health_error: status.error,
health_full_error: status.fullError,
};
});
const shouldShowPagination = Boolean(paginationMeta && onPageChange);
const totalCount = paginationMeta?.total_count ?? 0;
const totalPages = paginationMeta?.total_pages ?? 1;
const pageForDisplay = paginationMeta?.current_page ?? currentPage;
const pageSizeForDisplay = paginationMeta?.size ?? pageSize;
const resultsStart = shouldShowPagination && totalCount > 0 ? (pageForDisplay - 1) * pageSizeForDisplay + 1 : 0;
const resultsEnd = shouldShowPagination ? Math.min(pageForDisplay * pageSizeForDisplay, totalCount) : 0;
return (
<div>
<div className="mb-6">
@@ -522,6 +574,38 @@ const HealthCheckComponent: React.FC<HealthCheckComponentProps> = ({
</div>
<div>
{shouldShowPagination && (
<div className="flex justify-between items-center mb-3">
<span data-testid="health-results-count" className="text-sm text-gray-700">
{totalCount > 0
? `Showing ${resultsStart} - ${resultsEnd} of ${totalCount} results`
: "Showing 0 results"}
</span>
<div className="flex items-center space-x-2">
<button
onClick={() => handlePageChange(currentPage - 1)}
disabled={isLoading || currentPage === 1}
className={`px-3 py-1 text-sm border rounded-md ${
isLoading || currentPage === 1 ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "hover:bg-gray-50"
}`}
>
Previous
</button>
<button
onClick={() => handlePageChange(currentPage + 1)}
disabled={isLoading || currentPage >= totalPages}
className={`px-3 py-1 text-sm border rounded-md ${
isLoading || currentPage >= totalPages
? "bg-gray-100 text-gray-400 cursor-not-allowed"
: "hover:bg-gray-50"
}`}
>
Next
</button>
</div>
</div>
)}
<ModelDataTable
columns={healthCheckColumns(
modelHealthStatuses,
@@ -537,28 +621,8 @@ const HealthCheckComponent: React.FC<HealthCheckComponentProps> = ({
setSelectedModelId,
teams,
)}
data={modelData.data.map((model: any) => {
const modelId = model.model_info?.id;
const healthStatus = modelId ? modelHealthStatuses[modelId] : null;
const status = healthStatus || {
status: "none",
lastCheck: "None",
loading: false,
};
return {
model_name: model.model_name,
model_info: model.model_info,
provider: model.provider,
litellm_model_name: model.litellm_model_name,
health_status: status.status,
last_check: status.lastCheck,
last_success: status.lastSuccess || "None",
health_loading: status.loading,
health_error: status.error,
health_full_error: status.fullError,
};
})}
isLoading={false}
data={healthTableData}
isLoading={isLoading}
/>
</div>