Ant Design Select does not accept null as an option value.
Change { value: null, label: "None" } → { value: "", label: "None" }
and remove initialValue={null} from the Form.Item (no initial selection
is correctly represented by undefined, not null). The submit handler
already deletes litellm_credential_name from params when absent, so
the empty string is handled safely.
Co-Authored-By: Claude Sonnet 4 (1M context) <noreply@anthropic.com>
updateProxyBaseUrl had its own copy of the localhost:4000 fallback.
When getUiConfig received proxy_base_url=null from the backend it used
that hardcoded value, immediately overwriting the correct URL set via
NEXT_PUBLIC_LITELLM_PROXY_URL.
Fix: use the module-level defaultProxyBaseUrl (which already respects
NEXT_PUBLIC_LITELLM_PROXY_URL) as the fallback inside updateProxyBaseUrl.
Also remove the dead Form.useForm() instance in CredentialsPanel that
was never connected to any <Form form={...}>, causing the Ant Design
"Instance created by useForm is not connected" console warning.
Co-Authored-By: Claude Sonnet 4 (1M context) <noreply@anthropic.com>
The hardcoded localhost:4000 default in networking.tsx made it impossible
to run the frontend dev server against a port-forwarded K8s backend without
manually setting localStorage.
Add NEXT_PUBLIC_LITELLM_PROXY_URL as the top-level override in
defaultProxyBaseUrl. When set, all API calls (including the getUiConfig
discovery request) go to that URL. Falls back to the existing
localhost:4000 / NEXT_PUBLIC_USE_REWRITES logic unchanged.
Also fix getUiConfig to use proxyBaseUrl instead of defaultProxyBaseUrl,
so that litellm_worker_url set via localStorage is also respected for the
initial discovery request.
Co-Authored-By: Claude Sonnet 4 (1M context) <noreply@anthropic.com>
keys?.keys || [] creates a fresh [] reference on every render, which
triggers useFilterLogic's useEffect (dep: keys), which calls
setFilteredKeys, which re-renders the parent, which creates another
new [] — maximum update depth exceeded.
Stabilize the reference with useMemo(() => keys?.keys ?? [], [keys])
so the effect only fires when the actual server data changes.
Co-Authored-By: Claude Sonnet 4 (1M context) <noreply@anthropic.com>
After a credential was added/updated/deleted in the "LLM Credentials" tab,
ModelsAndEndpointsView's credentialsList (passed to the "Add Model" dropdown)
was never refreshed because CredentialsPanel called refetch() on its own
observer only.
Replace with queryClient.invalidateQueries({ queryKey: credentialsKeys.all })
— the established cross-component invalidation pattern used throughout the
codebase (accessGroupKeys, projectKeys, cloudZeroSettingsKeys, etc.) — which
marks the shared React Query cache entry as stale and triggers a refetch for
all active subscribers, including ModelsAndEndpointsView.
Co-Authored-By: Claude Sonnet 4 (1M context) <noreply@anthropic.com>
The data-testid attributes added to React components are not present
in the CI-built UI output. Switch to using getByRole and getByText
selectors which work with the rendered DOM regardless of build cache.
Add E2E tests covering:
- Test connection with bad credentials shows failure modal
- Adding a specific model and verifying it appears in All Models table
- Adding a wildcard route and verifying it appears in All Models table
- Verifying model dropdown shows provider-specific models (existing test updated)
Added data-testid attributes to UI components to support stable test selectors.
Tests verified passing 3/3 consecutive runs with zero flakiness.
Unit Tests: Proxy DB Operations / proxy-db (auth-checks, tests/proxy_unit_tests/test_auth_checks.py tests/proxy_unit_tests/test_user_api_key_auth.py, 20, 8) (push) Waiting to run
Unit Tests: Proxy DB Operations / proxy-db (remaining, tests/proxy_unit_tests --ignore=tests/proxy_unit_tests/test_key_generate_prisma.py --ignore=tests/proxy_unit_tests/test_auth_checks.py --ignore=tests/proxy_unit_tests/test_user_api_key_auth.py, 30, 8) (push) Waiting to run
Reviewer flagged that cleanup failures were silently swallowed and
suggested asserting `delete.ok()`. While thinking through the fix, the
actual question turned out to be "does the cleanup matter at all?" —
and the answer is no.
The e2e runner (`run_e2e.sh`) spins up a fresh postgres container per
invocation and tears it down at the end, so every local and CI run
starts with an empty DB. Playwright retries share the same DB but each
attempt creates a new model with a unique `Date.now()` name and only
queries its own model, so orphans from failed attempts never collide
with later attempts or other tests. Nothing else in the suite reads
the all-models table.
Keeping the cleanup would also turn every write test into an implicit
delete test, coupling responsibilities and inflating runtime — which
is probably why `teams.spec.ts` (create a team), `keys.spec.ts`
(update key limits), etc. all leave their entities in place. Matching
that convention, drop the try/finally block and the `createdModelId`
tracking. 12 lines removed, no behavior change.
Covers the full write-path flow for team-scoped models on the Models +
Endpoints page: create via /model/new, click the row to open the detail
view, click Edit Settings, change TPM/RPM, click Save Changes, assert
the new values render back. Cleans up via /model/delete in finally so
reruns stay deterministic.
Requires store_model_in_db: true in the fixture general_settings so the
proxy accepts /model/new and /model/delete. No existing test in the
dashboard e2e suite reads the all-models table or hits the model CRUD
endpoints, so enabling the flag has no cross-test impact.
The suite was superseded by ui/litellm-dashboard/e2e_tests/ on 2026-04-08
and is no longer referenced by CircleCI, docs, or Makefile targets. Drop
the directory wholesale and remove the orphaned e2e:psql npm script that
pointed at its runner.
Updates the expected header text to "Guardrails Settings" to match
GuardrailSettingsView's rendering, and moves the mock guardrails
from team_info.guardrails (legacy top-level path that nothing
reads) to team_info.metadata.guardrails where the component
actually looks. Also tightens the assertion to verify the
individual guardrail names appear, not just the section header.
Previously these were silently dropped with a verbose warning, which
could break observability integrations without surfacing a clear error.
Now raises ValueError with remediation steps (configure server-side
or pass the resolved value) so callers get immediate, actionable feedback.
Converts GuardrailSettingsView from @tremor/react (Badge, Text) to
antd (Tag, plain spans) as part of the Tremor migration. Also
captures the "no new Tremor imports" rule in CLAUDE.md and expands
the existing note in AGENTS.md with the specific antd equivalents
and the yellow→gold gotcha.
Pulls the Global / Team-specific subsection rendering out of
TeamInfo.tsx into a shared GuardrailSettingsView component with
card and inline variants, used on both the team Overview tab
(inside the existing Tremor Card) and the Team Settings tab read
view. The Global subsection header now carries a GlobalOutlined
icon, and since the icon is load-bearing the edit-form chip
coloring is simplified to a single blue instead of green/blue.
The Team Settings tab's read view listed every team field except
guardrails. Adds a Guardrails entry after Status with the same
Global / Team-specific subsections used on the Overview tab, so
the kill switch state and per-section membership are visible
without entering edit mode.
Replaces the flat guardrails list with two subsections under the
Guardrails card, so the global vs. team-specific distinction is
carried by the section headers instead of per-badge markers. The
kill-switch state now renders in place of the Global subsection as
"Bypassed for this team", and the separate "Disable Global
Guardrails" field with its confusing "Disabled - Global guardrails
active" badge is removed.
Addresses a11y feedback — global vs. non-global guardrails were
distinguished only by color (green vs. blue). Adds GlobalOutlined
next to global guardrails in (1) the selected-chip tagRender, (2)
the dropdown OptGroup label, and (3) the team info read view badge.
The blog CSS selectors for dark mode used descendant selectors like
[data-theme='dark'] .blog-wrapper which never matched because both
data-theme and .blog-wrapper are applied to the same <html> element
by Docusaurus. Fixed by using compound selectors (no space):
[data-theme='dark'].blog-wrapper.
Also added missing dark-mode overrides for:
- pre/code blocks in blog posts
- link colors in blog posts
- marquee items, separators, and labels on blog list page
- pagination links on blog list page
- meta text and author separators on blog list page
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Krrish Dholakia <krrish-berri-2@users.noreply.github.com>
Brings the date-range branch in line with the non-date-range branch which
already hashes sk- prefixed tokens before querying. Adds coverage for
filter-combination behavior in view_spend_logs.