Skip to content

refactor(ui): give each Logs tab its own route - #34430

Open
ryan-crabbe-berri wants to merge 3 commits into
litellm_internal_stagingfrom
litellm_logs_per_tab_routing
Open

refactor(ui): give each Logs tab its own route#34430
ryan-crabbe-berri wants to merge 3 commits into
litellm_internal_stagingfrom
litellm_logs_per_tab_routing

Conversation

@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Logs tabs shared one URL, so links/refresh always landed on Request Logs
  • All four tabs lived in one Tremor TabGroup god-component

How it solves it:

  • Each tab gets its own prerendered path under /logs
  • A shared layout drives the tab bar from the pathname
  • The god-component is split into one page per tab

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

UI-only change; steps to verify against a live proxy.

  1. Build the dashboard and serve it through the proxy, then open http://localhost:4000/ui/?page=logs
  2. Click through Request Logs, Audit Logs, Deleted Keys and Deleted Teams; the address bar becomes /ui/logs, /ui/logs/audit, /ui/logs/deleted-keys and /ui/logs/deleted-teams as you switch
  3. Hard-refresh the browser on /ui/logs/audit (and each of the others); the page reloads straight onto that tab instead of snapping back to Request Logs, and the Logs sidebar item stays highlighted
  4. Confirm the static export produced a real HTML file per tab: out/logs/index.html, out/logs/audit/index.html, out/logs/deleted-keys/index.html, out/logs/deleted-teams/index.html

Screenshots of each tab and of a hard-refresh deep link to be attached.

Type

🧹 Refactoring

Changes

