Commit graph

153 commits

Author SHA1 Message Date
ryan-crabbe-berri
2332d3f183
Merge pull request #40737 from BerriAI/litellm_logs_user_email_display
fix(ui): show internal user email in logs table and log detail drawer
2026-09-18 14:20:16 -07:00
Yassin Kortam
f6d9b2552f
Merge pull request #41692 from BerriAI/litellm_mcp_gateway_sessions_by_client_user
feat(mcp): show live gateway sessions by AI client and user
2026-09-18 11:37:16 -07:00
ryan-crabbe-berri
a9ee15372f
Merge pull request #39308 from BerriAI/litellm_ui_per_second_video_pricing
fix(ui): show per-second pricing for video models instead of $0.00 token costs
2026-09-18 10:05:26 -07:00
ryan-crabbe-berri
4e2117832a
Merge pull request #40700 from BerriAI/litellm_ui_editable_model_team_id
fix(ui): let admins change a model's team from the model edit page
2026-09-18 09:53:34 -07:00
ryan
b5070408e7 refactor(ui): move per-second cost formatter to dataUtils and type transformModelData input
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 15:48:56 +00:00
Devin AI
6db76b5f39 fix(ui): match the Team ID selector to the backend's proxy-admin-only bypass and show its placeholder
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 16:25:37 -07:00
Devin AI
500790a93a fix(ui): keep every team selectable for org admins in the Team ID selector
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 16:25:37 -07:00
Devin AI
59693d5019 fix(ui): only offer team admins the teams they administer in the Team ID selector
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 16:25:37 -07:00
yassin
313093a8a0 fix(ui): gate MCP live connections tab to proxy admin tier roles
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 22:36:00 +00:00
mateo
bec1abd32e refactor(ui): drop unused rolesAllowedToSeeUsage, viewOnlyRoles and isViewOnlyRole exports
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 20:06:22 +00:00
ryan-crabbe-berri
eda81fff59 feat(ui): shared URL-state layer for tables and tabs
Add useUrlTableState (search, sort, page, page size and filter_<column>
in the query string via one nuqs useQueryStates call, with keyPrefix and
urlKeys for routes that host two tables or need legacy key names) and
useUrlTab (validated ?tab= param with a role-aware fallback). Migrate
the Virtual Keys table onto useUrlTableState with a byte-identical URL
contract and bind the Playground tab strip to ?tab=.

DataTable gains controlled columnVisibility/onColumnVisibilityChange
plus usePersistedColumnVisibility (localStorage per table id), and an
isError prop that keeps the server page clamp from rewriting a
deep-linked ?page= after a failed fetch. Virtual Keys uses both.

The expired-session redirect in handleError now keeps the query string
and hash so the return URL captured on re-login restores the filtered
view instead of the bare list.

