@acarmisc/backstage-plugin-litellm 0.31.1 → 0.33.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 +16 -1
- package/dist/components/TeamUsage.d.ts +5 -0
- package/dist/index.cjs.js +356 -195
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +369 -208
- 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
package/dist/index.cjs.js
CHANGED
|
@@ -3127,7 +3127,7 @@ var init_KeyFormDialog = __esm({
|
|
|
3127
3127
|
});
|
|
3128
3128
|
|
|
3129
3129
|
// src/components/ManageTeamDialog.tsx
|
|
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_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;
|
|
3131
3131
|
var init_ManageTeamDialog = __esm({
|
|
3132
3132
|
"src/components/ManageTeamDialog.tsx"() {
|
|
3133
3133
|
"use strict";
|
|
@@ -3148,6 +3148,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3148
3148
|
import_Divider2 = __toESM(require("@mui/material/Divider"));
|
|
3149
3149
|
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
3150
3150
|
import_IconButton4 = __toESM(require("@mui/material/IconButton"));
|
|
3151
|
+
import_Tooltip4 = __toESM(require("@mui/material/Tooltip"));
|
|
3151
3152
|
import_Paper4 = __toESM(require("@mui/material/Paper"));
|
|
3152
3153
|
import_Table2 = __toESM(require("@mui/material/Table"));
|
|
3153
3154
|
import_TableBody2 = __toESM(require("@mui/material/TableBody"));
|
|
@@ -3186,7 +3187,10 @@ var init_ManageTeamDialog = __esm({
|
|
|
3186
3187
|
onSaveKnowledgeBases,
|
|
3187
3188
|
canManageMcpServers,
|
|
3188
3189
|
mcpServers,
|
|
3189
|
-
onSaveMcpServers
|
|
3190
|
+
onSaveMcpServers,
|
|
3191
|
+
membersOnly,
|
|
3192
|
+
currentUserId,
|
|
3193
|
+
memberManagerRoles
|
|
3190
3194
|
}) => {
|
|
3191
3195
|
const [alias, setAlias] = (0, import_react7.useState)("");
|
|
3192
3196
|
const [models, setModels] = (0, import_react7.useState)([]);
|
|
@@ -3200,6 +3204,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3200
3204
|
const [memberBudget, setMemberBudget] = (0, import_react7.useState)("");
|
|
3201
3205
|
const [memberBusy, setMemberBusy] = (0, import_react7.useState)(false);
|
|
3202
3206
|
const [memberError, setMemberError] = (0, import_react7.useState)(null);
|
|
3207
|
+
const [pendingMembers, setPendingMembers] = (0, import_react7.useState)([]);
|
|
3203
3208
|
const [kbIds, setKbIds] = (0, import_react7.useState)([]);
|
|
3204
3209
|
const [kbBusy, setKbBusy] = (0, import_react7.useState)(false);
|
|
3205
3210
|
const [kbError, setKbError] = (0, import_react7.useState)(null);
|
|
@@ -3210,8 +3215,17 @@ var init_ManageTeamDialog = __esm({
|
|
|
3210
3215
|
const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
|
|
3211
3216
|
const hideBudgetForManagers = config?.display?.hideTeamBudgetForManagers ?? false;
|
|
3212
3217
|
const budgetWriteOnly = hideBudgetForManagers && mode === "edit";
|
|
3213
|
-
const
|
|
3214
|
-
const
|
|
3218
|
+
const creating = mode === "create";
|
|
3219
|
+
const members = (0, import_react7.useMemo)(
|
|
3220
|
+
() => creating ? pendingMembers.map((p) => ({
|
|
3221
|
+
user_id: p.userEntityRef,
|
|
3222
|
+
user_email: p.label,
|
|
3223
|
+
role: "user"
|
|
3224
|
+
})) : team?.members_with_roles ?? [],
|
|
3225
|
+
[creating, pendingMembers, team]
|
|
3226
|
+
);
|
|
3227
|
+
const showMembers = !!canManageMembers || !!membersOnly;
|
|
3228
|
+
const wide = showMembers && !membersOnly;
|
|
3215
3229
|
const catalogApi = (0, import_core_plugin_api4.useApi)(import_plugin_catalog_react.catalogApiRef);
|
|
3216
3230
|
const [searchUsers, setSearchUsers] = (0, import_react7.useState)([]);
|
|
3217
3231
|
const [usersLoading, setUsersLoading] = (0, import_react7.useState)(false);
|
|
@@ -3256,8 +3270,8 @@ var init_ManageTeamDialog = __esm({
|
|
|
3256
3270
|
email,
|
|
3257
3271
|
label: email ? `${displayName} (${email})` : displayName
|
|
3258
3272
|
};
|
|
3259
|
-
}).filter((o) => !(o.email && existingEmails.has(o.email.toLowerCase()))).sort((a, b) => a.label.localeCompare(b.label));
|
|
3260
|
-
}, [searchUsers, members]);
|
|
3273
|
+
}).filter((o) => !(o.email && existingEmails.has(o.email.toLowerCase()))).filter((o) => !pendingMembers.some((p) => p.userEntityRef === o.ref)).sort((a, b) => a.label.localeCompare(b.label));
|
|
3274
|
+
}, [searchUsers, members, pendingMembers]);
|
|
3261
3275
|
(0, import_react7.useEffect)(() => {
|
|
3262
3276
|
return () => {
|
|
3263
3277
|
if (debounceTimerRef.current) {
|
|
@@ -3291,6 +3305,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3291
3305
|
setMemberRef("");
|
|
3292
3306
|
setMemberBudget("");
|
|
3293
3307
|
setMemberError(null);
|
|
3308
|
+
setPendingMembers([]);
|
|
3294
3309
|
setKbIds(team?.object_permission?.vector_stores ?? []);
|
|
3295
3310
|
setKbError(null);
|
|
3296
3311
|
setMcpIds(team?.object_permission?.mcp_servers ?? []);
|
|
@@ -3349,7 +3364,13 @@ var init_ManageTeamDialog = __esm({
|
|
|
3349
3364
|
...budgetDuration && { budget_duration: budgetDuration }
|
|
3350
3365
|
};
|
|
3351
3366
|
}
|
|
3352
|
-
await onSubmit(
|
|
3367
|
+
await onSubmit(
|
|
3368
|
+
payload,
|
|
3369
|
+
creating ? pendingMembers.map(({ userEntityRef, maxBudgetInTeam }) => ({
|
|
3370
|
+
userEntityRef,
|
|
3371
|
+
...maxBudgetInTeam !== void 0 && { maxBudgetInTeam }
|
|
3372
|
+
})) : void 0
|
|
3373
|
+
);
|
|
3353
3374
|
onClose();
|
|
3354
3375
|
} catch (err) {
|
|
3355
3376
|
setError(err instanceof Error ? err.message : "An error occurred");
|
|
@@ -3358,13 +3379,26 @@ var init_ManageTeamDialog = __esm({
|
|
|
3358
3379
|
}
|
|
3359
3380
|
};
|
|
3360
3381
|
const handleAddMember = async () => {
|
|
3361
|
-
if (!
|
|
3382
|
+
if (!memberRef.trim() || !creating && !onAddMember) return;
|
|
3362
3383
|
setMemberError(null);
|
|
3363
|
-
const budget = memberBudget ? parseFloat(memberBudget) : void 0;
|
|
3384
|
+
const budget = !membersOnly && memberBudget ? parseFloat(memberBudget) : void 0;
|
|
3364
3385
|
if (budget !== void 0 && (isNaN(budget) || budget <= 0)) {
|
|
3365
3386
|
setMemberError("Max budget in team must be a positive number");
|
|
3366
3387
|
return;
|
|
3367
3388
|
}
|
|
3389
|
+
if (creating) {
|
|
3390
|
+
const ref = memberRef.trim();
|
|
3391
|
+
if (!pendingMembers.some((p) => p.userEntityRef === ref)) {
|
|
3392
|
+
setPendingMembers((list) => [
|
|
3393
|
+
...list,
|
|
3394
|
+
{ userEntityRef: ref, label: memberQuery.trim() || ref, maxBudgetInTeam: budget }
|
|
3395
|
+
]);
|
|
3396
|
+
}
|
|
3397
|
+
setMemberQuery("");
|
|
3398
|
+
setMemberRef("");
|
|
3399
|
+
setMemberBudget("");
|
|
3400
|
+
return;
|
|
3401
|
+
}
|
|
3368
3402
|
try {
|
|
3369
3403
|
setMemberBusy(true);
|
|
3370
3404
|
await onAddMember(memberRef.trim(), budget);
|
|
@@ -3378,6 +3412,10 @@ var init_ManageTeamDialog = __esm({
|
|
|
3378
3412
|
}
|
|
3379
3413
|
};
|
|
3380
3414
|
const handleRemoveMember = async (userId) => {
|
|
3415
|
+
if (creating) {
|
|
3416
|
+
setPendingMembers((list) => list.filter((p) => p.userEntityRef !== userId));
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3381
3419
|
if (!onRemoveMember) return;
|
|
3382
3420
|
setMemberError(null);
|
|
3383
3421
|
try {
|
|
@@ -3421,186 +3459,242 @@ var init_ManageTeamDialog = __esm({
|
|
|
3421
3459
|
const showMcpServers = mode === "edit" && !!canManageMcpServers;
|
|
3422
3460
|
const mcpOptions = (mcpServers ?? []).map((s) => s.id);
|
|
3423
3461
|
const mcpLabel = (id) => (mcpServers ?? []).find((s) => s.id === id)?.name ?? id;
|
|
3424
|
-
|
|
3425
|
-
|
|
3462
|
+
const isRemovalDisabled = (memberUserId, memberRole) => {
|
|
3463
|
+
if (!membersOnly) return { disabled: false };
|
|
3464
|
+
if (memberUserId === currentUserId) {
|
|
3465
|
+
return { disabled: true, reason: "You can't remove yourself" };
|
|
3466
|
+
}
|
|
3467
|
+
if (memberManagerRoles?.includes(memberRole)) {
|
|
3468
|
+
return { disabled: true, reason: `Only platform admins can remove a team ${memberRole}` };
|
|
3469
|
+
}
|
|
3470
|
+
return { disabled: false };
|
|
3471
|
+
};
|
|
3472
|
+
const submitLabel = creating ? "Create team" : "Save";
|
|
3473
|
+
const getDialogTitle = () => {
|
|
3474
|
+
if (membersOnly) {
|
|
3475
|
+
return `Manage members \u2014 ${team?.team_alias || "Untitled team"}`;
|
|
3476
|
+
}
|
|
3477
|
+
return mode === "create" ? "Create Team" : "Edit Team";
|
|
3478
|
+
};
|
|
3479
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Dialog3.default, { open, onClose, maxWidth: wide ? "md" : "sm", fullWidth: true, children: [
|
|
3480
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_DialogTitle3.default, { children: getDialogTitle() }),
|
|
3426
3481
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_DialogContent3.default, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
3427
3482
|
error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", children: error }),
|
|
3428
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
fullWidth: true
|
|
3436
|
-
}
|
|
3437
|
-
),
|
|
3438
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3439
|
-
import_Autocomplete2.default,
|
|
3440
|
-
{
|
|
3441
|
-
multiple: true,
|
|
3442
|
-
options: modelNames,
|
|
3443
|
-
value: models,
|
|
3444
|
-
onChange: (_, newValue) => setModels(newValue),
|
|
3445
|
-
disabled: submitting,
|
|
3446
|
-
renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3447
|
-
import_TextField3.default,
|
|
3448
|
-
{
|
|
3449
|
-
...params,
|
|
3450
|
-
label: "Models",
|
|
3451
|
-
required: true,
|
|
3452
|
-
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.)"
|
|
3453
|
-
}
|
|
3454
|
-
)
|
|
3455
|
-
}
|
|
3456
|
-
),
|
|
3457
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3458
|
-
import_TextField3.default,
|
|
3459
|
-
{
|
|
3460
|
-
label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
|
|
3461
|
-
type: "number",
|
|
3462
|
-
value: maxBudget,
|
|
3463
|
-
onChange: (e) => setMaxBudget(e.target.value),
|
|
3464
|
-
disabled: submitting || unlimited,
|
|
3465
|
-
inputProps: { min: 0, max: maxBudgetCeiling },
|
|
3466
|
-
helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
|
|
3467
|
-
fullWidth: true
|
|
3468
|
-
}
|
|
3469
|
-
),
|
|
3470
|
-
allowUnlimitedBudget && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3471
|
-
import_FormControlLabel2.default,
|
|
3472
|
-
{
|
|
3473
|
-
control: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
|
|
3474
|
-
label: "Unlimited Budget"
|
|
3475
|
-
}
|
|
3476
|
-
),
|
|
3477
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3478
|
-
import_TextField3.default,
|
|
3483
|
+
membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Alert2.default, { severity: "info", children: [
|
|
3484
|
+
"As a team ",
|
|
3485
|
+
team?.members_with_roles?.find((m) => m.user_id === currentUserId)?.role || "member",
|
|
3486
|
+
" you can add or remove members. Budgets and models are managed by your platform admins."
|
|
3487
|
+
] }),
|
|
3488
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
3489
|
+
import_Box10.default,
|
|
3479
3490
|
{
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "Members" }),
|
|
3493
|
-
memberError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null), children: memberError }),
|
|
3494
|
-
members.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: "No members yet." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableContainer2.default, { component: import_Paper4.default, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Table2.default, { size: "small", sx: dataTableSx, children: [
|
|
3495
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableHead2.default, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
|
|
3496
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: "User" }),
|
|
3497
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: "Role" }),
|
|
3498
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", sx: { width: 40 } })
|
|
3499
|
-
] }) }),
|
|
3500
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableBody2.default, { children: members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
|
|
3501
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: m.user_email ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
3502
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", children: m.user_email }),
|
|
3491
|
+
sx: {
|
|
3492
|
+
display: "grid",
|
|
3493
|
+
gridTemplateColumns: {
|
|
3494
|
+
xs: "1fr",
|
|
3495
|
+
md: wide ? "minmax(0, 1fr) minmax(0, 1fr)" : "1fr"
|
|
3496
|
+
},
|
|
3497
|
+
gap: 3,
|
|
3498
|
+
alignItems: "start"
|
|
3499
|
+
},
|
|
3500
|
+
children: [
|
|
3501
|
+
!membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
3502
|
+
wide && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", children: "Settings" }),
|
|
3503
3503
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3504
|
-
|
|
3504
|
+
import_TextField3.default,
|
|
3505
3505
|
{
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3506
|
+
label: "Team Alias",
|
|
3507
|
+
value: alias,
|
|
3508
|
+
onChange: (e) => setAlias(e.target.value),
|
|
3509
|
+
disabled: submitting,
|
|
3510
|
+
fullWidth: true
|
|
3510
3511
|
}
|
|
3511
|
-
)
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_icons_material5.Delete, { fontSize: "small" })
|
|
3530
|
-
}
|
|
3531
|
-
) })
|
|
3532
|
-
] }, m.user_id)) })
|
|
3533
|
-
] }) }),
|
|
3534
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" }, children: [
|
|
3535
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3536
|
-
import_Autocomplete2.default,
|
|
3537
|
-
{
|
|
3538
|
-
freeSolo: true,
|
|
3539
|
-
autoHighlight: true,
|
|
3540
|
-
options: userOptions,
|
|
3541
|
-
loading: usersLoading,
|
|
3542
|
-
inputValue: memberQuery,
|
|
3543
|
-
onInputChange: (_, newInputValue, reason) => {
|
|
3544
|
-
setMemberQuery(newInputValue);
|
|
3545
|
-
if (reason === "input" || reason === "clear") {
|
|
3546
|
-
setMemberRef(newInputValue);
|
|
3547
|
-
handleUserSearch(newInputValue);
|
|
3512
|
+
),
|
|
3513
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3514
|
+
import_Autocomplete2.default,
|
|
3515
|
+
{
|
|
3516
|
+
multiple: true,
|
|
3517
|
+
options: modelNames,
|
|
3518
|
+
value: models,
|
|
3519
|
+
onChange: (_, newValue) => setModels(newValue),
|
|
3520
|
+
disabled: submitting,
|
|
3521
|
+
renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3522
|
+
import_TextField3.default,
|
|
3523
|
+
{
|
|
3524
|
+
...params,
|
|
3525
|
+
label: "Models",
|
|
3526
|
+
required: true,
|
|
3527
|
+
helperText: "Only models your admin allow-listed for teams. At least one is required."
|
|
3528
|
+
}
|
|
3529
|
+
)
|
|
3548
3530
|
}
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3531
|
+
),
|
|
3532
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
3533
|
+
import_Box10.default,
|
|
3534
|
+
{
|
|
3535
|
+
sx: {
|
|
3536
|
+
display: "grid",
|
|
3537
|
+
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
|
|
3538
|
+
gap: 2
|
|
3539
|
+
},
|
|
3540
|
+
children: [
|
|
3541
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3542
|
+
import_TextField3.default,
|
|
3543
|
+
{
|
|
3544
|
+
label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
|
|
3545
|
+
type: "number",
|
|
3546
|
+
value: maxBudget,
|
|
3547
|
+
onChange: (e) => setMaxBudget(e.target.value),
|
|
3548
|
+
disabled: submitting || unlimited,
|
|
3549
|
+
inputProps: { min: 0, max: maxBudgetCeiling },
|
|
3550
|
+
helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
|
|
3551
|
+
fullWidth: true
|
|
3552
|
+
}
|
|
3553
|
+
),
|
|
3554
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3555
|
+
import_TextField3.default,
|
|
3556
|
+
{
|
|
3557
|
+
select: true,
|
|
3558
|
+
label: "Budget Duration",
|
|
3559
|
+
value: budgetDuration,
|
|
3560
|
+
onChange: (e) => setBudgetDuration(e.target.value),
|
|
3561
|
+
disabled: submitting || unlimited,
|
|
3562
|
+
helperText: "Spend-reset period for the team budget",
|
|
3563
|
+
fullWidth: true,
|
|
3564
|
+
children: durationOptions.map((o) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_MenuItem2.default, { value: o.value, children: o.label }, o.value))
|
|
3565
|
+
}
|
|
3566
|
+
)
|
|
3567
|
+
]
|
|
3554
3568
|
}
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
disabled: memberBusy,
|
|
3559
|
-
sx: { flex: 1, minWidth: 260 },
|
|
3560
|
-
renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3561
|
-
import_TextField3.default,
|
|
3569
|
+
),
|
|
3570
|
+
allowUnlimitedBudget && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3571
|
+
import_FormControlLabel2.default,
|
|
3562
3572
|
{
|
|
3563
|
-
|
|
3564
|
-
label: "
|
|
3565
|
-
placeholder: "Search by name or email\u2026",
|
|
3566
|
-
size: "small",
|
|
3567
|
-
helperText: "Pick a catalog user, or paste a user entity ref",
|
|
3568
|
-
InputProps: {
|
|
3569
|
-
...params.InputProps,
|
|
3570
|
-
endAdornment: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
3571
|
-
usersLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_CircularProgress3.default, { color: "inherit", size: 14 }) : null,
|
|
3572
|
-
params.InputProps.endAdornment
|
|
3573
|
-
] })
|
|
3574
|
-
}
|
|
3573
|
+
control: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
|
|
3574
|
+
label: "Unlimited Budget"
|
|
3575
3575
|
}
|
|
3576
3576
|
)
|
|
3577
|
-
}
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3577
|
+
] }),
|
|
3578
|
+
showMembers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3579
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: creating ? 0.25 : 1 }, children: creating ? "Initial members" : "Members" }),
|
|
3580
|
+
creating && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: "Added as team users right after the team is created." }),
|
|
3581
|
+
memberError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null), children: memberError }),
|
|
3582
|
+
members.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: creating ? "No members picked yet \u2014 you can also add them later." : "No members yet." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableContainer2.default, { component: import_Paper4.default, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Table2.default, { size: "small", sx: dataTableSx, children: [
|
|
3583
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableHead2.default, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
|
|
3584
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: "User" }),
|
|
3585
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: "Role" }),
|
|
3586
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", sx: { width: 40 } })
|
|
3587
|
+
] }) }),
|
|
3588
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableBody2.default, { children: members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
|
|
3589
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: m.user_email ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
3590
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", children: m.user_email }),
|
|
3591
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3592
|
+
import_Typography8.default,
|
|
3593
|
+
{
|
|
3594
|
+
variant: "caption",
|
|
3595
|
+
color: "text.secondary",
|
|
3596
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 },
|
|
3597
|
+
children: m.user_id
|
|
3598
|
+
}
|
|
3599
|
+
)
|
|
3600
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3601
|
+
import_Typography8.default,
|
|
3602
|
+
{
|
|
3603
|
+
variant: "body2",
|
|
3604
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" },
|
|
3605
|
+
children: m.user_id
|
|
3606
|
+
}
|
|
3607
|
+
) }),
|
|
3608
|
+
/* @__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 }) }),
|
|
3609
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: (() => {
|
|
3610
|
+
const { disabled, reason } = isRemovalDisabled(m.user_id, m.role);
|
|
3611
|
+
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)(
|
|
3612
|
+
import_IconButton4.default,
|
|
3613
|
+
{
|
|
3614
|
+
edge: "end",
|
|
3615
|
+
size: "small",
|
|
3616
|
+
"aria-label": `remove ${m.user_id}`,
|
|
3617
|
+
disabled: memberBusy || disabled,
|
|
3618
|
+
onClick: () => handleRemoveMember(m.user_id),
|
|
3619
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_icons_material5.Delete, { fontSize: "small" })
|
|
3620
|
+
}
|
|
3621
|
+
) }) });
|
|
3622
|
+
})() })
|
|
3623
|
+
] }, m.user_id)) })
|
|
3624
|
+
] }) }),
|
|
3625
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" }, children: [
|
|
3626
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3627
|
+
import_Autocomplete2.default,
|
|
3628
|
+
{
|
|
3629
|
+
freeSolo: true,
|
|
3630
|
+
autoHighlight: true,
|
|
3631
|
+
options: userOptions,
|
|
3632
|
+
loading: usersLoading,
|
|
3633
|
+
inputValue: memberQuery,
|
|
3634
|
+
onInputChange: (_, newInputValue, reason) => {
|
|
3635
|
+
setMemberQuery(newInputValue);
|
|
3636
|
+
if (reason === "input" || reason === "clear") {
|
|
3637
|
+
setMemberRef(newInputValue);
|
|
3638
|
+
handleUserSearch(newInputValue);
|
|
3639
|
+
}
|
|
3640
|
+
},
|
|
3641
|
+
onChange: (_, newValue) => {
|
|
3642
|
+
if (newValue && typeof newValue !== "string") {
|
|
3643
|
+
setMemberQuery(newValue.label);
|
|
3644
|
+
setMemberRef(newValue.ref);
|
|
3645
|
+
}
|
|
3646
|
+
},
|
|
3647
|
+
getOptionLabel: (o) => typeof o === "string" ? o : o.label,
|
|
3648
|
+
isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
|
|
3649
|
+
disabled: memberBusy,
|
|
3650
|
+
sx: { flex: "1 1 240px" },
|
|
3651
|
+
renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3652
|
+
import_TextField3.default,
|
|
3653
|
+
{
|
|
3654
|
+
...params,
|
|
3655
|
+
label: "Add member",
|
|
3656
|
+
placeholder: "Search by name or email\u2026",
|
|
3657
|
+
size: "small",
|
|
3658
|
+
helperText: "Pick a catalog user, or paste a user entity ref",
|
|
3659
|
+
InputProps: {
|
|
3660
|
+
...params.InputProps,
|
|
3661
|
+
endAdornment: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
3662
|
+
usersLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_CircularProgress3.default, { color: "inherit", size: 14 }) : null,
|
|
3663
|
+
params.InputProps.endAdornment
|
|
3664
|
+
] })
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
)
|
|
3668
|
+
}
|
|
3669
|
+
),
|
|
3670
|
+
!membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3671
|
+
import_TextField3.default,
|
|
3672
|
+
{
|
|
3673
|
+
label: "Max budget in team ($)",
|
|
3674
|
+
type: "number",
|
|
3675
|
+
value: memberBudget,
|
|
3676
|
+
onChange: (e) => setMemberBudget(e.target.value),
|
|
3677
|
+
disabled: memberBusy,
|
|
3678
|
+
size: "small",
|
|
3679
|
+
sx: { width: 150 }
|
|
3680
|
+
}
|
|
3681
|
+
),
|
|
3682
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3683
|
+
import_Button4.default,
|
|
3684
|
+
{
|
|
3685
|
+
onClick: handleAddMember,
|
|
3686
|
+
disabled: memberBusy || !memberRef.trim(),
|
|
3687
|
+
variant: "outlined",
|
|
3688
|
+
sx: { mt: 0.5 },
|
|
3689
|
+
children: "Add"
|
|
3690
|
+
}
|
|
3691
|
+
)
|
|
3692
|
+
] })
|
|
3693
|
+
] })
|
|
3694
|
+
]
|
|
3695
|
+
}
|
|
3696
|
+
),
|
|
3697
|
+
!membersOnly && showKnowledgeBases && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3604
3698
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
|
|
3605
3699
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "Knowledge Bases" }),
|
|
3606
3700
|
/* @__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." }),
|
|
@@ -3627,7 +3721,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
3627
3721
|
}
|
|
3628
3722
|
) })
|
|
3629
3723
|
] }),
|
|
3630
|
-
showMcpServers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3724
|
+
!membersOnly && showMcpServers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
|
|
3631
3725
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
|
|
3632
3726
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "MCP Servers" }),
|
|
3633
3727
|
/* @__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." }),
|
|
@@ -3656,8 +3750,8 @@ var init_ManageTeamDialog = __esm({
|
|
|
3656
3750
|
] })
|
|
3657
3751
|
] }),
|
|
3658
3752
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_DialogActions3.default, { children: [
|
|
3659
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: onClose, disabled: submitting, children: "Cancel" }),
|
|
3660
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained", children: submitting ? "Saving..." :
|
|
3753
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: onClose, disabled: submitting, children: membersOnly ? "Done" : "Cancel" }),
|
|
3754
|
+
!membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained", children: submitting ? "Saving..." : submitLabel })
|
|
3661
3755
|
] })
|
|
3662
3756
|
] });
|
|
3663
3757
|
};
|
|
@@ -4893,7 +4987,7 @@ function budgetStatValue(hidden, budget) {
|
|
|
4893
4987
|
if (budget > 0) return fmtUsd2(budget);
|
|
4894
4988
|
return "Unlimited";
|
|
4895
4989
|
}
|
|
4896
|
-
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;
|
|
4990
|
+
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;
|
|
4897
4991
|
var init_TeamUsage = __esm({
|
|
4898
4992
|
"src/components/TeamUsage.tsx"() {
|
|
4899
4993
|
"use strict";
|
|
@@ -4914,12 +5008,13 @@ var init_TeamUsage = __esm({
|
|
|
4914
5008
|
import_Button6 = __toESM(require("@mui/material/Button"));
|
|
4915
5009
|
import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
|
|
4916
5010
|
import_styles10 = require("@mui/material/styles");
|
|
5011
|
+
import_Tooltip5 = __toESM(require("@mui/material/Tooltip"));
|
|
4917
5012
|
import_icons_material7 = require("@mui/icons-material");
|
|
4918
5013
|
import_recharts2 = require("recharts");
|
|
4919
5014
|
init_ui();
|
|
4920
5015
|
import_jsx_runtime15 = require("react/jsx-runtime");
|
|
4921
5016
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
4922
|
-
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam, onExpand }) => {
|
|
5017
|
+
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand }) => {
|
|
4923
5018
|
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
4924
5019
|
const chart = useChartTheme();
|
|
4925
5020
|
const handleExpandChange = (newExpanded) => {
|
|
@@ -5029,6 +5124,17 @@ var init_TeamUsage = __esm({
|
|
|
5029
5124
|
children: "Edit"
|
|
5030
5125
|
}
|
|
5031
5126
|
),
|
|
5127
|
+
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)(
|
|
5128
|
+
import_Button6.default,
|
|
5129
|
+
{
|
|
5130
|
+
size: "small",
|
|
5131
|
+
variant: "text",
|
|
5132
|
+
startIcon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_icons_material7.GroupAddOutlined, {}),
|
|
5133
|
+
onClick: () => onManageMembers(team),
|
|
5134
|
+
sx: { textTransform: "none" },
|
|
5135
|
+
children: "Manage members"
|
|
5136
|
+
}
|
|
5137
|
+
) }),
|
|
5032
5138
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
5033
5139
|
import_IconButton6.default,
|
|
5034
5140
|
{
|
|
@@ -5159,9 +5265,23 @@ var init_TeamUsage = __esm({
|
|
|
5159
5265
|
onEditTeam,
|
|
5160
5266
|
canCreate,
|
|
5161
5267
|
onCreateTeam,
|
|
5162
|
-
onTeamExpand
|
|
5268
|
+
onTeamExpand,
|
|
5269
|
+
canManageMembers,
|
|
5270
|
+
onManageMembers,
|
|
5271
|
+
readOnly
|
|
5163
5272
|
}) => {
|
|
5164
|
-
const
|
|
5273
|
+
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)(
|
|
5274
|
+
import_Box14.default,
|
|
5275
|
+
{
|
|
5276
|
+
component: "span",
|
|
5277
|
+
sx: { display: "inline-flex", alignItems: "center", gap: 0.5, color: "text.secondary", fontSize: 12.5 },
|
|
5278
|
+
children: [
|
|
5279
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_icons_material7.LockOutlined, { sx: { fontSize: 15 } }),
|
|
5280
|
+
"Synced from identity provider"
|
|
5281
|
+
]
|
|
5282
|
+
}
|
|
5283
|
+
) });
|
|
5284
|
+
const createButton = canCreate && onCreateTeam ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
5165
5285
|
import_Button6.default,
|
|
5166
5286
|
{
|
|
5167
5287
|
variant: "contained",
|
|
@@ -5172,6 +5292,7 @@ var init_TeamUsage = __esm({
|
|
|
5172
5292
|
children: "Create Team"
|
|
5173
5293
|
}
|
|
5174
5294
|
) : void 0;
|
|
5295
|
+
const createAction = readOnly ? readOnlyBadge : createButton;
|
|
5175
5296
|
if (loading) {
|
|
5176
5297
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(SectionCard, { title: "Teams", actions: createAction, children: [
|
|
5177
5298
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Skeleton5.default, { variant: "rounded", height: 120, sx: { mb: 2 } }),
|
|
@@ -5201,6 +5322,8 @@ var init_TeamUsage = __esm({
|
|
|
5201
5322
|
usageLoading: getTeamUsageLoading(team.team_id),
|
|
5202
5323
|
canManage,
|
|
5203
5324
|
onEditTeam,
|
|
5325
|
+
canManageMembers: canManageMembers?.(team),
|
|
5326
|
+
onManageMembers,
|
|
5204
5327
|
onExpand: onTeamExpand
|
|
5205
5328
|
},
|
|
5206
5329
|
team.team_id
|
|
@@ -5229,7 +5352,7 @@ function fmtTokens(n) {
|
|
|
5229
5352
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
5230
5353
|
return String(n);
|
|
5231
5354
|
}
|
|
5232
|
-
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,
|
|
5355
|
+
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;
|
|
5233
5356
|
var init_ModelsTable = __esm({
|
|
5234
5357
|
"src/components/ModelsTable.tsx"() {
|
|
5235
5358
|
"use strict";
|
|
@@ -5251,7 +5374,7 @@ var init_ModelsTable = __esm({
|
|
|
5251
5374
|
import_InputAdornment3 = __toESM(require("@mui/material/InputAdornment"));
|
|
5252
5375
|
import_icons_material8 = require("@mui/icons-material");
|
|
5253
5376
|
import_ContentCopy = __toESM(require("@mui/icons-material/ContentCopy"));
|
|
5254
|
-
|
|
5377
|
+
import_Tooltip6 = __toESM(require("@mui/material/Tooltip"));
|
|
5255
5378
|
import_Snackbar = __toESM(require("@mui/material/Snackbar"));
|
|
5256
5379
|
import_Alert4 = __toESM(require("@mui/material/Alert"));
|
|
5257
5380
|
init_ui();
|
|
@@ -5414,7 +5537,7 @@ var init_ModelsTable = __esm({
|
|
|
5414
5537
|
] }) }),
|
|
5415
5538
|
/* @__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: [
|
|
5416
5539
|
/* @__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: [
|
|
5417
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5540
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_Tooltip6.default, { title: m.model_name, placement: "top", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5418
5541
|
"span",
|
|
5419
5542
|
{
|
|
5420
5543
|
role: "button",
|
|
@@ -5426,7 +5549,7 @@ var init_ModelsTable = __esm({
|
|
|
5426
5549
|
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
5427
5550
|
}
|
|
5428
5551
|
) }),
|
|
5429
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5552
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_Tooltip6.default, { title: "Copy model ID", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5430
5553
|
import_Box15.default,
|
|
5431
5554
|
{
|
|
5432
5555
|
component: "span",
|
|
@@ -5836,6 +5959,19 @@ var init_feedback = __esm({
|
|
|
5836
5959
|
}
|
|
5837
5960
|
});
|
|
5838
5961
|
|
|
5962
|
+
// src/teamMemberManager.ts
|
|
5963
|
+
function isTeamMemberManager(team, userId, memberManagerRoles, readOnly) {
|
|
5964
|
+
if (!team || !userId || !memberManagerRoles || memberManagerRoles.length === 0 || readOnly) {
|
|
5965
|
+
return false;
|
|
5966
|
+
}
|
|
5967
|
+
return team.members_with_roles?.some((m) => m.user_id === userId && memberManagerRoles.includes(m.role)) ?? false;
|
|
5968
|
+
}
|
|
5969
|
+
var init_teamMemberManager = __esm({
|
|
5970
|
+
"src/teamMemberManager.ts"() {
|
|
5971
|
+
"use strict";
|
|
5972
|
+
}
|
|
5973
|
+
});
|
|
5974
|
+
|
|
5839
5975
|
// src/unprovisionedCopy.ts
|
|
5840
5976
|
function getUnprovisionedTitle() {
|
|
5841
5977
|
return "Your LiteLLM account isn't set up yet";
|
|
@@ -5912,6 +6048,7 @@ var init_LiteLLMPage = __esm({
|
|
|
5912
6048
|
init_dates();
|
|
5913
6049
|
init_permissions();
|
|
5914
6050
|
init_feedback();
|
|
6051
|
+
init_teamMemberManager();
|
|
5915
6052
|
init_unprovisionedCopy();
|
|
5916
6053
|
import_jsx_runtime18 = require("react/jsx-runtime");
|
|
5917
6054
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
@@ -6358,6 +6495,8 @@ var init_LiteLLMPage = __esm({
|
|
|
6358
6495
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
6359
6496
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
6360
6497
|
const visibleTeams = Array.from(teamsById.values());
|
|
6498
|
+
const readOnly = liteLlmConfig?.teamManagement?.readOnly ?? false;
|
|
6499
|
+
const memberManagerRoles = liteLlmConfig?.teamManagement?.memberManagerRoles ?? [];
|
|
6361
6500
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6362
6501
|
TeamUsage,
|
|
6363
6502
|
{
|
|
@@ -6365,10 +6504,13 @@ var init_LiteLLMPage = __esm({
|
|
|
6365
6504
|
loading: teamsLoading,
|
|
6366
6505
|
getTeamUsage: (teamId) => teamUsageCache[teamId] ?? null,
|
|
6367
6506
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
6368
|
-
canManage: teamMgmtEnabled && canManageTeam,
|
|
6507
|
+
canManage: teamMgmtEnabled && canManageTeam && !readOnly,
|
|
6369
6508
|
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
|
|
6370
|
-
canCreate: teamMgmtEnabled && canCreateTeam,
|
|
6509
|
+
canCreate: teamMgmtEnabled && canCreateTeam && !readOnly,
|
|
6371
6510
|
onCreateTeam: () => setManageTeam({ mode: "create" }),
|
|
6511
|
+
canManageMembers: (t) => teamMgmtEnabled && canManageMembers && isTeamMemberManager(t, userInfo?.user_id, memberManagerRoles, readOnly),
|
|
6512
|
+
onManageMembers: (t) => setManageTeam({ mode: "members", team: t }),
|
|
6513
|
+
readOnly,
|
|
6372
6514
|
onTeamExpand: (team) => loadTeamUsage(team.team_id)
|
|
6373
6515
|
}
|
|
6374
6516
|
);
|
|
@@ -6420,11 +6562,14 @@ var init_LiteLLMPage = __esm({
|
|
|
6420
6562
|
{
|
|
6421
6563
|
open: !!manageTeam,
|
|
6422
6564
|
onClose: () => setManageTeam(null),
|
|
6423
|
-
mode: manageTeam?.mode ?? "create",
|
|
6565
|
+
mode: manageTeam?.mode === "members" ? "edit" : manageTeam?.mode ?? "create",
|
|
6424
6566
|
team: manageTeam?.team,
|
|
6425
6567
|
allModels: allModels ?? [],
|
|
6426
6568
|
config: liteLlmConfig,
|
|
6427
|
-
|
|
6569
|
+
membersOnly: manageTeam?.mode === "members",
|
|
6570
|
+
currentUserId: userInfo?.user_id,
|
|
6571
|
+
memberManagerRoles: liteLlmConfig?.teamManagement?.memberManagerRoles,
|
|
6572
|
+
onSubmit: async (payload, initialMembers) => {
|
|
6428
6573
|
try {
|
|
6429
6574
|
if (manageTeam?.mode === "edit" && manageTeam.team) {
|
|
6430
6575
|
const loadedAt = manageTeam.team.metadata?.updated_at_iso;
|
|
@@ -6434,7 +6579,23 @@ var init_LiteLLMPage = __esm({
|
|
|
6434
6579
|
...typeof loadedAt === "string" ? { expectedUpdatedAtIso: loadedAt } : {}
|
|
6435
6580
|
});
|
|
6436
6581
|
} else {
|
|
6437
|
-
await api.createTeam(payload);
|
|
6582
|
+
const created = await api.createTeam(payload);
|
|
6583
|
+
const failed = [];
|
|
6584
|
+
for (const m of initialMembers ?? []) {
|
|
6585
|
+
try {
|
|
6586
|
+
await api.addTeamMember(created.team_id, m);
|
|
6587
|
+
} catch (e) {
|
|
6588
|
+
failed.push(`${m.userEntityRef} (${e.message})`);
|
|
6589
|
+
}
|
|
6590
|
+
}
|
|
6591
|
+
if (failed.length) {
|
|
6592
|
+
alertApi.post({
|
|
6593
|
+
message: `Team created, but some members could not be added: ${failed.join(", ")}`,
|
|
6594
|
+
severity: "warning"
|
|
6595
|
+
});
|
|
6596
|
+
refreshTeams();
|
|
6597
|
+
return;
|
|
6598
|
+
}
|
|
6438
6599
|
}
|
|
6439
6600
|
const alert = toastFor("teamSaveSuccess");
|
|
6440
6601
|
if (alert) alertApi.post(alert);
|
|
@@ -6444,7 +6605,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6444
6605
|
if (alert) alertApi.post(alert);
|
|
6445
6606
|
}
|
|
6446
6607
|
},
|
|
6447
|
-
canManageMembers: teamMgmtEnabled && canManageMembers,
|
|
6608
|
+
canManageMembers: teamMgmtEnabled && canManageMembers && !liteLlmConfig?.teamManagement?.readOnly,
|
|
6448
6609
|
onAddMember: async (userEntityRef, maxBudgetInTeam) => {
|
|
6449
6610
|
if (!manageTeam?.team) return;
|
|
6450
6611
|
const updated = await api.addTeamMember(manageTeam.team.team_id, {
|