mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-16 23:41:43 +00:00
feat(ui): send logging_exporters as a typed field; picker trusts backend scoping
Assignments now live on a typed logging_exporters column, so the key/team/org create and update forms send logging_exporters as a top-level field instead of packing it into the metadata JSON blob, and the info views read it off the object (via loggingExportersOf, column-first with a metadata fallback for rows written before the column existed). The destination picker no longer filters options by the caller's role client-side. GET /credentials is already scoped server-side by the same predicate the assignment gate and the resolver use, so the picker renders exactly what the backend returns; the previous isAdminRole short-circuit treated org-admins and admin-viewers as 'see every destination', which was broader than the backend's proxy-admin-only 'see all'. Regenerated schema.d.ts for the new field.
This commit is contained in:
parent
a86e333832
commit
9733ce5809
11 changed files with 126 additions and 162 deletions
|
|
@ -470,21 +470,12 @@ const Teams: React.FC<TeamProps> = ({ accessToken, userID, userRole, premiumUser
|
|||
formValues.metadata = JSON.stringify(metadata);
|
||||
}
|
||||
|
||||
// Merge admin-owned logging_exporters into metadata at create time so the
|
||||
// user can assign destinations from the new-team form (instead of create-then-edit).
|
||||
if (Array.isArray(formValues.logging_exporters) && formValues.logging_exporters.length > 0) {
|
||||
let metadata: Record<string, unknown> = {};
|
||||
if (typeof formValues.metadata === "string" && formValues.metadata.trim().length > 0) {
|
||||
try {
|
||||
metadata = JSON.parse(formValues.metadata);
|
||||
} catch (e) {
|
||||
console.warn("Invalid JSON in metadata field, starting with empty object");
|
||||
}
|
||||
}
|
||||
metadata = { ...metadata, logging_exporters: formValues.logging_exporters };
|
||||
formValues.metadata = JSON.stringify(metadata);
|
||||
// logging_exporters is a top-level typed field on the team (its own column),
|
||||
// not part of the free-form metadata blob; send it as-is when set so the user
|
||||
// can assign destinations from the new-team form (instead of create-then-edit).
|
||||
if (!Array.isArray(formValues.logging_exporters) || formValues.logging_exporters.length === 0) {
|
||||
delete formValues.logging_exporters;
|
||||
}
|
||||
delete formValues.logging_exporters;
|
||||
|
||||
if (formValues.secret_manager_settings) {
|
||||
if (typeof formValues.secret_manager_settings === "string") {
|
||||
|
|
|
|||
|
|
@ -4,22 +4,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|||
import LoggingExportersSelect from "./LoggingExportersSelect";
|
||||
|
||||
const mockUseCredentials = vi.fn();
|
||||
const mockUseAuthorized = vi.fn();
|
||||
const mockUseTeams = vi.fn();
|
||||
const mockUseOrganizations = vi.fn();
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/credentials/useCredentials", () => ({
|
||||
useCredentials: () => mockUseCredentials(),
|
||||
}));
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({
|
||||
useTeams: () => mockUseTeams(),
|
||||
}));
|
||||
vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
|
||||
useOrganizations: () => mockUseOrganizations(),
|
||||
}));
|
||||
|
||||
vi.mock("antd", async () => {
|
||||
const React = await import("react");
|
||||
|
|
@ -48,10 +36,7 @@ vi.mock("antd", async () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Default: a proxy admin (formatted role "Admin"), no team/org membership needed.
|
||||
mockUseAuthorized.mockReturnValue({ userRole: "Admin" });
|
||||
mockUseTeams.mockReturnValue({ data: [] });
|
||||
mockUseOrganizations.mockReturnValue({ data: [] });
|
||||
mockUseCredentials.mockReset();
|
||||
});
|
||||
|
||||
describe("LoggingExportersSelect", () => {
|
||||
|
|
@ -99,32 +84,21 @@ describe("LoggingExportersSelect", () => {
|
|||
expect(screen.getByTestId("empty").textContent).toMatch(/proxy admin/i);
|
||||
});
|
||||
|
||||
it("scopes a non-admin caller to destinations granted to their team/org (plus global/auto_enable)", () => {
|
||||
// An internal_user who is a member of team-a. They must see only what they could
|
||||
// actually assign: the team-a destination, the global one, and the auto_enable
|
||||
// default -- never the team-b destination or the foreign-org one. This mirrors the
|
||||
// backend assignment gate; the backend stays the authoritative check.
|
||||
mockUseAuthorized.mockReturnValue({ userRole: "Internal User" });
|
||||
mockUseTeams.mockReturnValue({ data: [{ team_id: "team-a" }] });
|
||||
mockUseOrganizations.mockReturnValue({ data: [{ organization_id: "org-a" }] });
|
||||
it("shows exactly the logging destinations the backend returned, without any client-side scope filtering", () => {
|
||||
// GET /credentials is already scoped server-side (proxy admin -> all; team/org
|
||||
// admin -> only in-scope destinations) by the same predicate the assignment gate
|
||||
// and the resolver use. The picker must therefore render every logging-typed
|
||||
// destination in the response verbatim; re-filtering here by role/scope on the
|
||||
// client would risk disagreeing with the authoritative backend in either
|
||||
// direction. This response mixes access shapes to prove none are dropped locally.
|
||||
mockUseCredentials.mockReturnValue({
|
||||
data: {
|
||||
credentials: [
|
||||
{
|
||||
credential_name: "mine-team",
|
||||
credential_info: { credential_type: "logging", access: { teams: ["team-a"] } },
|
||||
},
|
||||
{
|
||||
credential_name: "foreign-team",
|
||||
credential_info: { credential_type: "logging", access: { teams: ["team-b"] } },
|
||||
},
|
||||
{ credential_name: "mine-org", credential_info: { credential_type: "logging", access: { orgs: ["org-a"] } } },
|
||||
{
|
||||
credential_name: "foreign-org",
|
||||
credential_info: { credential_type: "logging", access: { orgs: ["org-z"] } },
|
||||
},
|
||||
{ credential_name: "team-scoped", credential_info: { credential_type: "logging", access: { teams: ["t"] } } },
|
||||
{ credential_name: "org-scoped", credential_info: { credential_type: "logging", access: { orgs: ["o"] } } },
|
||||
{ credential_name: "everyone", credential_info: { credential_type: "logging", access: { global: true } } },
|
||||
{ credential_name: "always-on", credential_info: { credential_type: "logging", auto_enable: true } },
|
||||
{ credential_name: "provider", credential_info: { custom_llm_provider: "openai" } },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -132,27 +106,7 @@ describe("LoggingExportersSelect", () => {
|
|||
render(<LoggingExportersSelect value={[]} onChange={() => {}} />);
|
||||
|
||||
const options = screen.getAllByTestId("option").map((el) => el.textContent);
|
||||
expect(options).toEqual(["mine-team", "mine-org", "everyone", "always-on"]);
|
||||
});
|
||||
|
||||
it("shows every logging destination to a proxy admin regardless of access scope", () => {
|
||||
mockUseAuthorized.mockReturnValue({ userRole: "Admin" });
|
||||
mockUseTeams.mockReturnValue({ data: [] });
|
||||
mockUseCredentials.mockReturnValue({
|
||||
data: {
|
||||
credentials: [
|
||||
{
|
||||
credential_name: "team-b-only",
|
||||
credential_info: { credential_type: "logging", access: { teams: ["team-b"] } },
|
||||
},
|
||||
{ credential_name: "no-access", credential_info: { credential_type: "logging" } },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
render(<LoggingExportersSelect value={[]} onChange={() => {}} />);
|
||||
|
||||
const options = screen.getAllByTestId("option").map((el) => el.textContent);
|
||||
expect(options).toEqual(["team-b-only", "no-access"]);
|
||||
// every logging destination the backend returned, and only those (provider dropped)
|
||||
expect(options).toEqual(["team-scoped", "org-scoped", "everyone", "always-on"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@ import { Select } from "antd";
|
|||
import React from "react";
|
||||
|
||||
import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials";
|
||||
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
|
||||
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { isAdminRole } from "@/utils/roles";
|
||||
|
||||
interface LoggingExportersSelectProps {
|
||||
value?: string[];
|
||||
|
|
@ -14,39 +10,22 @@ interface LoggingExportersSelectProps {
|
|||
|
||||
/**
|
||||
* Multi-select of admin-owned logging destinations (credential_type=logging) that an
|
||||
* identity (key / team / org) exports its traces to. The selected names are stored in
|
||||
* metadata.logging_exporters; the proxy unions them across the identity chain and fans
|
||||
* out.
|
||||
* identity (key / team / org) exports its traces to. The selected names are persisted to
|
||||
* the identity's logging_exporters column; the proxy unions them across the identity
|
||||
* chain and fans out.
|
||||
*
|
||||
* Options are scoped to what the caller can actually assign: a proxy admin sees every
|
||||
* destination; everyone else sees only the ones visible to a team or org they belong to
|
||||
* (plus global / auto_enable destinations). This mirrors the backend assignment gate so
|
||||
* a team admin is not offered another tenant's destination only to have the save
|
||||
* rejected, and it avoids surfacing other tenants' destination names. The backend stays
|
||||
* the authoritative check -- this filter is UX, not a security boundary.
|
||||
* The options are exactly what GET /credentials returns for the caller, which the backend
|
||||
* already scopes: a proxy admin receives every destination, while a team or org admin
|
||||
* receives only the destinations granted to a scope they administer. Visibility is
|
||||
* enforced server-side by the same predicate the assignment gate and the request-time
|
||||
* resolver use, so this component does no role-based filtering of its own; doing so would
|
||||
* risk disagreeing with the backend in either direction.
|
||||
*/
|
||||
const LoggingExportersSelect: React.FC<LoggingExportersSelectProps> = ({ value, onChange }) => {
|
||||
const { data } = useCredentials();
|
||||
const { userRole } = useAuthorized();
|
||||
const { data: teams } = useTeams();
|
||||
const { data: orgs } = useOrganizations();
|
||||
|
||||
const seesEveryDestination = isAdminRole(userRole ?? "");
|
||||
const myTeamIds = new Set((teams ?? []).map((t) => t.team_id));
|
||||
const myOrgIds = new Set((orgs ?? []).map((o) => o.organization_id));
|
||||
|
||||
const assignable = (info: {
|
||||
auto_enable?: boolean;
|
||||
access?: { global?: boolean; teams?: string[]; orgs?: string[] };
|
||||
}) => {
|
||||
if (info.auto_enable === true || info.access?.global === true) return true;
|
||||
if ((info.access?.teams ?? []).some((id) => myTeamIds.has(id))) return true;
|
||||
return (info.access?.orgs ?? []).some((id) => myOrgIds.has(id));
|
||||
};
|
||||
|
||||
const options = (data?.credentials ?? [])
|
||||
.filter((credential) => credential.credential_info?.credential_type === "logging")
|
||||
.filter((credential) => seesEveryDestination || assignable(credential.credential_info))
|
||||
.map((credential) => ({
|
||||
value: credential.credential_name,
|
||||
label: credential.credential_info?.host
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* The admin-owned logging destinations assigned to an identity (key / team / org).
|
||||
*
|
||||
* Assignments live on a typed logging_exporters column, surfaced at the top level of
|
||||
* the API object. A metadata.logging_exporters fallback is kept only so a row written
|
||||
* before the column existed still renders; the column is the source of truth.
|
||||
*/
|
||||
export const loggingExportersOf = (obj: unknown): string[] => {
|
||||
const record = obj as {
|
||||
logging_exporters?: unknown;
|
||||
metadata?: { logging_exporters?: unknown } | null;
|
||||
} | null;
|
||||
if (Array.isArray(record?.logging_exporters)) {
|
||||
return record.logging_exporters as string[];
|
||||
}
|
||||
const fromMetadata = record?.metadata?.logging_exporters;
|
||||
return Array.isArray(fromMetadata) ? (fromMetadata as string[]) : [];
|
||||
};
|
||||
|
|
@ -13,6 +13,7 @@ import MemberTable from "../common_components/MemberTable";
|
|||
import UserSearchModal from "../common_components/user_search_modal";
|
||||
import MCPServerSelector from "../mcp_server_management/MCPServerSelector";
|
||||
import LoggingExportersSelect from "../logging_credentials/LoggingExportersSelect";
|
||||
import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
|
||||
import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials";
|
||||
import { ModelSelect } from "../ModelSelect/ModelSelect";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
|
@ -155,10 +156,9 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
|
|||
max_budget: values.max_budget,
|
||||
budget_duration: values.budget_duration,
|
||||
},
|
||||
metadata: {
|
||||
...(values.metadata ? JSON.parse(values.metadata) : {}),
|
||||
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
|
||||
},
|
||||
metadata: values.metadata ? JSON.parse(values.metadata) : {},
|
||||
// logging_exporters is a top-level typed column on the org, not metadata.
|
||||
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
|
||||
};
|
||||
|
||||
// Handle object_permission updates
|
||||
|
|
@ -336,9 +336,7 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
|
|||
<Text>Logging Exporters</Text>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{(() => {
|
||||
const own = Array.isArray(orgData.metadata?.logging_exporters)
|
||||
? (orgData.metadata.logging_exporters as string[])
|
||||
: [];
|
||||
const own = loggingExportersOf(orgData);
|
||||
const ownSet = new Set(own);
|
||||
const scopedOnly = scopedExportersForOrg.filter((n) => !ownSet.has(n));
|
||||
const all = [
|
||||
|
|
@ -416,7 +414,7 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
|
|||
max_budget: orgData.litellm_budget_table.max_budget,
|
||||
budget_duration: orgData.litellm_budget_table.budget_duration,
|
||||
metadata: orgData.metadata ? JSON.stringify(orgData.metadata, null, 2) : "",
|
||||
logging_exporters: orgData.metadata?.logging_exporters || [],
|
||||
logging_exporters: loggingExportersOf(orgData),
|
||||
vector_stores: orgData.object_permission?.vector_stores || [],
|
||||
mcp_servers_and_groups: {
|
||||
servers: orgData.object_permission?.mcp_servers || [],
|
||||
|
|
@ -553,9 +551,7 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
|
|||
<div>
|
||||
<Text className="font-medium">Logging Exporters</Text>
|
||||
{(() => {
|
||||
const own = Array.isArray(orgData.metadata?.logging_exporters)
|
||||
? (orgData.metadata.logging_exporters as string[])
|
||||
: [];
|
||||
const own = loggingExportersOf(orgData);
|
||||
const ownSet = new Set(own);
|
||||
const scopedOnly = scopedExportersForOrg.filter((n) => !ownSet.has(n));
|
||||
const all = [
|
||||
|
|
|
|||
|
|
@ -159,16 +159,11 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(values.logging_exporters) && values.logging_exporters.length > 0) {
|
||||
let existingMetadata: Record<string, unknown> = {};
|
||||
if (typeof values.metadata === "string" && values.metadata.trim().length > 0) {
|
||||
existingMetadata = JSON.parse(values.metadata);
|
||||
} else if (values.metadata && typeof values.metadata === "object") {
|
||||
existingMetadata = values.metadata;
|
||||
}
|
||||
values.metadata = { ...existingMetadata, logging_exporters: values.logging_exporters };
|
||||
// logging_exporters is a top-level typed field on the org (its own column),
|
||||
// not part of the free-form metadata blob; send it as-is when set.
|
||||
if (!Array.isArray(values.logging_exporters) || values.logging_exporters.length === 0) {
|
||||
delete values.logging_exporters;
|
||||
}
|
||||
delete values.logging_exporters;
|
||||
|
||||
await organizationCreateCall(accessToken, values);
|
||||
NotificationsManager.success("Organization created successfully");
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
|
|||
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
|
||||
import EditLoggingCredentialModal from "./logging_credentials/EditLoggingCredentialModal";
|
||||
import AccessControlFields from "./logging_credentials/AccessControlFields";
|
||||
import { loggingExportersOf } from "./logging_credentials/loggingExportersOf";
|
||||
import {
|
||||
backendLabel,
|
||||
createLoggingCredential,
|
||||
|
|
@ -295,15 +296,14 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
|
|||
for (const teamId of access?.teams ?? []) teams.add(teamAlias(teamId));
|
||||
for (const orgId of access?.orgs ?? []) orgs.add(orgAlias(orgId));
|
||||
for (const team of teamsData ?? []) {
|
||||
const teamMetadata = (team as { metadata?: Record<string, unknown> | null }).metadata;
|
||||
const exporters = teamMetadata?.logging_exporters;
|
||||
if (Array.isArray(exporters) && exporters.includes(destinationName)) {
|
||||
const exporters = loggingExportersOf(team);
|
||||
if (exporters.includes(destinationName)) {
|
||||
teams.add(team.team_alias || team.team_id);
|
||||
}
|
||||
}
|
||||
for (const org of orgsData ?? []) {
|
||||
const exporters = (org.metadata as Record<string, unknown> | null | undefined)?.logging_exporters;
|
||||
if (Array.isArray(exporters) && exporters.includes(destinationName)) {
|
||||
const exporters = loggingExportersOf(org);
|
||||
if (exporters.includes(destinationName)) {
|
||||
orgs.add(org.organization_alias || org.organization_id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import VectorStoreSelector from "../vector_store_management/VectorStoreSelector"
|
|||
import SearchToolSelector from "../SearchTools/SearchToolSelector";
|
||||
import EditLoggingSettings from "./EditLoggingSettings";
|
||||
import LoggingExportersSelect from "../logging_credentials/LoggingExportersSelect";
|
||||
import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
|
||||
import RouterSettingsAccordion, { RouterSettingsAccordionRef } from "../common_components/RouterSettingsAccordion";
|
||||
import MemberModal from "./EditMembership";
|
||||
import MemberPermissions from "./member_permissions";
|
||||
|
|
@ -545,13 +546,14 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
|||
max_budget: values.max_budget,
|
||||
soft_budget: sanitizeNumeric(values.soft_budget),
|
||||
budget_duration: values.budget_duration,
|
||||
// logging_exporters is a top-level typed column on the team, not metadata.
|
||||
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
|
||||
metadata: {
|
||||
...parsedMetadata,
|
||||
...passthroughRoutesMetadata,
|
||||
guardrails: (values.guardrails || []).filter((n: string) => !globalGuardrailNames.has(n)),
|
||||
opted_out_global_guardrails: optedOutGlobalGuardrails,
|
||||
...(values.logging_settings?.length > 0 ? { logging: values.logging_settings } : {}),
|
||||
...(values.logging_exporters !== undefined ? { logging_exporters: values.logging_exporters } : {}),
|
||||
disable_global_guardrails: killSwitchOnAtSave,
|
||||
soft_budget_alerting_emails:
|
||||
typeof values.soft_budget_alerting_emails === "string"
|
||||
|
|
@ -888,9 +890,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
|||
|
||||
<LoggingSettingsView
|
||||
loggingConfigs={info.metadata?.logging || []}
|
||||
loggingExporters={
|
||||
Array.isArray(info.metadata?.logging_exporters) ? info.metadata.logging_exporters : []
|
||||
}
|
||||
loggingExporters={loggingExportersOf(info)}
|
||||
scopedExporters={scopedExportersForTeam}
|
||||
disabledCallbacks={[]}
|
||||
variant="card"
|
||||
|
|
@ -1004,7 +1004,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
|||
)
|
||||
: "",
|
||||
logging_settings: info.metadata?.logging || [],
|
||||
logging_exporters: info.metadata?.logging_exporters || [],
|
||||
logging_exporters: loggingExportersOf(info),
|
||||
secret_manager_settings: info.metadata?.secret_manager_settings
|
||||
? JSON.stringify(info.metadata.secret_manager_settings, null, 2)
|
||||
: "",
|
||||
|
|
@ -1678,9 +1678,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
|||
|
||||
<LoggingSettingsView
|
||||
loggingConfigs={info.metadata?.logging || []}
|
||||
loggingExporters={
|
||||
Array.isArray(info.metadata?.logging_exporters) ? info.metadata.logging_exporters : []
|
||||
}
|
||||
loggingExporters={loggingExportersOf(info)}
|
||||
scopedExporters={scopedExportersForTeam}
|
||||
disabledCallbacks={[]}
|
||||
variant="inline"
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import NumericalInput from "../shared/numerical_input";
|
|||
import { Tag } from "../tag_management/types";
|
||||
import EditLoggingSettings from "../team/EditLoggingSettings";
|
||||
import LoggingExportersSelect from "../logging_credentials/LoggingExportersSelect";
|
||||
import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
|
||||
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
|
||||
|
||||
interface KeyEditViewProps {
|
||||
|
|
@ -191,7 +192,7 @@ export function KeyEditView({
|
|||
accessGroups: keyData.object_permission?.agent_access_groups || [],
|
||||
},
|
||||
logging_settings: extractLoggingSettings(keyData.metadata),
|
||||
logging_exporters: keyData.metadata?.logging_exporters || [],
|
||||
logging_exporters: loggingExportersOf(keyData),
|
||||
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
|
||||
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
|
||||
: [],
|
||||
|
|
@ -221,7 +222,7 @@ export function KeyEditView({
|
|||
},
|
||||
mcp_tool_permissions: keyData.object_permission?.mcp_tool_permissions || {},
|
||||
logging_settings: extractLoggingSettings(keyData.metadata),
|
||||
logging_exporters: keyData.metadata?.logging_exporters || [],
|
||||
logging_exporters: loggingExportersOf(keyData),
|
||||
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
|
||||
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
|
||||
: [],
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import DeleteResourceModal from "../common_components/DeleteResourceModal";
|
|||
import { extractLoggingSettings, formatMetadataForDisplay, stripTagsFromMetadata } from "../key_info_utils";
|
||||
import { KeyResponse } from "../key_team_helpers/key_list";
|
||||
import LoggingSettingsView from "../logging_settings_view";
|
||||
import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
|
||||
import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials";
|
||||
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
|
||||
import NotificationManager from "../molecules/notifications_manager";
|
||||
|
|
@ -278,7 +279,6 @@ export default function KeyInfoView({
|
|||
...(Array.isArray(formValues.logging_settings) && formValues.logging_settings.length > 0
|
||||
? { logging: formValues.logging_settings }
|
||||
: {}),
|
||||
...(formValues.logging_exporters !== undefined ? { logging_exporters: formValues.logging_exporters } : {}),
|
||||
...(formValues.disabled_callbacks?.length > 0
|
||||
? {
|
||||
litellm_disabled_callbacks: mapDisplayToInternalNames(formValues.disabled_callbacks),
|
||||
|
|
@ -300,7 +300,6 @@ export default function KeyInfoView({
|
|||
...(Array.isArray(formValues.logging_settings) && formValues.logging_settings.length > 0
|
||||
? { logging: formValues.logging_settings }
|
||||
: {}),
|
||||
...(formValues.logging_exporters !== undefined ? { logging_exporters: formValues.logging_exporters } : {}),
|
||||
...(formValues.disabled_callbacks?.length > 0
|
||||
? {
|
||||
litellm_disabled_callbacks: mapDisplayToInternalNames(formValues.disabled_callbacks),
|
||||
|
|
@ -643,11 +642,7 @@ export default function KeyInfoView({
|
|||
|
||||
<LoggingSettingsView
|
||||
loggingConfigs={extractLoggingSettings(currentKeyData.metadata)}
|
||||
loggingExporters={
|
||||
Array.isArray(currentKeyData.metadata?.logging_exporters)
|
||||
? currentKeyData.metadata.logging_exporters
|
||||
: []
|
||||
}
|
||||
loggingExporters={loggingExportersOf(currentKeyData)}
|
||||
scopedExporters={scopedExportersForKey}
|
||||
disabledCallbacks={
|
||||
Array.isArray(currentKeyData.metadata?.litellm_disabled_callbacks)
|
||||
|
|
@ -897,11 +892,7 @@ export default function KeyInfoView({
|
|||
|
||||
<LoggingSettingsView
|
||||
loggingConfigs={extractLoggingSettings(currentKeyData.metadata)}
|
||||
loggingExporters={
|
||||
Array.isArray(currentKeyData.metadata?.logging_exporters)
|
||||
? currentKeyData.metadata.logging_exporters
|
||||
: []
|
||||
}
|
||||
loggingExporters={loggingExportersOf(currentKeyData)}
|
||||
scopedExporters={scopedExportersForKey}
|
||||
disabledCallbacks={
|
||||
Array.isArray(currentKeyData.metadata?.litellm_disabled_callbacks)
|
||||
|
|
|
|||
65
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
65
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -2424,12 +2424,13 @@ export interface paths {
|
|||
* Get Credentials
|
||||
* @description [BETA] endpoint. This might change unexpectedly.
|
||||
*
|
||||
* Proxy admins see every credential (values masked). Team-admins and
|
||||
* org-admins see only logging-typed destinations so they can self-assign
|
||||
* them; provider credentials stay invisible to non-PROXY_ADMINs. Plain
|
||||
* internal users with no team-admin or org-admin status get 403 — they
|
||||
* have no use for the list and shouldn't see destination names, hosts,
|
||||
* or scope metadata (Veria F2).
|
||||
* Proxy admins see every credential (values masked). A non-proxy-admin sees
|
||||
* only the logging destinations actually visible to a scope they administer:
|
||||
* the same ``is_destination_visible`` predicate the assignment validator and
|
||||
* the request-time resolver use, so the list can never show a destination a
|
||||
* caller could neither assign nor route to. Provider credentials, and logging
|
||||
* destinations scoped to other tenants, stay invisible. A caller who
|
||||
* administers nothing gets 403 (Veria F2).
|
||||
*/
|
||||
get: operations["get_credentials_credentials_get"];
|
||||
put?: never;
|
||||
|
|
@ -23530,6 +23531,8 @@ export interface components {
|
|||
* @default default
|
||||
*/
|
||||
key_type: components["schemas"]["LiteLLMKeyType"] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -23675,6 +23678,8 @@ export interface components {
|
|||
key_name?: string | null;
|
||||
/** Litellm Budget Table */
|
||||
litellm_budget_table?: unknown | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -24523,6 +24528,8 @@ export interface components {
|
|||
/** Litellm Changed By */
|
||||
litellm_changed_by?: string | null;
|
||||
litellm_model_table?: components["schemas"]["LiteLLM_ModelTable"] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -24670,6 +24677,8 @@ export interface components {
|
|||
} | null;
|
||||
/** Litellm Changed By */
|
||||
litellm_changed_by?: string | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -25140,6 +25149,8 @@ export interface components {
|
|||
/** Created By */
|
||||
created_by: string;
|
||||
litellm_budget_table?: components["schemas"]["LiteLLM_BudgetTable"] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/**
|
||||
* Members
|
||||
* @default []
|
||||
|
|
@ -25683,6 +25694,8 @@ export interface components {
|
|||
/** Default Team Member Models */
|
||||
default_team_member_models?: string[] | null;
|
||||
litellm_model_table?: components["schemas"]["LiteLLM_ModelTable"] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -26039,6 +26052,8 @@ export interface components {
|
|||
litellm_budget_table?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -27455,6 +27470,8 @@ export interface components {
|
|||
budget_duration?: string | null;
|
||||
/** Budget Id */
|
||||
budget_id?: string | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -27504,6 +27521,8 @@ export interface components {
|
|||
/** Created By */
|
||||
created_by: string;
|
||||
litellm_budget_table?: components["schemas"]["LiteLLM_BudgetTable"] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Metadata */
|
||||
metadata?: {
|
||||
[key: string]: unknown;
|
||||
|
|
@ -27706,6 +27725,8 @@ export interface components {
|
|||
} | null;
|
||||
/** Guardrails */
|
||||
guardrails?: string[] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Mcp Rpm Limit */
|
||||
|
|
@ -27827,6 +27848,8 @@ export interface components {
|
|||
guardrails?: string[] | null;
|
||||
/** Key Alias */
|
||||
key_alias?: string | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -27979,6 +28002,8 @@ export interface components {
|
|||
key_name?: string | null;
|
||||
/** Litellm Budget Table */
|
||||
litellm_budget_table?: unknown | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -29594,6 +29619,8 @@ export interface components {
|
|||
* @default default
|
||||
*/
|
||||
key_type: components["schemas"]["LiteLLMKeyType"] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -30636,6 +30663,8 @@ export interface components {
|
|||
/** Default Team Member Models */
|
||||
default_team_member_models?: string[] | null;
|
||||
litellm_model_table?: components["schemas"]["LiteLLM_ModelTable"] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -30751,6 +30780,8 @@ export interface components {
|
|||
*/
|
||||
keys_count: number;
|
||||
litellm_model_table?: components["schemas"]["LiteLLM_ModelTable"] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -31559,6 +31590,8 @@ export interface components {
|
|||
key: string;
|
||||
/** Key Alias */
|
||||
key_alias?: string | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -31908,6 +31941,8 @@ export interface components {
|
|||
} | null;
|
||||
/** Guardrails */
|
||||
guardrails?: string[] | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Mcp Rpm Limit */
|
||||
|
|
@ -32014,6 +32049,8 @@ export interface components {
|
|||
guardrails?: string[] | null;
|
||||
/** Key Alias */
|
||||
key_alias?: string | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -32112,6 +32149,8 @@ export interface components {
|
|||
guardrails?: string[] | null;
|
||||
/** Key Alias */
|
||||
key_alias?: string | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -32385,6 +32424,8 @@ export interface components {
|
|||
litellm_budget_table?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** Logging Exporters */
|
||||
logging_exporters?: string[] | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
|
|
@ -43825,13 +43866,13 @@ export interface operations {
|
|||
/**
|
||||
* @description Unified rate-limit error.
|
||||
*
|
||||
* Every rate-limit condition surfaced by litellm — whether it originated from
|
||||
* an upstream LLM provider, a vendor batch endpoint, or one of litellm's own
|
||||
* proxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,
|
||||
* max-iterations, etc.) — is raised as an instance of this class.
|
||||
* Every rate-limit condition surfaced by litellm — whether it originated from
|
||||
* an upstream LLM provider, a vendor batch endpoint, or one of litellm's own
|
||||
* proxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,
|
||||
* max-iterations, etc.) — is raised as an instance of this class.
|
||||
*
|
||||
* The :attr:`category` attribute lets callers distinguish the source. See
|
||||
* :class:`RateLimitErrorCategory` for the available values.
|
||||
* The :attr:`category` attribute lets callers distinguish the source. See
|
||||
* :class:`RateLimitErrorCategory` for the available values.
|
||||
*/
|
||||
429: {
|
||||
headers: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue