@acarmisc/backstage-plugin-litellm 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ManageTeamDialog.d.ts +6 -0
- package/dist/components/TeamUsage.d.ts +5 -0
- package/dist/index.cjs.js +193 -98
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +206 -111
- package/dist/index.esm.js.map +4 -4
- package/dist/teamMemberManager.d.ts +9 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
|
@@ -26,6 +26,12 @@ interface ManageTeamDialogProps {
|
|
|
26
26
|
mcpServers?: McpServerInfo[];
|
|
27
27
|
/** Called to replace the team's attached MCP servers; parent refreshes `team`. */
|
|
28
28
|
onSaveMcpServers?: (mcpServerIds: string[]) => Promise<void>;
|
|
29
|
+
/** When true, only members section is shown (not budget/models/KB/MCP); title changes; Save becomes Done. */
|
|
30
|
+
membersOnly?: boolean;
|
|
31
|
+
/** Current user's LiteLLM ID; used in membersOnly mode to prevent self-removal and role-restricted removal. */
|
|
32
|
+
currentUserId?: string;
|
|
33
|
+
/** Roles that can manage members in membersOnly mode; used to disable removal of role holders. */
|
|
34
|
+
memberManagerRoles?: string[];
|
|
29
35
|
}
|
|
30
36
|
export declare const ManageTeamDialog: FC<ManageTeamDialogProps>;
|
|
31
37
|
export {};
|
|
@@ -12,6 +12,11 @@ interface TeamUsageProps {
|
|
|
12
12
|
onCreateTeam?: () => void;
|
|
13
13
|
/** Called when a team card is expanded to trigger data loading. */
|
|
14
14
|
onTeamExpand?: (team: TeamInfo) => void;
|
|
15
|
+
/** Per-team: whether the caller may manage this team's members only. */
|
|
16
|
+
canManageMembers?: (team: TeamInfo) => boolean;
|
|
17
|
+
onManageMembers?: (team: TeamInfo) => void;
|
|
18
|
+
/** Teams are synced from an identity provider: show a badge, no write controls. */
|
|
19
|
+
readOnly?: boolean;
|
|
15
20
|
}
|
|
16
21
|
export declare const TeamUsage: FC<TeamUsageProps>;
|
|
17
22
|
export {};
|
package/dist/index.cjs.js
CHANGED
|
@@ -2575,9 +2575,22 @@ var init_KeyFormDialog = __esm({
|
|
|
2575
2575
|
setCloseWithoutCopyConfirm(false);
|
|
2576
2576
|
setTouched({});
|
|
2577
2577
|
setSubmitAttempted(false);
|
|
2578
|
-
onGetConfig().then(setConfig).catch(() => setConfig(null));
|
|
2579
2578
|
}
|
|
2580
|
-
}, [open, isCreate, keyToEdit, username, keyGenerationSettings?.allowUnlimitedBudget
|
|
2579
|
+
}, [open, isCreate, keyToEdit, username, keyGenerationSettings?.allowUnlimitedBudget]);
|
|
2580
|
+
const onGetConfigRef = (0, import_react6.useRef)(onGetConfig);
|
|
2581
|
+
onGetConfigRef.current = onGetConfig;
|
|
2582
|
+
(0, import_react6.useEffect)(() => {
|
|
2583
|
+
if (!open) return void 0;
|
|
2584
|
+
let cancelled = false;
|
|
2585
|
+
onGetConfigRef.current().then((c) => {
|
|
2586
|
+
if (!cancelled) setConfig(c);
|
|
2587
|
+
}).catch(() => {
|
|
2588
|
+
if (!cancelled) setConfig(null);
|
|
2589
|
+
});
|
|
2590
|
+
return () => {
|
|
2591
|
+
cancelled = true;
|
|
2592
|
+
};
|
|
2593
|
+
}, [open]);
|
|
2581
2594
|
const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
|
|
2582
2595
|
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
2583
2596
|
const formData = isCreate ? generateForm : editFormState;
|
|
@@ -3114,7 +3127,7 @@ var init_KeyFormDialog = __esm({
|
|
|
3114
3127
|
});
|
|
3115
3128
|
|
|
3116
3129
|
// src/components/ManageTeamDialog.tsx
|
|
3117
|
-
var import_react7, import_Dialog3, import_DialogTitle3, import_DialogContent3, import_DialogActions3, import_TextField3, import_Button4, import_Alert2, import_Checkbox2, import_FormControlLabel2, import_Autocomplete2, import_CircularProgress3, import_MenuItem2, import_Box10, import_Divider2, import_Typography8, import_IconButton4, import_Paper4, import_Table2, import_TableBody2, import_TableCell2, import_TableContainer2, import_TableHead2, import_TableRow2, import_icons_material5, import_core_plugin_api4, import_plugin_catalog_react, import_catalog_model, import_jsx_runtime11, BUDGET_DURATION_OPTIONS, DEFAULT_BUDGET_DURATION, FALLBACK_BUDGET_CEILING, ManageTeamDialog;
|
|
3130
|
+
var import_react7, import_Dialog3, import_DialogTitle3, import_DialogContent3, import_DialogActions3, import_TextField3, import_Button4, import_Alert2, import_Checkbox2, import_FormControlLabel2, import_Autocomplete2, import_CircularProgress3, import_MenuItem2, import_Box10, import_Divider2, import_Typography8, import_IconButton4, import_Tooltip4, import_Paper4, import_Table2, import_TableBody2, import_TableCell2, import_TableContainer2, import_TableHead2, import_TableRow2, import_icons_material5, import_core_plugin_api4, import_plugin_catalog_react, import_catalog_model, import_jsx_runtime11, BUDGET_DURATION_OPTIONS, DEFAULT_BUDGET_DURATION, FALLBACK_BUDGET_CEILING, ManageTeamDialog;
|
|
3118
3131
|
var init_ManageTeamDialog = __esm({
|
|
3119
3132
|
"src/components/ManageTeamDialog.tsx"() {
|
|
3120
3133
|
"use strict";
|
|
@@ -3135,6 +3148,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3135
3148
|
import_Divider2 = __toESM(require("@mui/material/Divider"));
|
|
3136
3149
|
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
3137
3150
|
import_IconButton4 = __toESM(require("@mui/material/IconButton"));
|
|
3151
|
+
import_Tooltip4 = __toESM(require("@mui/material/Tooltip"));
|
|
3138
3152
|
import_Paper4 = __toESM(require("@mui/material/Paper"));
|
|
3139
3153
|
import_Table2 = __toESM(require("@mui/material/Table"));
|
|
3140
3154
|
import_TableBody2 = __toESM(require("@mui/material/TableBody"));
|
|
@@ -3173,7 +3187,10 @@ var init_ManageTeamDialog = __esm({
|
|
|
3173
3187
|
onSaveKnowledgeBases,
|
|
3174
3188
|
canManageMcpServers,
|
|
3175
3189
|
mcpServers,
|
|
3176
|
-
onSaveMcpServers
|
|
3190
|
+
onSaveMcpServers,
|
|
3191
|
+
membersOnly,
|
|
3192
|
+
currentUserId,
|
|
3193
|
+
memberManagerRoles
|
|
3177
3194
|
}) => {
|
|
3178
3195
|
const [alias, setAlias] = (0, import_react7.useState)("");
|
|
3179
3196
|
const [models, setModels] = (0, import_react7.useState)([]);
|
|
@@ -3198,7 +3215,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3198
3215
|
const hideBudgetForManagers = config?.display?.hideTeamBudgetForManagers ?? false;
|
|
3199
3216
|
const budgetWriteOnly = hideBudgetForManagers && mode === "edit";
|
|
3200
3217
|
const members = (0, import_react7.useMemo)(() => team?.members_with_roles ?? [], [team]);
|
|
3201
|
-
const showMembers = mode === "edit" && !!canManageMembers;
|
|
3218
|
+
const showMembers = mode === "edit" && !!canManageMembers || membersOnly;
|
|
3202
3219
|
const catalogApi = (0, import_core_plugin_api4.useApi)(import_plugin_catalog_react.catalogApiRef);
|
|
3203
3220
|
const [searchUsers, setSearchUsers] = (0, import_react7.useState)([]);
|
|
3204
3221
|
const [usersLoading, setUsersLoading] = (0, import_react7.useState)(false);
|
|
@@ -3347,7 +3364,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3347
3364
|
const handleAddMember = async () => {
|
|
3348
3365
|
if (!onAddMember || !memberRef.trim()) return;
|
|
3349
3366
|
setMemberError(null);
|
|
3350
|
-
const budget = memberBudget ? parseFloat(memberBudget) : void 0;
|
|
3367
|
+
const budget = !membersOnly && memberBudget ? parseFloat(memberBudget) : void 0;
|
|
3351
3368
|
if (budget !== void 0 && (isNaN(budget) || budget <= 0)) {
|
|
3352
3369
|
setMemberError("Max budget in team must be a positive number");
|
|
3353
3370
|
return;
|
|
@@ -3408,72 +3425,95 @@ var init_ManageTeamDialog = __esm({
|
|
|
3408
3425
|
const showMcpServers = mode === "edit" && !!canManageMcpServers;
|
|
3409
3426
|
const mcpOptions = (mcpServers ?? []).map((s) => s.id);
|
|
3410
3427
|
const mcpLabel = (id) => (mcpServers ?? []).find((s) => s.id === id)?.name ?? id;
|
|
3428
|
+
const isRemovalDisabled = (memberUserId, memberRole) => {
|
|
3429
|
+
if (!membersOnly) return { disabled: false };
|
|
3430
|
+
if (memberUserId === currentUserId) {
|
|
3431
|
+
return { disabled: true, reason: "You can't remove yourself" };
|
|
3432
|
+
}
|
|
3433
|
+
if (memberManagerRoles?.includes(memberRole)) {
|
|
3434
|
+
return { disabled: true, reason: `Only platform admins can remove a team ${memberRole}` };
|
|
3435
|
+
}
|
|
3436
|
+
return { disabled: false };
|
|
3437
|
+
};
|
|
3438
|
+
const getDialogTitle = () => {
|
|
3439
|
+
if (membersOnly) {
|
|
3440
|
+
return `Manage members \u2014 ${team?.team_alias || "Untitled team"}`;
|
|
3441
|
+
}
|
|
3442
|
+
return mode === "create" ? "Create Team" : "Edit Team";
|
|
3443
|
+
};
|
|
3411
3444
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Dialog3.default, { open, onClose, maxWidth: "sm", fullWidth: true, children: [
|
|
3412
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_DialogTitle3.default, { children:
|
|
3445
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_DialogTitle3.default, { children: getDialogTitle() }),
|
|
3413
3446
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_DialogContent3.default, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
3414
3447
|
error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", children: error }),
|
|
3415
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3448
|
+
membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Alert2.default, { severity: "info", children: [
|
|
3449
|
+
"As a team ",
|
|
3450
|
+
team?.members_with_roles?.find((m) => m.user_id === currentUserId)?.role || "member",
|
|
3451
|
+
" you can add or remove members. Budgets and models are managed by your platform admins."
|
|
3452
|
+
] }),
|
|
3453
|
+
!membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
3454
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3455
|
+
import_TextField3.default,
|
|
3456
|
+
{
|
|
3457
|
+
label: "Team Alias",
|
|
3458
|
+
value: alias,
|
|
3459
|
+
onChange: (e) => setAlias(e.target.value),
|
|
3460
|
+
disabled: submitting,
|
|
3461
|
+
fullWidth: true
|
|
3462
|
+
}
|
|
3463
|
+
),
|
|
3464
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3465
|
+
import_Autocomplete2.default,
|
|
3466
|
+
{
|
|
3467
|
+
multiple: true,
|
|
3468
|
+
options: modelNames,
|
|
3469
|
+
value: models,
|
|
3470
|
+
onChange: (_, newValue) => setModels(newValue),
|
|
3471
|
+
disabled: submitting,
|
|
3472
|
+
renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3473
|
+
import_TextField3.default,
|
|
3474
|
+
{
|
|
3475
|
+
...params,
|
|
3476
|
+
label: "Models",
|
|
3477
|
+
required: true,
|
|
3478
|
+
helperText: "At least one model is required here \u2014 this delegated flow only grants access to models your admin has allow-listed for team creation, not every proxy model. (Teams with no model restriction shown elsewhere were configured directly in LiteLLM.)"
|
|
3479
|
+
}
|
|
3480
|
+
)
|
|
3481
|
+
}
|
|
3482
|
+
),
|
|
3483
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3484
|
+
import_TextField3.default,
|
|
3485
|
+
{
|
|
3486
|
+
label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
|
|
3487
|
+
type: "number",
|
|
3488
|
+
value: maxBudget,
|
|
3489
|
+
onChange: (e) => setMaxBudget(e.target.value),
|
|
3490
|
+
disabled: submitting || unlimited,
|
|
3491
|
+
inputProps: { min: 0, max: maxBudgetCeiling },
|
|
3492
|
+
helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
|
|
3493
|
+
fullWidth: true
|
|
3494
|
+
}
|
|
3495
|
+
),
|
|
3496
|
+
allowUnlimitedBudget && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3497
|
+
import_FormControlLabel2.default,
|
|
3498
|
+
{
|
|
3499
|
+
control: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
|
|
3500
|
+
label: "Unlimited Budget"
|
|
3501
|
+
}
|
|
3502
|
+
),
|
|
3503
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3504
|
+
import_TextField3.default,
|
|
3505
|
+
{
|
|
3506
|
+
select: true,
|
|
3507
|
+
label: "Budget Duration",
|
|
3508
|
+
value: budgetDuration,
|
|
3509
|
+
onChange: (e) => setBudgetDuration(e.target.value),
|
|
3510
|
+
disabled: submitting || unlimited,
|
|
3511
|
+
helperText: "Spend-reset period for the team budget",
|
|
3512
|
+
fullWidth: true,
|
|
3513
|
+
children: durationOptions.map((o) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_MenuItem2.default, { value: o.value, children: o.label }, o.value))
|
|
3514
|
+
}
|
|
3515
|
+
)
|
|
3516
|
+
] }),
|
|
3477
3517
|
showMembers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3478
3518
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
|
|
3479
3519
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "Members" }),
|
|
@@ -3505,17 +3545,20 @@ var init_ManageTeamDialog = __esm({
|
|
|
3505
3545
|
}
|
|
3506
3546
|
) }),
|
|
3507
3547
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false }) }),
|
|
3508
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children:
|
|
3509
|
-
|
|
3510
|
-
{
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3548
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: (() => {
|
|
3549
|
+
const { disabled, reason } = isRemovalDisabled(m.user_id, m.role);
|
|
3550
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Tooltip4.default, { title: reason ?? "", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3551
|
+
import_IconButton4.default,
|
|
3552
|
+
{
|
|
3553
|
+
edge: "end",
|
|
3554
|
+
size: "small",
|
|
3555
|
+
"aria-label": `remove ${m.user_id}`,
|
|
3556
|
+
disabled: memberBusy || disabled,
|
|
3557
|
+
onClick: () => handleRemoveMember(m.user_id),
|
|
3558
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_icons_material5.Delete, { fontSize: "small" })
|
|
3559
|
+
}
|
|
3560
|
+
) }) });
|
|
3561
|
+
})() })
|
|
3519
3562
|
] }, m.user_id)) })
|
|
3520
3563
|
] }) }),
|
|
3521
3564
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" }, children: [
|
|
@@ -3563,7 +3606,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3563
3606
|
)
|
|
3564
3607
|
}
|
|
3565
3608
|
),
|
|
3566
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3609
|
+
!membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3567
3610
|
import_TextField3.default,
|
|
3568
3611
|
{
|
|
3569
3612
|
label: "Max budget in team ($)",
|
|
@@ -3587,7 +3630,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3587
3630
|
)
|
|
3588
3631
|
] })
|
|
3589
3632
|
] }),
|
|
3590
|
-
showKnowledgeBases && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3633
|
+
!membersOnly && showKnowledgeBases && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3591
3634
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
|
|
3592
3635
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "Knowledge Bases" }),
|
|
3593
3636
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: "Attaching a knowledge base exposes its documents to every key in this team." }),
|
|
@@ -3614,7 +3657,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3614
3657
|
}
|
|
3615
3658
|
) })
|
|
3616
3659
|
] }),
|
|
3617
|
-
showMcpServers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3660
|
+
!membersOnly && showMcpServers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3618
3661
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
|
|
3619
3662
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "MCP Servers" }),
|
|
3620
3663
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: "Attaching an MCP server lets every key in this team invoke that server's tools through the model \u2014 including any side effects those tools have." }),
|
|
@@ -3643,8 +3686,8 @@ var init_ManageTeamDialog = __esm({
|
|
|
3643
3686
|
] })
|
|
3644
3687
|
] }),
|
|
3645
3688
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_DialogActions3.default, { children: [
|
|
3646
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: onClose, disabled: submitting, children: "Cancel" }),
|
|
3647
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained", children: submitting ? "Saving..." : "Save" })
|
|
3689
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: onClose, disabled: submitting, children: membersOnly ? "Done" : "Cancel" }),
|
|
3690
|
+
!membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained", children: submitting ? "Saving..." : "Save" })
|
|
3648
3691
|
] })
|
|
3649
3692
|
] });
|
|
3650
3693
|
};
|
|
@@ -4880,7 +4923,7 @@ function budgetStatValue(hidden, budget) {
|
|
|
4880
4923
|
if (budget > 0) return fmtUsd2(budget);
|
|
4881
4924
|
return "Unlimited";
|
|
4882
4925
|
}
|
|
4883
|
-
var import_react10, import_Paper8, import_Box14, import_Typography12, import_Skeleton5, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse2, import_IconButton6, import_Button6, import_CircularProgress4, import_styles10, import_icons_material7, import_recharts2, import_jsx_runtime15, fmtUsd2, TeamCard, TeamUsage;
|
|
4926
|
+
var import_react10, import_Paper8, import_Box14, import_Typography12, import_Skeleton5, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse2, import_IconButton6, import_Button6, import_CircularProgress4, import_styles10, import_Tooltip5, import_icons_material7, import_recharts2, import_jsx_runtime15, fmtUsd2, TeamCard, TeamUsage;
|
|
4884
4927
|
var init_TeamUsage = __esm({
|
|
4885
4928
|
"src/components/TeamUsage.tsx"() {
|
|
4886
4929
|
"use strict";
|
|
@@ -4901,12 +4944,13 @@ var init_TeamUsage = __esm({
|
|
|
4901
4944
|
import_Button6 = __toESM(require("@mui/material/Button"));
|
|
4902
4945
|
import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
|
|
4903
4946
|
import_styles10 = require("@mui/material/styles");
|
|
4947
|
+
import_Tooltip5 = __toESM(require("@mui/material/Tooltip"));
|
|
4904
4948
|
import_icons_material7 = require("@mui/icons-material");
|
|
4905
4949
|
import_recharts2 = require("recharts");
|
|
4906
4950
|
init_ui();
|
|
4907
4951
|
import_jsx_runtime15 = require("react/jsx-runtime");
|
|
4908
4952
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
4909
|
-
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam, onExpand }) => {
|
|
4953
|
+
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand }) => {
|
|
4910
4954
|
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
4911
4955
|
const chart = useChartTheme();
|
|
4912
4956
|
const handleExpandChange = (newExpanded) => {
|
|
@@ -5016,6 +5060,17 @@ var init_TeamUsage = __esm({
|
|
|
5016
5060
|
children: "Edit"
|
|
5017
5061
|
}
|
|
5018
5062
|
),
|
|
5063
|
+
canManageMembers && onManageMembers && !canManage && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Tooltip5.default, { title: "Add or remove members of this team", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
5064
|
+
import_Button6.default,
|
|
5065
|
+
{
|
|
5066
|
+
size: "small",
|
|
5067
|
+
variant: "text",
|
|
5068
|
+
startIcon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_icons_material7.GroupAddOutlined, {}),
|
|
5069
|
+
onClick: () => onManageMembers(team),
|
|
5070
|
+
sx: { textTransform: "none" },
|
|
5071
|
+
children: "Manage members"
|
|
5072
|
+
}
|
|
5073
|
+
) }),
|
|
5019
5074
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
5020
5075
|
import_IconButton6.default,
|
|
5021
5076
|
{
|
|
@@ -5146,9 +5201,23 @@ var init_TeamUsage = __esm({
|
|
|
5146
5201
|
onEditTeam,
|
|
5147
5202
|
canCreate,
|
|
5148
5203
|
onCreateTeam,
|
|
5149
|
-
onTeamExpand
|
|
5204
|
+
onTeamExpand,
|
|
5205
|
+
canManageMembers,
|
|
5206
|
+
onManageMembers,
|
|
5207
|
+
readOnly
|
|
5150
5208
|
}) => {
|
|
5151
|
-
const
|
|
5209
|
+
const readOnlyBadge = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Tooltip5.default, { title: "Teams and memberships are managed in your identity provider. Changes made here would be overwritten, so editing is disabled.", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
5210
|
+
import_Box14.default,
|
|
5211
|
+
{
|
|
5212
|
+
component: "span",
|
|
5213
|
+
sx: { display: "inline-flex", alignItems: "center", gap: 0.5, color: "text.secondary", fontSize: 12.5 },
|
|
5214
|
+
children: [
|
|
5215
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_icons_material7.LockOutlined, { sx: { fontSize: 15 } }),
|
|
5216
|
+
"Synced from identity provider"
|
|
5217
|
+
]
|
|
5218
|
+
}
|
|
5219
|
+
) });
|
|
5220
|
+
const createButton = canCreate && onCreateTeam ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
5152
5221
|
import_Button6.default,
|
|
5153
5222
|
{
|
|
5154
5223
|
variant: "contained",
|
|
@@ -5159,6 +5228,7 @@ var init_TeamUsage = __esm({
|
|
|
5159
5228
|
children: "Create Team"
|
|
5160
5229
|
}
|
|
5161
5230
|
) : void 0;
|
|
5231
|
+
const createAction = readOnly ? readOnlyBadge : createButton;
|
|
5162
5232
|
if (loading) {
|
|
5163
5233
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(SectionCard, { title: "Teams", actions: createAction, children: [
|
|
5164
5234
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Skeleton5.default, { variant: "rounded", height: 120, sx: { mb: 2 } }),
|
|
@@ -5188,6 +5258,8 @@ var init_TeamUsage = __esm({
|
|
|
5188
5258
|
usageLoading: getTeamUsageLoading(team.team_id),
|
|
5189
5259
|
canManage,
|
|
5190
5260
|
onEditTeam,
|
|
5261
|
+
canManageMembers: canManageMembers?.(team),
|
|
5262
|
+
onManageMembers,
|
|
5191
5263
|
onExpand: onTeamExpand
|
|
5192
5264
|
},
|
|
5193
5265
|
team.team_id
|
|
@@ -5216,7 +5288,7 @@ function fmtTokens(n) {
|
|
|
5216
5288
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
5217
5289
|
return String(n);
|
|
5218
5290
|
}
|
|
5219
|
-
var import_react11, import_Box15, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_TableSortLabel2, import_Typography13, import_MenuItem4, import_Select, import_FormControl, import_InputLabel, import_TextField5, import_InputAdornment3, import_icons_material8, import_ContentCopy,
|
|
5291
|
+
var import_react11, import_Box15, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_TableSortLabel2, import_Typography13, import_MenuItem4, import_Select, import_FormControl, import_InputLabel, import_TextField5, import_InputAdornment3, import_icons_material8, import_ContentCopy, import_Tooltip6, import_Snackbar, import_Alert4, import_styles11, import_jsx_runtime16, ALL_PROXY_MODELS, ModelsTable;
|
|
5220
5292
|
var init_ModelsTable = __esm({
|
|
5221
5293
|
"src/components/ModelsTable.tsx"() {
|
|
5222
5294
|
"use strict";
|
|
@@ -5238,7 +5310,7 @@ var init_ModelsTable = __esm({
|
|
|
5238
5310
|
import_InputAdornment3 = __toESM(require("@mui/material/InputAdornment"));
|
|
5239
5311
|
import_icons_material8 = require("@mui/icons-material");
|
|
5240
5312
|
import_ContentCopy = __toESM(require("@mui/icons-material/ContentCopy"));
|
|
5241
|
-
|
|
5313
|
+
import_Tooltip6 = __toESM(require("@mui/material/Tooltip"));
|
|
5242
5314
|
import_Snackbar = __toESM(require("@mui/material/Snackbar"));
|
|
5243
5315
|
import_Alert4 = __toESM(require("@mui/material/Alert"));
|
|
5244
5316
|
init_ui();
|
|
@@ -5401,7 +5473,7 @@ var init_ModelsTable = __esm({
|
|
|
5401
5473
|
] }) }),
|
|
5402
5474
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_TableBody5.default, { children: filteredModels.map((m) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_TableRow5.default, { hover: true, children: [
|
|
5403
5475
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_TableCell5.default, { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_Box15.default, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children: [
|
|
5404
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5476
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_Tooltip6.default, { title: m.model_name, placement: "top", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5405
5477
|
"span",
|
|
5406
5478
|
{
|
|
5407
5479
|
role: "button",
|
|
@@ -5413,7 +5485,7 @@ var init_ModelsTable = __esm({
|
|
|
5413
5485
|
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
5414
5486
|
}
|
|
5415
5487
|
) }),
|
|
5416
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5488
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_Tooltip6.default, { title: "Copy model ID", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5417
5489
|
import_Box15.default,
|
|
5418
5490
|
{
|
|
5419
5491
|
component: "span",
|
|
@@ -5823,6 +5895,19 @@ var init_feedback = __esm({
|
|
|
5823
5895
|
}
|
|
5824
5896
|
});
|
|
5825
5897
|
|
|
5898
|
+
// src/teamMemberManager.ts
|
|
5899
|
+
function isTeamMemberManager(team, userId, memberManagerRoles, readOnly) {
|
|
5900
|
+
if (!team || !userId || !memberManagerRoles || memberManagerRoles.length === 0 || readOnly) {
|
|
5901
|
+
return false;
|
|
5902
|
+
}
|
|
5903
|
+
return team.members_with_roles?.some((m) => m.user_id === userId && memberManagerRoles.includes(m.role)) ?? false;
|
|
5904
|
+
}
|
|
5905
|
+
var init_teamMemberManager = __esm({
|
|
5906
|
+
"src/teamMemberManager.ts"() {
|
|
5907
|
+
"use strict";
|
|
5908
|
+
}
|
|
5909
|
+
});
|
|
5910
|
+
|
|
5826
5911
|
// src/unprovisionedCopy.ts
|
|
5827
5912
|
function getUnprovisionedTitle() {
|
|
5828
5913
|
return "Your LiteLLM account isn't set up yet";
|
|
@@ -5899,6 +5984,7 @@ var init_LiteLLMPage = __esm({
|
|
|
5899
5984
|
init_dates();
|
|
5900
5985
|
init_permissions();
|
|
5901
5986
|
init_feedback();
|
|
5987
|
+
init_teamMemberManager();
|
|
5902
5988
|
init_unprovisionedCopy();
|
|
5903
5989
|
import_jsx_runtime18 = require("react/jsx-runtime");
|
|
5904
5990
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
@@ -6073,6 +6159,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6073
6159
|
]);
|
|
6074
6160
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
6075
6161
|
}, [allModels, userInfo, teams]);
|
|
6162
|
+
const getConfig = (0, import_react13.useCallback)(() => api.getConfig(), [api]);
|
|
6076
6163
|
const handleGenerateKey = (0, import_react13.useCallback)(
|
|
6077
6164
|
async (request) => {
|
|
6078
6165
|
try {
|
|
@@ -6344,6 +6431,8 @@ var init_LiteLLMPage = __esm({
|
|
|
6344
6431
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
6345
6432
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
6346
6433
|
const visibleTeams = Array.from(teamsById.values());
|
|
6434
|
+
const readOnly = liteLlmConfig?.teamManagement?.readOnly ?? false;
|
|
6435
|
+
const memberManagerRoles = liteLlmConfig?.teamManagement?.memberManagerRoles ?? [];
|
|
6347
6436
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6348
6437
|
TeamUsage,
|
|
6349
6438
|
{
|
|
@@ -6351,10 +6440,13 @@ var init_LiteLLMPage = __esm({
|
|
|
6351
6440
|
loading: teamsLoading,
|
|
6352
6441
|
getTeamUsage: (teamId) => teamUsageCache[teamId] ?? null,
|
|
6353
6442
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
6354
|
-
canManage: teamMgmtEnabled && canManageTeam,
|
|
6443
|
+
canManage: teamMgmtEnabled && canManageTeam && !readOnly,
|
|
6355
6444
|
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
|
|
6356
|
-
canCreate: teamMgmtEnabled && canCreateTeam,
|
|
6445
|
+
canCreate: teamMgmtEnabled && canCreateTeam && !readOnly,
|
|
6357
6446
|
onCreateTeam: () => setManageTeam({ mode: "create" }),
|
|
6447
|
+
canManageMembers: (t) => teamMgmtEnabled && canManageMembers && isTeamMemberManager(t, userInfo?.user_id, memberManagerRoles, readOnly),
|
|
6448
|
+
onManageMembers: (t) => setManageTeam({ mode: "members", team: t }),
|
|
6449
|
+
readOnly,
|
|
6358
6450
|
onTeamExpand: (team) => loadTeamUsage(team.team_id)
|
|
6359
6451
|
}
|
|
6360
6452
|
);
|
|
@@ -6398,7 +6490,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6398
6490
|
onCreateKey: handleGenerateKey,
|
|
6399
6491
|
onUpdateKey: handleUpdateKey,
|
|
6400
6492
|
onResetKeySpend: handleResetKeySpend,
|
|
6401
|
-
onGetConfig:
|
|
6493
|
+
onGetConfig: getConfig
|
|
6402
6494
|
}
|
|
6403
6495
|
),
|
|
6404
6496
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
@@ -6406,10 +6498,13 @@ var init_LiteLLMPage = __esm({
|
|
|
6406
6498
|
{
|
|
6407
6499
|
open: !!manageTeam,
|
|
6408
6500
|
onClose: () => setManageTeam(null),
|
|
6409
|
-
mode: manageTeam?.mode ?? "create",
|
|
6501
|
+
mode: manageTeam?.mode === "members" ? "edit" : manageTeam?.mode ?? "create",
|
|
6410
6502
|
team: manageTeam?.team,
|
|
6411
6503
|
allModels: allModels ?? [],
|
|
6412
6504
|
config: liteLlmConfig,
|
|
6505
|
+
membersOnly: manageTeam?.mode === "members",
|
|
6506
|
+
currentUserId: userInfo?.user_id,
|
|
6507
|
+
memberManagerRoles: liteLlmConfig?.teamManagement?.memberManagerRoles,
|
|
6413
6508
|
onSubmit: async (payload) => {
|
|
6414
6509
|
try {
|
|
6415
6510
|
if (manageTeam?.mode === "edit" && manageTeam.team) {
|
|
@@ -6430,7 +6525,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6430
6525
|
if (alert) alertApi.post(alert);
|
|
6431
6526
|
}
|
|
6432
6527
|
},
|
|
6433
|
-
canManageMembers: teamMgmtEnabled && canManageMembers,
|
|
6528
|
+
canManageMembers: teamMgmtEnabled && canManageMembers && !liteLlmConfig?.teamManagement?.readOnly,
|
|
6434
6529
|
onAddMember: async (userEntityRef, maxBudgetInTeam) => {
|
|
6435
6530
|
if (!manageTeam?.team) return;
|
|
6436
6531
|
const updated = await api.addTeamMember(manageTeam.team.team_id, {
|