Add health status pagination controls

Made-with: Cursor
This commit is contained in:
shivam 2026-04-29 15:50:55 -07:00
parent 9bc317b4d0
commit 1277cbe454
No known key found for this signature in database
3 changed files with 174 additions and 49 deletions

View File

@ -62,6 +62,8 @@ 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 healthPageSize = 50;
const [showMissingProviderBanner, setShowMissingProviderBanner] = useState(() => {
if (typeof window !== "undefined") {
return localStorage.getItem("hideMissingProviderBanner") !== "true";
@ -71,6 +73,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,
healthPageSize,
);
const { data: modelCostMapData, isLoading: isLoadingModelCostMap } = useModelCostMap();
const { data: credentialsResponse, isLoading: isLoadingCredentials } = useCredentials();
const credentialsList = credentialsResponse?.credentials || [];
@ -104,12 +110,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 +131,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 ?? healthPageSize,
};
}, [healthModelDataResponse, healthCurrentPage, healthPageSize]);
const isProxyAdmin = userRole && isProxyAdminRole(userRole);
const isInternalUser = userRole && internalUserRoles.includes(userRole);
const isUserTeamAdmin = userID && isUserTeamAdminForAnyTeam(teams, userID);
@ -166,7 +186,7 @@ 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" }));
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
refetchModels();
};
@ -441,11 +461,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={healthPageSize}
onPageChange={setHealthCurrentPage}
/>
</TabPanel>
<ModelRetrySettingsTab

View File

@ -92,18 +92,57 @@ 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",
},
],
};
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}
/>,
);
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",
},
],

View File

@ -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,12 @@ const HealthCheckComponent: React.FC<HealthCheckComponentProps> = ({
}
};
const handlePageChange = (page: number) => {
setSelectedModelsForHealth([]);
setAllModelsSelected(false);
onPageChange?.(page);
};
const getStatusBadge = (status: string) => {
switch (status) {
case "healthy":
@ -490,6 +511,34 @@ 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 totalCount = paginationMeta?.total_count ?? healthTableData.length;
const totalPages = paginationMeta?.total_pages ?? 1;
const resultsStart = totalCount > 0 ? (currentPage - 1) * pageSize + 1 : 0;
const resultsEnd = Math.min(currentPage * pageSize, totalCount);
const shouldShowPagination = Boolean(paginationMeta && onPageChange);
return (
<div>
<div className="mb-6">
@ -522,6 +571,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 +618,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>