@@ -26,6 +52,10 @@ vi.mock("@/components/ToolPolicies/ToolPoliciesPanel", () => ({
}));
describe("ToolPoliciesView", () => {
+ beforeEach(() => {
+ currentUrl = "/ui/tool-policies";
+ });
+
it("should render the overview by default", () => {
renderWithProviders(
);
@@ -40,6 +70,14 @@ describe("ToolPoliciesView", () => {
expect(screen.getByText("Detail: my-tool")).toBeInTheDocument();
expect(screen.queryByText("Tool Policies Overview")).not.toBeInTheDocument();
+ expect(currentUrl).toBe("/ui/tool-policies?tool=my-tool");
+ });
+
+ it("should open a tool's detail directly from the ?tool= deep link", () => {
+ currentUrl = "/ui/tool-policies?tool=deep-linked-tool";
+ renderWithProviders(
);
+
+ expect(screen.getByText("Detail: deep-linked-tool")).toBeInTheDocument();
});
it("should navigate back to overview when back is clicked", async () => {
diff --git a/ui/litellm-dashboard/src/components/ToolPoliciesView.tsx b/ui/litellm-dashboard/src/components/ToolPoliciesView.tsx
index bdff40153b9..59f4e1cdcb6 100644
--- a/ui/litellm-dashboard/src/components/ToolPoliciesView.tsx
+++ b/ui/litellm-dashboard/src/components/ToolPoliciesView.tsx
@@ -1,30 +1,43 @@
"use client";
-import React, { useState } from "react";
+import React, { useCallback } from "react";
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { ToolDetail } from "@/components/ToolDetail";
import { ToolPoliciesPanel } from "@/components/ToolPolicies/ToolPoliciesPanel";
-type View = { type: "overview" } | { type: "detail"; toolName: string };
+export const TOOL_QUERY_PARAM = "tool";
interface ToolPoliciesViewProps {
accessToken: string | null;
}
export default function ToolPoliciesView({ accessToken }: ToolPoliciesViewProps) {
- const [view, setView] = useState
({ type: "overview" });
+ const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+ const selectedTool = searchParams.get(TOOL_QUERY_PARAM);
- const handleSelectTool = (toolName: string) => {
- setView({ type: "detail", toolName });
- };
+ const navigateToTool = useCallback(
+ (toolName: string | null) => {
+ const params = new URLSearchParams(searchParams.toString());
+ if (toolName) {
+ params.set(TOOL_QUERY_PARAM, toolName);
+ } else {
+ params.delete(TOOL_QUERY_PARAM);
+ }
+ const query = params.toString();
+ router.push(query ? `${pathname}?${query}` : pathname);
+ },
+ [pathname, router, searchParams],
+ );
- const handleBack = () => {
- setView({ type: "overview" });
- };
+ const handleSelectTool = useCallback((toolName: string) => navigateToTool(toolName), [navigateToTool]);
+ const handleBack = useCallback(() => navigateToTool(null), [navigateToTool]);
return (
- {view.type === "detail" ? (
-
+ {selectedTool ? (
+
) : (
)}
diff --git a/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx b/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx
index 6ee3319dc10..c7ae0387244 100644
--- a/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx
+++ b/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx
@@ -104,6 +104,7 @@ export function BarChart>({
fill={fills[i]}
stackId={stack ? "stack" : undefined}
isAnimationActive={false}
+ className={onValueChange ? "cursor-pointer" : undefined}
onClick={
onValueChange
? (item: { payload?: TDatum }) => {