-
+
+ setTab(value as LensTab)} className="min-h-0 flex-1 gap-0">
+
+
-
+
+
-
-
-
-
-
-
-
- {isProxyAdminTierRole(userRole) ? (
-
+
+
+
- ) : (
-
- Investigations require proxy administrator access. You can still view your traces.
-
- )}
-
-
+
+
+
+
+ {canInvestigate ? (
+
+ ) : (
+
+ Investigations require proxy administrator access. You can still view your traces.
+
+ )}
+
+
+
);
diff --git a/ui/litellm-dashboard/src/components/lens/investigations/InvestigationList.tsx b/ui/litellm-dashboard/src/components/lens/investigations/InvestigationList.tsx
index 6f400bd638d..10a70999232 100644
--- a/ui/litellm-dashboard/src/components/lens/investigations/InvestigationList.tsx
+++ b/ui/litellm-dashboard/src/components/lens/investigations/InvestigationList.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Fragment, useState, type ReactNode } from "react";
+import { Fragment, useState, type KeyboardEvent, type ReactNode } from "react";
import {
ChevronRight,
Circle,
@@ -26,16 +26,22 @@ import { scopeLabel } from "../model/format";
import { type Finding, type Lens } from "../model/types";
import { useListSearchRoute } from "../route";
-const PRIORITY_COLOR = { high: "text-[#e5484d]", medium: "text-amber-500", low: "text-muted-foreground" } as const;
+const PRIORITY_COLOR = { high: "text-destructive", medium: "text-amber-500", low: "text-muted-foreground" } as const;
const ROW =
"border-b border-border/60 transition-colors duration-150 hover:bg-trace-row-hover motion-reduce:transition-none";
-const META = "truncate text-[11px] text-muted-foreground";
+const META = "truncate text-xs text-muted-foreground";
+
+function openOnRowKeyDown(event: KeyboardEvent, open: () => void) {
+ if (event.target !== event.currentTarget || (event.key !== "Enter" && event.key !== " ")) return;
+ event.preventDefault();
+ open();
+}
function JobIcon({ lens }: { lens: Lens }) {
const status = lens.jobs[0]?.status;
const className = "size-4 shrink-0";
if (status === "queued" || status === "running")
- return ;
+ return ;
if (status === "failed") return ;
if (status === "cancelled")
return ;
@@ -47,7 +53,7 @@ function TwoLine({ meta, title, className }: { meta: ReactNode; title: ReactNode
return (
{meta}
- {title}
+ {title}
);
}
@@ -56,6 +62,7 @@ export function InvestigationList({
lenses,
connected,
readOnly = false,
+ onOpen,
onEdit,
onRunNow,
onOpenFinding,
@@ -63,6 +70,7 @@ export function InvestigationList({
lenses: Lens[];
connected: boolean;
readOnly?: boolean;
+ onOpen: (id: string) => void;
onEdit: (id: string) => void;
onRunNow: (id: string) => void;
onOpenFinding: (lens: Lens, finding: Finding) => void;
@@ -87,7 +95,7 @@ export function InvestigationList({
setSearch(e.target.value)}
/>
@@ -112,9 +120,11 @@ export function InvestigationList({
return (
onEdit(lens.id)}
+ onClick={() => onOpen(lens.id)}
+ onKeyDown={(event) => openOnRowKeyDown(event, () => onOpen(lens.id))}
+ tabIndex={0}
aria-label={lens.settings.name}
- className={cn(ROW, "group h-14", !readOnly && "cursor-pointer")}
+ className={cn(ROW, "group h-14 cursor-pointer focus-visible:outline-2 focus-visible:outline-ring")}
>
@@ -149,7 +159,7 @@ export function InvestigationList({
@@ -159,14 +169,14 @@ export function InvestigationList({
{findings.length > 0 && (
{findings.length}
)}
{latest ? agoLabel(Date.parse(latest.created_at), now) : "never run"}
@@ -186,12 +196,18 @@ export function InvestigationList({
>
-
+
)}
@@ -205,8 +221,10 @@ export function InvestigationList({
onOpenFinding(lens, finding)}
+ onKeyDown={(event) => openOnRowKeyDown(event, () => onOpenFinding(lens, finding))}
+ tabIndex={0}
aria-label={finding.title}
- className={cn(ROW, "h-12 cursor-pointer")}
+ className={cn(ROW, "h-12 cursor-pointer focus-visible:outline-2 focus-visible:outline-ring")}
>
@@ -227,12 +245,12 @@ export function InvestigationList({
/>
-
+
{runs} {runs === 1 ? "run" : "runs"}
{agoLabel(Date.parse(finding.last_seen), now)}
@@ -249,12 +267,10 @@ export function InvestigationList({
{!shown.length && (
-
- No investigations match your search.
-
+ No investigations match your search.
)}
-
- Investigations require proxy administrator access. You can still view your traces. -
- )} -+ Investigations require proxy administrator access. You can still view your traces. +
+ )} +