Delete useTabRouting and tabRoutes, the pathname tab router left over
from the reverted path-per-tab attempt (#34327, reverted in #34629);
tab persistence has to be a query param on the static export.
2026-09-16 15:51:45 -07:00
Tin Chi Lo
109ca70f66 feat(auto-router): allow opted-in team members to manage their routers 2026-09-14 23:56:55 -07:00
Devin AI
b7596d6fba refactor(ui): drop redundant comment on canListUsers role list
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-11 17:16:30 +00:00
Devin AI
3630642110 fix(ui): allow Org Admin session role to resolve user emails in logs
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-11 16:21:20 +00:00
ryan-crabbe-berri
71d1bfb70a feat(ui): link the User, Team and Created By cells on the Virtual Keys page
The key detail page already walks out to the user, team and org behind a key,
but the Virtual Keys table rendered those same values as dead text, so getting
to a team meant copying its alias and searching the Teams page.

User, Team and Created By now render through the shared IdentityCell with an
href, the same hover-highlight-and-chevron affordance the Key column already
uses.

Sentinel ids do not get a link, since they have no detail page to open.
Rather than repeat that check at every call site, teamDetailHref and
userDetailHref now return undefined for "litellm-dashboard" and
"default_user_id", the way modelGroupHref already does for model grants, and
EntityLink falls back to plain text when it has no href, the way BadgeLink
already does. Both sentinels move into src/utils/sentinels.ts instead of
staying as string literals scattered across components.

Claude-Session: https://claude.ai/code/session_01NfwfQhamRNnSqgXMUjf3h4
2026-09-10 15:55:03 -07:00
ryan-crabbe-berri
1258d84221 refactor(ui): route the sidebar by pathname and shrink the ?page= shim to a redirect table
The sidebar and header were still keyed on legacy ?page= ids and mapped
back and forth through MIGRATED_PAGES, legacyPageHref and
legacyKeyForPathname. Leaves are now plain Next links to their path
route, the active item and breadcrumb come from usePathname, and the
setPage/defaultSelectedKey prop chain is gone.

The id-to-route table moves next to the dashboard root page as its only
consumer. That redirect now forwards the remaining query params instead
of dropping them, so deep links such as the proxy's MCP env-var setup
link (?page=mcp-servers&fill_env_vars=) no longer rely on the target page
reading the pre-redirect URL during its first render. The proxy builds
that link as /ui/mcp-servers?fill_env_vars= directly, and the Playground
warnings link to the real routes instead of relative ?page= URLs.

migratedHref is renamed uiHref, the /ui base-path helper it always was.
2026-09-05 16:44:11 -07:00
Mateo Wang
bad55da9bf
Merge pull request #38872 from BerriAI/litellm_fix_viewer_add_model_tab
fix(ui): hide model write affordances from view-only admin sessions
2026-09-01 14:52:54 -07:00
ryan-crabbe-berri
d9f7f9ea16 feat(ui): add search to Agent Hub tab and admin agents table
Ports the Model Hub search to the AI Hub Agent Hub tab and the admin
/agents toolbar as a client-side filter over agent name and description.
Extracts the hub search matching into utils/searchUtils and fixes the
public Model Hub rendering the whole catalog when a search matches
nothing (LIT-5230)
2026-09-01 11:46:58 -07:00
mateo-berri
b6bd749c02 fix(ui): withhold team-scoped model writes from view-only sessions too
The route-level RBAC in litellm/proxy/auth/route_checks.py 403s
/model/new, /model/update, and /model/delete for proxy_admin_viewer on
the session role alone, before ModelManagementAuthChecks' team-admin
carve-out can run. A view-only session therefore gets no model write
affordance, team admin or not.
2026-08-30 10:01:44 -07:00
mateo-berri
74fb398f9b fix(ui): hide model write affordances from view-only admin sessions 2026-08-29 21:40:28 -07:00
ryan-crabbe-berri
fe63ebdb19 fix(ui): filter the models page by exact model group instead of substring search
Pass the selected group as the exact model= param on /v2/model/info rather than as the substring search, so a group like gpt-4 no longer pulls gpt-4o rows into the page and count. Drop two comments that restated helper behavior.
2026-08-29 10:18:06 -07:00
ryan-crabbe-berri
56a80c8125 feat(ui): link team and key model chips to the models page filtered to that group
Model chips on the team info and virtual key info pages (overview and settings tabs) now link to /models-and-endpoints?model_group=<name>, which the All Models tab reads through a new nuqs-backed model group filter. Grant sentinels such as all-proxy-models stay plain badges. The selected group is also sent as the server-side search so the matching deployments are fetched even when they are beyond the first page.
2026-08-27 21:08:33 -07:00
tin-berri
0c50286a55
feat(ui): add per-key Savings tab to key detail page (#37693)
* feat(ui): add per-key Savings tab to key detail page

Adds a "Savings" tab to the key detail view, showing the same four metrics
and time-series chart as the proxy-wide Cost Optimization view, but scoped
to a single API key.

For org admins, the tab shows the key's full savings across all requests.
Non-admins see only their own requests on the key, with a scope note
explaining the limitation.

Root cause: userDailyActivityCall and userDailyActivityAggregatedCall
never forwarded an api_key query parameter to the backend, even though
both handlers already accept and filter by it.

Changes:

- networking.tsx: Add optional apiKey param to both daily activity call
  wrappers (appended to variadic options tuple for backward compatibility).

- costOptimizationUtils.ts: Extract shared metrics helpers (compressionOf,
  cachingOf, autorouterOf, savedTokensOf, cacheHitRatio) and shortDate
  so both UsageTab and KeySavingsTab use the same formulas and prevent
  divergence.

- useDailyActivityRange.ts: Refactor into useScopedDailyActivityRange(
  accessToken, scope: {userId, apiKey?}) for reuse-by-parameter unbundling.
  Role resolution stays at the entry point (useDailyActivityRange), not in
  a scoped caller. Update test expectations for new 6-arg tuple.

- UsageTab.tsx: Simplify by importing extracted helpers and SummaryCard
  component instead of defining them inline. No behavioral change.

- key_info_view.tsx: Insert "Savings" tab trigger between "Overview" and
  "Settings"; wire TabsContent to new KeySavingsTab component with lazy
  mounting (no keepMounted) to defer daily-activity fetch until tab opened.

- NEW: components/shared/SummaryCard.tsx — Shared presenter for four-tile
  summary row (label + value + hint + optional info popover). Extracted
  from UsageTab so both surfaces show identical tile layout without CSS
  divergence.

- NEW: components/templates/KeySavingsTab.tsx — Per-key view with admin/
  non-admin scope branching, empty-state messaging, same chart toggles
  and info popovers as UsageTab.

- NEW: components/templates/KeySavingsTab.test.tsx — 7 tests covering mount,
  loading state, empty state, scoping, and scope-note visibility.

Authorization: No new permission check. Both backends gate api_key filter
by the same user role check that governs the request itself. Non-admins
must send their own user_id and can only see their own keys.

Tests: 6121 pass (1 pre-existing failure unrelated to this change).

Prior art / collision note:
- PR #37570 (budgets tab) lands in same TabsList hunks as "Savings" tab,
  but different tab names so conflict trivial if both merge.
- PR #37659 (my own) adds progress/cancelled/cancel to DailyActivityRange,
  but this PR uses stable three-field interface from staging.

* fix(ui): scope spend view by the backend's admin-view contract, not all_admin_roles

Greptile flagged org admin handling on the key savings tab. The live bug it
described does not fire today: useAuthorized supplies session-role labels and
all_admin_roles only carries the raw org_admin spelling, so an org admin was
already scoped. That safety was accidental, so replace the predicate with
spendScopeUserId / hasProxyWideSpendView in utils/roles.ts, mirroring the
backend's user_api_key_has_admin_view (proxy admin and admin viewer only, org
admin excluded in both spellings), and use it in both useDailyActivityRange
and KeySavingsTab

Reclassify the KeySavingsTab render test as an integration test per the
repo's unit/integration split, move scope-resolution coverage to roles.test.ts
as a full role matrix, use real session-role values instead of raw ones, and
assert tile totals against non-empty metrics. Replace the nested ternary in
the chart body (frontend-lint error) with flat conditional rendering

* fix(ui): show auto-router savings as the fourth key-savings tile

Cache hit rate had displaced auto-router savings from the fourth slot,
diverging from the org-wide Cost Optimization page's tile order. Match
it: Total / Compression / Prompt caching / Auto-router, with cache hit
rate as a fifth tile.

* fix(ui): drop cache hit rate from the key savings tiles

Keep the four tiles this page is meant to show: total, compression,
prompt caching, and auto-router savings.

* fix(ui): stop an empty api_key from widening a key-scoped activity read

The paginated and aggregated daily-activity wrappers disagreed on an
empty filter value: the paginated one appended it, the aggregated one
coerced it to undefined with || and dropped it. Since the aggregated
call is the one tried first, an empty key hash would have silently
turned a key-scoped read into a proxy-wide one and reported every
key's savings as this key's. Use ?? so both send the filter through
and it matches nothing instead.

* style(ui): satisfy prettier and the inline-object lint rule in key savings tests

* refactor(ui): drop the cacheHitRatio extraction left over from the removed tile

* fix(ui): pass daily-activity filters raw so both transports agree at the null boundary

* refactor(ui): share the savings tiles and totals between both surfaces

The per-key Savings tab and the proxy-wide Cost Optimization tab carried a byte-identical
four-tile block, three long metric-definition strings included, and five identical useMemo
totals. Both now render SavingsTiles and total through useSavingsTotals, so the donut cannot
slice numbers the tile above it disagrees with.

* docs(ui): say request, not mount, in the savings tab comment

The comment claimed mounting eagerly would fire the rollup sweep, which reads as a claim about
the bundle. Only the request is deferred; the module ships with the key page either way.

* test(ui): pin the daily-activity args array against the real caller signatures

The sibling unit test mocks networking, so it checks the positional array against itself and
stays green when the array and a networking signature drift apart. Swapping user_id and api_key
in the aggregated signature alone passes there and fails here on user_id=hash-abc.

* style(ui): hoist the daily-activity query options out of the call argument

The four-property object literal tripped local/no-large-inline-object-arg. The violation predates
this branch, which only moved the line into the annotated range, and the rule count drops 550 to 549.
2026-08-21 14:50:02 -07:00
yuneng-jiang
7675ba8717
test(ui): split the vitest suite into unit, component, integration and type projects (#37488)
* test(ui): split the vitest suite into unit, component and integration tiers

Every test file booted jsdom, including the ~1800 that assert pure functions
and never render. They now run as a separate vitest project in the node
environment, where the whole tier finishes in under four seconds.

The tiers are vitest projects rather than a naming convention, so CI can run
them as independent jobs. A .test.ts that renders React, a hook test being the
usual case, is listed explicitly and stays in the jsdom tier.

* test(ui): report per-test duration against a per-tier budget

A timeout only catches a hung test, and it has to stay generous enough to
survive a loaded runner, so it never reports the multi-second render tests that
make CI fail the moment the box is busy. Budgets are separate and far tighter:
50ms unit, 1s component, 3s integration.

The counts are laptop measurements, so the CI job is report-only for now.
Flipping it to blocking is one line once CI has published its own numbers.

* test(ui): run the tiers as separate CI jobs and stop clicking popups by text

The old job ran every file in one process, so the single slowest file set the
wall clock and a bigger box bought nothing. The tiers now run as separate jobs
with the component tier sharded four ways.

getByText and findByText match hidden nodes, so they resolve against a closed
Base UI popup whose positioner still carries pointer-events: none, and the
click lands or not depending on how far the open transition got. Two files
failed this way, one three runs in five and one every run. Querying the option
by role waits for it to be visible, and both are now stable. A lint rule keeps
the pattern from coming back.

* test(ui): give React Testing Library's async queries a CI-sized window

findBy* and waitFor run on asyncUtilTimeout, which defaults to 1000ms and is
independent of vitest's testTimeout. Raising the vitest timeout therefore did
nothing for them: a query still gave up after one second while the test had 59
seconds of budget left, which is why a loaded runner produced 'Unable to find
role=...' rather than a timeout.

UserSearchModal is the worked example. The role query it makes resolves in
249ms on a laptop and blew past 1000ms on CI, failing the run at 1494ms. Five
seconds keeps the same assertions and only widens the window a failing query
waits before reporting; a passing query still resolves the moment the element
appears.

* test(ui): calibrate the tier budgets from real CI numbers and report by default

The first CI run showed the laptop counts were badly off: component 176 local
against 326 on CI, integration 87 against 128. The maxima now come from that
run with headroom.

continue-on-error still painted the check red, which is the opposite of the
point, so the report-only decision moves into test-budgets.json as an explicit
enforce flag. The job passes and prints the counts; flipping enforce to true
makes it a gate.

* docs(ui): drop the CLAUDE.md edits from the tier split

Keeping this PR to the vitest, CI and test changes.

* ci(ui): run every tier in one job instead of eight check rows

Sharding bought nothing. Measured on the first run of this branch, the
component tier unsharded finishes in 198s while the integration tier is floored
at 384s by a single file, so integration was always the critical path and the
four component shards only added rows. One job running every project comes in
around 384s against the 426s the split jobs took.

Eight rows named things like 'component (2)' also told a reviewer nothing, on a
PR page that already carries forty checks.

The job keeps the id ui-unit-tests because guard-internal-staging requires that
exact context; renaming the jobs had silently stopped it reporting, which would
have blocked every merge on a check that no longer existed. The workflow's
display name becomes UI Tests since it runs more than unit tests.

The tier split itself is untouched: it lives in the vitest projects config, so
the unit tier still runs in node with no jsdom, and each tier keeps its own
timeout and budget.

* fix(ui): stop the type check from running the whole suite a second time

test:types was 'vitest --run --typecheck.only'. Under test.projects that flag
is ignored and the root-level typecheck block is not inherited, so the step
collected each project's normal include and ran all 8464 runtime tests instead
of type-checking. It took 542s on CI against 33s on the flat config it
replaced, and the job then ran the same suite again in the next step.

Typecheck now belongs to a project of its own, with an empty include so it
contributes no runtime tests, and the CI job runs one vitest invocation for all
four. The type tier adds about 3s to a full run and reports 'Type Errors: no
errors' rather than a suite of tests.

Verified it still catches things: breaking SortingState in DataTable.test-d.tsx
fails with 'Type number is not assignable to type string' and exit 1, and
restoring it passes.

* test(ui): scope the split down to the vitest tier projects

Removes everything from this branch that was not the tier split.

The three lint rules brought 1381 lines of grandfathered suppressions in
eslint-suppressions.json, which is 81% of the branch's added lines and
debt nobody is going to pay down. The per-test duration budget does not
scale as a CI step. Both are gone, along with the two query rewrites the
no-click-by-text rule forced: those files pass 10/10 at this base, quiet
and under load, so there was no failure behind them.

The workflow is byte-identical to the base again. It already runs
npm run test:types and then vitest related on pull requests, so PR cost
is unchanged; the split only repoints test:types at the new project.
That project is required, not optional: vitest silently ignores
--typecheck.only under test.projects, so without it the type script
collects the whole suite instead of the one typed file.

Restores the base 60s testTimeout on the unit tier. The 5s cap was not
part of the split and failed ChatShell.serverRootPath.test.ts, a 960ms
test, under load.
2026-08-19 20:40:14 +00:00
yuneng-jiang
b19d59be09
refactor(ui): move the model info edit form off antd Form (#37392)
* test(ui): characterize the model info and cache control submit payloads

Pins the antd behaviour these forms have today, ahead of moving them onto
react-hook-form: the full model info PATCH body, the sticky touched-field
semantics that decide which pricing keys ship, the mounted-only cache control
keys, and the string-typed injection point index.

* refactor(ui): move the model info edit form off antd Form

The deployment edit form on the model info view now runs on react-hook-form
with a zod resolver and shadcn controls, extracted into ModelInfoEditForm so
the view keeps the payload builder and the form keeps the fields.

Cache control injection points become a presentational value/onChange child,
which lets the model info view host it through react-hook-form while the add
model form keeps hosting it through antd. That child never wrote to a real
store on either side: it registered under cache_control_points while both
parents read cache_control_injection_points, so its form prop was inert.

antd marks a field touched on change and never clears it, and neither
touchedFields nor dirtyFields reproduces that, so the four pricing keys that
gate on it track first change explicitly.

The PTU rules move from antd validator wrappers to pure predicates that both
surfaces share, since the add model form still feeds the wrappers to its own
antd form.

* refactor(ui): trim comments and type the model record prop on the edit form

Cuts the explanatory comments that the house rules do not allow, keeping
only the three that record non-obvious library behaviour plus the eslint
directive, and narrows the modelData prop to the two fields the form reads.

Corrects the claim in 5b7ecede4e that the cache control child registered
the wrong key. At the staging tip the Form.List registration is on
cache_control_injection_points and is live, which is why this PR rehosts
it into advanced_settings. The dead part is the three
getFieldValue("cache_control_points") readers, whose key nothing
registers, so updateCacheControlPoints dereferences undefined and the
caught error reaches the console on every role, index or remove change.
2026-08-18 17:40:49 -07:00
Yassin Kortam
3fe0201d40
fix(proxy): let org admins view their organization's usage (#37235)
An internal user who administers an organization saw an empty
Organization Usage dashboard and had to be promoted to proxy admin to
see any of it.

Two independent gates were closed on them. The route layer rejected
GET /organization/daily/activity with 401 before the handler ran, since
the route belonged to no list a non-proxy-admin can reach, and the
handler's own org-admin scoping was therefore dead code. In the
dashboard, viewOrganizationUsage was granted by session role alone, and
an org admin's session role is internal_user, so the Organization Usage
option never rendered and its data fetch stayed disabled.

The route now sits in self_managed_routes, where the handler restricts
results to organizations the caller is ORG_ADMIN of and 403s on any
other org, and viewOrganizationUsage joins the existing per-capability
org-admin allowance that already covers viewDeletedTeams.

A caller who administers no organization resolves to an empty id list
rather than to None, so the organization-alias lookup is scoped by that
same list instead of reading the whole table.

The Usage page falls back to the global view when org-admin membership
is revoked while it is open, so the selector never keeps a value it no
longer offers.
2026-08-18 14:44:36 -07:00
ryan-crabbe-berri
83ae623733
refactor(ui): codemod every toast call site onto lib/toast and delete the antd-era facades (#37253)
MessageManager and NotificationManager were thin facades over lib/toast since #37207. This
rewrites their ~750 call sites (226 files) to import { toast } from @/lib/toast directly:
success/info/warning/error keep their names, fromBackend becomes fromError, destroy/clear
become dismiss. The one config-object caller (CreateMCPServer's admin-review branch) becomes
an explicit toast.success(message, { description }). Behaviour is unchanged: no production
caller passed a duration, so every toast keeps the same kind, title and default duration.

Tests: the global vitest mock now targets @/lib/toast (toast.test.ts opts back out with
vi.unmock), so the per-file vi.mock boilerplate for the facades is deleted and assertions read
toast.success / toast.fromError. The two facade files, their test and their filename-case
suppressions are removed, along with the commented-out facade calls left in networking.tsx
2026-08-18 18:24:49 +00:00
Yuneng Jiang
6670061a33 test(ui): assert presence and state so dashboard tests fail when behaviour breaks
One test could not fail: it asserted on the Promise returned by an unawaited
findAllByText, and a pending Promise is always truthy, so the organization
detail view had no working coverage at all.

Rewrites 316 assertions across 128 files onto matchers that describe what the
user perceives, and awaits the queries that were being compared as Promises.

Most of this was mechanical, but the fixers behind these rules are not
trustworthy, so every site they damaged was repaired by hand. The quiet one
worth naming: prefer-to-have-text-content wraps strings in new RegExp() without
escaping, turning toContain("100K+ requests") into a pattern meaning "100
followed by one-or-more K". That compiles, lints clean, and keeps passing while
no longer asserting what it claims.
2026-08-17 14:46:55 -07:00
ryan-crabbe-berri
9d40cd4df7
feat(ui): link key info header to its user, creator, team, and organization (#37187)
* feat(ui): link key info header to its user, creator, team, and organization

The key info page showed the owning user and creator as plain text and never surfaced the team or organization at all, so walking from a key to its parent entities meant copying ids into other pages. The header now renders User and Created By as links to the user detail page, and gains a far-right column with Team and Organization links (alias when known, id otherwise). Client-side navigation logic shared by BadgeLink and IdentityCell moves into a reusable EntityLink so all entity links behave the same

* test(ui): mock next/navigation in VirtualKeysTable test

KeyInfoView now renders EntityLink, which calls useRouter, so the table test that opens the key detail needs the app router mocked
2026-08-17 10:17:28 -07:00
yucheng-berri
0a25756e78
fix(ptu): stop per-token billing on a PTU-configured deployment (#36829)
A deployment with PTU flat-cost attribution also billed every request per
token, so a team paid for reserved capacity and again for the traffic that
capacity serves. Nothing set the per-token price and an unset price falls
back to the public cost map, which made the double charge the default.

/model/new and /model/{id}/update now store zero for every pricing field the
cost map could otherwise fill, refuse a price the caller supplies alongside
PTU config with a 400 naming the field, zero a price already on the row
rather than rejecting later edits of unrelated fields, and drop the zeros
again when the PTU config goes.

A PTU deployment is no longer read as a free model by the budget checks,
which would have waived every budget for it.
2026-08-13 20:16:12 -07:00
Yassin Kortam
b72dab8049
feat(ui): show provider prompt cache tokens in chat response metrics (#36827)
The chat metrics bar reported In/Out/Reasoning/Total/cost only, so a
playground user had no signal that provider prompt caching worked. The
cached-token counts were already visible in the Logs drawer, which meant
the answer to "does caching work here" lived on a different page.

Adds cacheReadTokens and cacheCreationTokens to TokenUsage and renders
them as two chips, reusing the prompt-cache tooltip wording already
introduced for the Logs drawer so both surfaces say the same thing.

A single helper, extractPromptCacheTokens, normalizes the three usage
shapes the playground consumes: Anthropic Messages
(cache_read_input_tokens / cache_creation_input_tokens), chat
completions (prompt_tokens_details) and the Responses API
(input_tokens_details). All three producers call it instead of parsing
per surface. Counts that are absent, zero or non-finite are dropped, so
providers without prompt caching render exactly what they render today.
2026-08-13 16:58:14 -07:00
yuneng-jiang
80f34cb6fc
Merge pull request #36478 from BerriAI/litellm_/vibrant-booth-d4258b
fix(ui): restore the Logs Deleted Teams tab for organization admins
2026-08-10 17:18:26 -07:00
Yuneng Jiang
d46ef9aeb4
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/vibrant-booth-d4258b
# Conflicts:
#	ui/litellm-dashboard/src/utils/capabilities.test.ts
2026-08-10 16:50:50 -07:00
Yuneng Jiang
75e6a26418
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/epic-turing-e9b2f0
# Conflicts:
#	ui/litellm-dashboard/src/utils/capabilities.test.ts
#	ui/litellm-dashboard/src/utils/capabilities.ts
2026-08-10 16:50:28 -07:00
yuneng-jiang
022c0cce95
Merge pull request #36469 from BerriAI/litellm_/nifty-knuth-f7f2c6
fix(ui): gate the Old Usage page behind a proxy-admin capability
2026-08-10 16:46:52 -07:00
yuneng-jiang
487f8b2408
Merge pull request #36472 from BerriAI/litellm_/modest-mcclintock-5b4d30
fix(ui): scope Virtual Keys and Logs team lists to the caller
2026-08-10 16:46:30 -07:00
Yuneng Jiang
41eed477f3
test(ui): name the org-admin session role instead of commenting it 2026-08-10 16:29:01 -07:00
Yuneng Jiang
f306927853
fix(ui): restore the Logs Deleted Teams tab for organization admins
Hiding the tab behind all_admin_roles took it away from org admins, who are
entitled to it: /v2/team/list?status=deleted returns 200 for them, scoped to
their own organizations. An org admin is an organization membership rather
than a global role, so their session carries user_role "internal_user" and no
role-based gate can ever see them.

Lift the membership lookup the left nav already did into a shared
useIsOrgAdmin hook, and let a capability opt into allowing org admins.
viewDeletedTeams is the only one that opts in; the backend still refuses org
admins on /v1/tool/list, /policies/list, /prompts/list and /audit, so those
gates stay as they are. The hook also accepts a session role of org_admin, in
case a deployment maps one through SSO.
2026-08-10 16:11:05 -07:00
Yuneng Jiang
dc69f6e4a2
test(ui): trim rationale comments in the Old Usage gate tests
Drop the duplicated org_admin note and shorten the flush-window note to
the one line that keeps the liveness test from looking redundant.
2026-08-10 15:39:31 -07:00
Yuneng Jiang
e7450b11ba
test(ui): drop redundant commentary from the team-list scoping tests
The removed comments restated the test names and the assertions directly
below them. The reasoning they carried is already recorded in the commit
that introduced the fix and in the pull request body.
2026-08-10 15:38:59 -07:00
Yuneng Jiang
ab904e8954
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/epic-turing-e9b2f0
# Conflicts:
#	ui/litellm-dashboard/src/utils/capabilities.ts
2026-08-10 15:36:24 -07:00
Yuneng Jiang
255d65192e
fix(ui): gate four sidebar pages on the roles their endpoints allow
Workflow Runs, Memory and Guardrails Monitor were visible to every role
while their page-load routes are proxy-admin-only, so a non-admin got a
page shell and a 401. Cost Optimization was half-broken the same way: its
Overall charts run on /user/daily/activity, which every role may call, but
tool spend, prompt caching, prompt compression and auto-router benchmarks
are all proxy-admin-only.

Add viewWorkflowRuns, viewMemory, viewGuardrailUsage and
viewProxyWideCostData, each gating the nav entry, the page and the request
together. The first three hide their page, including the direct-URL path,
since nothing on them works for a non-admin. Cost Optimization keeps its
page and drops only the parts a non-admin cannot read.

Gating both Agentic children left roles with no visible child rendering the
parent as a leaf link to ?page=agentic, which is not a route, so a parent
whose children are all filtered out is now dropped.

Role lists follow what the proxy actually grants: proxy_admin and
proxy_admin_viewer are served, and org admins are not, because
_user_is_org_admin needs an organization_id that a page-load GET never
carries.
2026-08-10 15:35:54 -07:00
ryan-crabbe-berri
ec9ab43d20
feat(ui): show vector store indexes on the Vector Stores page (#36306)
* feat(ui): show vector store indexes on the Vector Stores page

Adds a proxy-admin-only Indexes tab listing rows from GET /v1/indexes:
index name, backing vector store, provider index, creator, and created
date. The tab is hidden for non proxy-admin roles to match the
endpoint's gate, and data loads lazily on first visit.

* feat(ui): link index rows to their vector store and creator

Vector Store cells open the store's info view when the name resolves to
a registered store, and Created By cells deep link to the users page via
a new userDetailHref, with the users page reading the user query param
through nuqs so the link is shareable.

* feat(ui): link docs and note supported providers on Indexes tab

* fix(ui): show not-found state instead of infinite loading for missing vector store
2026-08-10 15:24:20 -07:00
Yuneng Jiang
25172e94d0
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/nifty-knuth-f7f2c6
# Conflicts:
#	ui/litellm-dashboard/src/utils/capabilities.test.ts
#	ui/litellm-dashboard/src/utils/capabilities.ts
2026-08-10 15:10:44 -07:00
Yuneng Jiang
5096fc7927
fix(ui): gate the Old Usage page behind a proxy-admin capability
The Old Usage nav entry carried no role restriction, so every role saw it
and the page immediately fired eight /global/spend/* requests that the
proxy withholds from non-admins, producing a wall of 401s.

Gate the nav entry, the page, and both of its mount effects behind a
single viewGlobalSpend capability scoped to proxy_admin and
proxy_admin_viewer, matching what the backend actually serves.

Also drop the session JWT that adminspendByProvider put in the
/global/spend/provider query string; the handler never read it.
2026-08-10 15:10:08 -07:00
Yuneng Jiang
8f0644e63f
fix(ui): scope Virtual Keys and Logs team lists to the caller
The Virtual Keys table and the Logs page team filter both asked for every
team on the proxy, which /v2/team/list and /team/list reject with a 401 for
any role below proxy admin or org admin. Both endpoints answer the same
request with the caller's own teams when it carries a user_id, so send one.

Only the two unscoped call sites change. The remaining callers either
already role-branch or render on surfaces gated to roles the endpoints
answer broadly, and scoping those would shrink the list they see: a proxy
admin scoped to their own id gets nothing back, and an org admin scoped on
/team/list loses the org teams they administer but do not belong to.

The shared helper reads the display-form session role rather than
all_admin_roles, which mixes display labels with raw role names and so does
not match the "Org Admin" value the dashboard actually holds.
2026-08-10 15:00:45 -07:00
Yuneng Jiang
23f4eaaa61
Merge remote-tracking branch 'origin/litellm_internal_staging' into HEAD
# Conflicts:
#	ui/litellm-dashboard/src/utils/capabilities.test.ts
#	ui/litellm-dashboard/src/utils/capabilities.ts
2026-08-10 14:06:58 -07:00
Yuneng Jiang
3a2830ee76
Merge remote-tracking branch 'origin/litellm_internal_staging' into HEAD
# Conflicts:
#	ui/litellm-dashboard/src/utils/capabilities.ts
2026-08-10 13:47:57 -07:00
Yuneng Jiang
b40a469836
Merge remote-tracking branch 'origin/litellm_internal_staging' into HEAD
# Conflicts:
#	ui/litellm-dashboard/src/components/templates/key_edit_view.tsx
#	ui/litellm-dashboard/src/utils/capabilities.ts
2026-08-10 13:43:09 -07:00
yuneng-jiang
3e680a4ffc
Merge pull request #36333 from BerriAI/litellm_/elated-bhaskara-739752
fix(ui): hide admin-only Logs tabs from roles that cannot call their endpoints
2026-08-10 13:39:51 -07:00