Logs moves from a single Tremor TabGroup wrapper to path-based routing, the same pattern used for Models + Endpoints (#34327), Caching (#34415), Cost Optimization (#34424) and Router Settings (#34428).

tabRoutes.ts is the source of truth: the tab slugs plus logsTabHref and slugFromPathname, which map a slug to a trailing-slash URL and read the active slug back off the pathname. layout.tsx keeps the page's existing loading contract (a spinner until access token, session token, role and user id are all present), renders the tab bar, derives the active tab from usePathname() so no tab state is stored, and pushes to a tab's path on click; an unknown slug redirects to the base route. Each tab is its own page.tsx (Request Logs at the index, then audit, deleted-keys, deleted-teams). Build-time-known tab paths prerender to static HTML, so deep links and hard-loads work with no nginx change; there is no runtime-unknown id here, so nothing needs query params.

The former SpendLogsTable god-component is deleted and its tab bar rebuilt on the shadcn Tabs primitive. Previously it tracked an activeTab string and passed an isActive flag into the Request Logs and Audit Logs panels so background tabs would not query. With routing, only the active tab's panel is mounted, so each panel receives isActive directly; this also drops a pre-existing quirk where selecting Deleted Keys or Deleted Teams set isActive true on the audit-logs panel. The four panel components are unchanged and stay where they are.

A second commit fixes a shared shell helper that every nested tab route depends on: legacyKeyForPathname matched the full relative path against a single route segment, so a path like /logs/audit resolved to no key and the shell fell back to the default page, leaving the sidebar item unhighlighted. It now matches on the first path segment, which fixes highlighting for every migrated page with nested routes. This is the same one-line shell fix that is also in #34415, #34424 and #34428; the edits are byte-identical, so they merge cleanly whichever lands first.

Tests: tabRoutes.test.ts locks the slug/href mapping, layout.test.tsx covers rendering the four tabs, the active base tab, routing on click, the unknown-slug redirect and the credentials-not-ready spinner (the guard regression migrated from the deleted god-component's test), and migratedPages.test.ts gains a regression for nested-route sidebar resolution.

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Refactors Logs into route-backed tabs

  • Adds dedicated Request Logs, Audit Logs, Deleted Keys, and Deleted Teams pages under /logs
  • Introduces a reusable route-aware tab bar and shared Logs layout
  • Updates nested-route sidebar resolution and associated tests

Confidence Score: 4/5

This PR is not safe to merge until Logs routes work when the server root path collides with the Logs route segment

The attempted routing fix still parses the first logs segment from the raw pathname, causing valid deep links to redirect and the base Logs route to reload itself when SERVER_ROOT_PATH is /logs

ui/litellm-dashboard/src/app/(dashboard)/logs/tabRoutes.ts and ui/litellm-dashboard/src/utils/tabRoutes.ts

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/app/(dashboard)/logs/tabRoutes.ts Delegates Logs routing to a shared parser that retains the previously reported SERVER_ROOT_PATH collision
ui/litellm-dashboard/src/app/(dashboard)/logs/layout.tsx Adds the credential-gated shared layout and pathname-driven tab navigation
ui/litellm-dashboard/src/app/(dashboard)/components/TabRouteBar.tsx Adds reusable anchor-based tabs with client-side navigation and modifier-click support
ui/litellm-dashboard/src/utils/migratedPages.ts Resolves nested migrated routes to their sidebar key using the first relative path segment

Reviews (2): Last reviewed commit: "refactor(ui): adopt shared tab-routing h..." | Re-trigger Greptile

Comment thread ui/litellm-dashboard/src/app/(dashboard)/logs/tabRoutes.ts Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_logs_per_tab_routing (9200361) with litellm_internal_staging (43e7b96)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (07f7fc2) during the generation of this report, so 43e7b96 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Split the Logs page's four tabs (Request Logs, Audit Logs, Deleted Keys,
Deleted Teams) into their own prerendered paths under /logs, mirroring
the earlier per-tab routing migrations. A shared layout gates on
credentials (the existing loading-spinner contract), renders the tab bar
and derives the active tab from the pathname; each tab is its own
page.tsx, so deep links and hard-loads to /logs/audit, /logs/deleted-keys
and /logs/deleted-teams resolve to real static HTML with no nginx change.

The former SpendLogsTable god-component (a Tremor TabGroup wrapping the
four panels) is gone; its tab bar is rebuilt on the shadcn Tabs
primitive. Because each tab now mounts only when its route is active, the
Request Logs and Audit Logs panels receive isActive directly instead of a
shared activeTab flag, which also drops the quirk where selecting Deleted
Keys/Teams marked the audit-logs panel active.
legacyKeyForPathname matched the full relative path against a single
route segment, so a nested tab route like /logs/audit resolved to no key
and the shell fell back to the default page, leaving the Logs nav item
unhighlighted. Match on the first path segment instead, which fixes every
migrated page with nested tab routes.
…n Logs

Replace the page's hand-written tabRoutes.ts and layout routing engine
with createTabRoutes + useTabRouting + the shared <TabRouteBar>, keeping
the credentials loading-spinner guard inline. The per-page tabRoutes.test.ts
is dropped in favor of the central factory test.
@ryan-crabbe-berri
ryan-crabbe-berri force-pushed the litellm_logs_per_tab_routing branch from 4676196 to 9200361 Compare July 23, 2026 23:58
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

Rebased onto staging now that the shared tab-routing helpers (#34435) are merged. This branch now uses createTabRoutes + useTabRouting + a new shared <TabRouteBar> (shadcn tab bar rendered as real anchors, so left-click soft-navigates and middle-click / cmd-click opens in a new tab) instead of the per-page copy-paste. Behavior is unchanged aside from that added middle-click support.

@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai re review

Comment thread ui/litellm-dashboard/src/app/(dashboard)/logs/tabRoutes.ts
@devin-ai-integration

Copy link
Copy Markdown
Contributor

QA (Devin) — before/after after the shared tab-routing helper refactor

Re-QA'd the Logs per-tab routing locally (next dev, routing-only — no live backend, so tables render empty; route paths are at the root, e.g. /logs, not /ui/logs).

AFTER results (this PR): each tab has its own URL — clicking a tab and deep-linking / hard-refreshing both land on the exact nested tab (e.g. /logs/audit/), including through the auth-gated layout, and the sidebar "Logs" item stays highlighted on nested routes. BEFORE: switching sub-tabs kept the single /logs/ URL and the nested routes 404'd.

BEFORE — Request Logs, URL /logs/ AFTER — /logs/audit/ deep-link selects Audit Logs
before after

Two things to flag (shared across all 4 routing PRs):

  1. A garbage nested slug (e.g. /logs/bogus/) shows the Next.js 404 page rather than redirecting to the base tab — the useTabRouting redirect can't fire because a truly-missing route never mounts the section layout (the redirect only helps routes that exist but are hidden from visibleKeys). Product call whether a bare 404 is acceptable here.
  2. Minor non-fatal console warning from Base UI ("a component that acts as a button expected a native <button>") introduced by rendering <a> inside TabsTrigger. Tabs still work; nativeButton={false} would silence it.

Full report + recording: https://app.devin.ai/sessions/f98c3131bcf746c88756b609bae3e724

@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

Good catch on the server-root collision. The parser lives in the shared createTabRoutes helper (already on staging), so the fix is a separate PR against staging: #34628 changes slugFromPathname to match the last occurrence of the base segment (the route base always follows any server-root prefix, and no tab slug equals the base). That hardens Logs and the other three pages at once, with a regression test for /logs/ui/logs/audit/. This branch picks it up once #34628 merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants