@acarmisc/backstage-plugin-litellm 0.32.0 → 0.34.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/index.cjs.js CHANGED
@@ -1843,8 +1843,6 @@ var init_KeysTable = __esm({
1843
1843
  setPruneSubmitting(true);
1844
1844
  try {
1845
1845
  await onPruneExpiredKeys();
1846
- } catch (error) {
1847
- console.error("Failed to prune expired keys:", error);
1848
1846
  } finally {
1849
1847
  setPruneSubmitting(false);
1850
1848
  setPruneConfirmCount(null);
@@ -1913,7 +1911,8 @@ var init_KeysTable = __esm({
1913
1911
  bgcolor: (0, import_styles7.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.08 : 0.05),
1914
1912
  px: 0.75,
1915
1913
  py: 0.375,
1916
- borderRadius: "5px"
1914
+ borderRadius: "5px",
1915
+ whiteSpace: "nowrap"
1917
1916
  }),
1918
1917
  children: shortKeyId(keyId)
1919
1918
  }
@@ -3204,6 +3203,7 @@ var init_ManageTeamDialog = __esm({
3204
3203
  const [memberBudget, setMemberBudget] = (0, import_react7.useState)("");
3205
3204
  const [memberBusy, setMemberBusy] = (0, import_react7.useState)(false);
3206
3205
  const [memberError, setMemberError] = (0, import_react7.useState)(null);
3206
+ const [pendingMembers, setPendingMembers] = (0, import_react7.useState)([]);
3207
3207
  const [kbIds, setKbIds] = (0, import_react7.useState)([]);
3208
3208
  const [kbBusy, setKbBusy] = (0, import_react7.useState)(false);
3209
3209
  const [kbError, setKbError] = (0, import_react7.useState)(null);
@@ -3214,8 +3214,17 @@ var init_ManageTeamDialog = __esm({
3214
3214
  const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
3215
3215
  const hideBudgetForManagers = config?.display?.hideTeamBudgetForManagers ?? false;
3216
3216
  const budgetWriteOnly = hideBudgetForManagers && mode === "edit";
3217
- const members = (0, import_react7.useMemo)(() => team?.members_with_roles ?? [], [team]);
3218
- const showMembers = mode === "edit" && !!canManageMembers || membersOnly;
3217
+ const creating = mode === "create";
3218
+ const members = (0, import_react7.useMemo)(
3219
+ () => creating ? pendingMembers.map((p) => ({
3220
+ user_id: p.userEntityRef,
3221
+ user_email: p.label,
3222
+ role: "user"
3223
+ })) : team?.members_with_roles ?? [],
3224
+ [creating, pendingMembers, team]
3225
+ );
3226
+ const showMembers = !!canManageMembers || !!membersOnly;
3227
+ const wide = showMembers && !membersOnly;
3219
3228
  const catalogApi = (0, import_core_plugin_api4.useApi)(import_plugin_catalog_react.catalogApiRef);
3220
3229
  const [searchUsers, setSearchUsers] = (0, import_react7.useState)([]);
3221
3230
  const [usersLoading, setUsersLoading] = (0, import_react7.useState)(false);
@@ -3260,8 +3269,8 @@ var init_ManageTeamDialog = __esm({
3260
3269
  email,
3261
3270
  label: email ? `${displayName} (${email})` : displayName
3262
3271
  };
3263
- }).filter((o) => !(o.email && existingEmails.has(o.email.toLowerCase()))).sort((a, b) => a.label.localeCompare(b.label));
3264
- }, [searchUsers, members]);
3272
+ }).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));
3273
+ }, [searchUsers, members, pendingMembers]);
3265
3274
  (0, import_react7.useEffect)(() => {
3266
3275
  return () => {
3267
3276
  if (debounceTimerRef.current) {
@@ -3295,6 +3304,7 @@ var init_ManageTeamDialog = __esm({
3295
3304
  setMemberRef("");
3296
3305
  setMemberBudget("");
3297
3306
  setMemberError(null);
3307
+ setPendingMembers([]);
3298
3308
  setKbIds(team?.object_permission?.vector_stores ?? []);
3299
3309
  setKbError(null);
3300
3310
  setMcpIds(team?.object_permission?.mcp_servers ?? []);
@@ -3353,7 +3363,13 @@ var init_ManageTeamDialog = __esm({
3353
3363
  ...budgetDuration && { budget_duration: budgetDuration }
3354
3364
  };
3355
3365
  }
3356
- await onSubmit(payload);
3366
+ await onSubmit(
3367
+ payload,
3368
+ creating ? pendingMembers.map(({ userEntityRef, maxBudgetInTeam }) => ({
3369
+ userEntityRef,
3370
+ ...maxBudgetInTeam !== void 0 && { maxBudgetInTeam }
3371
+ })) : void 0
3372
+ );
3357
3373
  onClose();
3358
3374
  } catch (err) {
3359
3375
  setError(err instanceof Error ? err.message : "An error occurred");
@@ -3362,13 +3378,26 @@ var init_ManageTeamDialog = __esm({
3362
3378
  }
3363
3379
  };
3364
3380
  const handleAddMember = async () => {
3365
- if (!onAddMember || !memberRef.trim()) return;
3381
+ if (!memberRef.trim() || !creating && !onAddMember) return;
3366
3382
  setMemberError(null);
3367
3383
  const budget = !membersOnly && memberBudget ? parseFloat(memberBudget) : void 0;
3368
3384
  if (budget !== void 0 && (isNaN(budget) || budget <= 0)) {
3369
3385
  setMemberError("Max budget in team must be a positive number");
3370
3386
  return;
3371
3387
  }
3388
+ if (creating) {
3389
+ const ref = memberRef.trim();
3390
+ if (!pendingMembers.some((p) => p.userEntityRef === ref)) {
3391
+ setPendingMembers((list) => [
3392
+ ...list,
3393
+ { userEntityRef: ref, label: memberQuery.trim() || ref, maxBudgetInTeam: budget }
3394
+ ]);
3395
+ }
3396
+ setMemberQuery("");
3397
+ setMemberRef("");
3398
+ setMemberBudget("");
3399
+ return;
3400
+ }
3372
3401
  try {
3373
3402
  setMemberBusy(true);
3374
3403
  await onAddMember(memberRef.trim(), budget);
@@ -3382,6 +3411,10 @@ var init_ManageTeamDialog = __esm({
3382
3411
  }
3383
3412
  };
3384
3413
  const handleRemoveMember = async (userId) => {
3414
+ if (creating) {
3415
+ setPendingMembers((list) => list.filter((p) => p.userEntityRef !== userId));
3416
+ return;
3417
+ }
3385
3418
  if (!onRemoveMember) return;
3386
3419
  setMemberError(null);
3387
3420
  try {
@@ -3435,13 +3468,14 @@ var init_ManageTeamDialog = __esm({
3435
3468
  }
3436
3469
  return { disabled: false };
3437
3470
  };
3471
+ const submitLabel = creating ? "Create team" : "Save";
3438
3472
  const getDialogTitle = () => {
3439
3473
  if (membersOnly) {
3440
3474
  return `Manage members \u2014 ${team?.team_alias || "Untitled team"}`;
3441
3475
  }
3442
3476
  return mode === "create" ? "Create Team" : "Edit Team";
3443
3477
  };
3444
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Dialog3.default, { open, onClose, maxWidth: "sm", fullWidth: true, children: [
3478
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Dialog3.default, { open, onClose, maxWidth: wide ? "md" : "sm", fullWidth: true, children: [
3445
3479
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_DialogTitle3.default, { children: getDialogTitle() }),
3446
3480
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_DialogContent3.default, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 }, children: [
3447
3481
  error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", children: error }),
@@ -3450,186 +3484,215 @@ var init_ManageTeamDialog = __esm({
3450
3484
  team?.members_with_roles?.find((m) => m.user_id === currentUserId)?.role || "member",
3451
3485
  " you can add or remove members. Budgets and models are managed by your platform admins."
3452
3486
  ] }),
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
- ] }),
3517
- showMembers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
3518
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
3519
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "Members" }),
3520
- memberError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null), children: memberError }),
3521
- 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: [
3522
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableHead2.default, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
3523
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: "User" }),
3524
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: "Role" }),
3525
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", sx: { width: 40 } })
3526
- ] }) }),
3527
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableBody2.default, { children: members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
3528
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: m.user_email ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3529
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", children: m.user_email }),
3487
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3488
+ import_Box10.default,
3489
+ {
3490
+ sx: {
3491
+ display: "grid",
3492
+ gridTemplateColumns: {
3493
+ xs: "1fr",
3494
+ md: wide ? "minmax(0, 1fr) minmax(0, 1fr)" : "1fr"
3495
+ },
3496
+ gap: 3,
3497
+ alignItems: "start"
3498
+ },
3499
+ children: [
3500
+ !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
3501
+ wide && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", children: "Settings" }),
3530
3502
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3531
- import_Typography8.default,
3503
+ import_TextField3.default,
3532
3504
  {
3533
- variant: "caption",
3534
- color: "text.secondary",
3535
- sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 },
3536
- children: m.user_id
3505
+ label: "Team Alias",
3506
+ value: alias,
3507
+ onChange: (e) => setAlias(e.target.value),
3508
+ disabled: submitting,
3509
+ fullWidth: true
3537
3510
  }
3538
- )
3539
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3540
- import_Typography8.default,
3541
- {
3542
- variant: "body2",
3543
- sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" },
3544
- children: m.user_id
3545
- }
3546
- ) }),
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 }) }),
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,
3511
+ ),
3512
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3513
+ import_Autocomplete2.default,
3552
3514
  {
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
- })() })
3562
- ] }, m.user_id)) })
3563
- ] }) }),
3564
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" }, children: [
3565
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3566
- import_Autocomplete2.default,
3567
- {
3568
- freeSolo: true,
3569
- autoHighlight: true,
3570
- options: userOptions,
3571
- loading: usersLoading,
3572
- inputValue: memberQuery,
3573
- onInputChange: (_, newInputValue, reason) => {
3574
- setMemberQuery(newInputValue);
3575
- if (reason === "input" || reason === "clear") {
3576
- setMemberRef(newInputValue);
3577
- handleUserSearch(newInputValue);
3515
+ multiple: true,
3516
+ options: modelNames,
3517
+ value: models,
3518
+ onChange: (_, newValue) => setModels(newValue),
3519
+ disabled: submitting,
3520
+ renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3521
+ import_TextField3.default,
3522
+ {
3523
+ ...params,
3524
+ label: "Models",
3525
+ required: true,
3526
+ helperText: "Only models your admin allow-listed for teams. At least one is required."
3527
+ }
3528
+ )
3578
3529
  }
3579
- },
3580
- onChange: (_, newValue) => {
3581
- if (newValue && typeof newValue !== "string") {
3582
- setMemberQuery(newValue.label);
3583
- setMemberRef(newValue.ref);
3530
+ ),
3531
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3532
+ import_Box10.default,
3533
+ {
3534
+ sx: {
3535
+ display: "grid",
3536
+ gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
3537
+ gap: 2
3538
+ },
3539
+ children: [
3540
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3541
+ import_TextField3.default,
3542
+ {
3543
+ label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
3544
+ type: "number",
3545
+ value: maxBudget,
3546
+ onChange: (e) => setMaxBudget(e.target.value),
3547
+ disabled: submitting || unlimited,
3548
+ inputProps: { min: 0, max: maxBudgetCeiling },
3549
+ helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
3550
+ fullWidth: true
3551
+ }
3552
+ ),
3553
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3554
+ import_TextField3.default,
3555
+ {
3556
+ select: true,
3557
+ label: "Budget Duration",
3558
+ value: budgetDuration,
3559
+ onChange: (e) => setBudgetDuration(e.target.value),
3560
+ disabled: submitting || unlimited,
3561
+ helperText: "Spend-reset period for the team budget",
3562
+ fullWidth: true,
3563
+ children: durationOptions.map((o) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_MenuItem2.default, { value: o.value, children: o.label }, o.value))
3564
+ }
3565
+ )
3566
+ ]
3584
3567
  }
3585
- },
3586
- getOptionLabel: (o) => typeof o === "string" ? o : o.label,
3587
- isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
3588
- disabled: memberBusy,
3589
- sx: { flex: 1, minWidth: 260 },
3590
- renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3591
- import_TextField3.default,
3568
+ ),
3569
+ allowUnlimitedBudget && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3570
+ import_FormControlLabel2.default,
3592
3571
  {
3593
- ...params,
3594
- label: "Add member",
3595
- placeholder: "Search by name or email\u2026",
3596
- size: "small",
3597
- helperText: "Pick a catalog user, or paste a user entity ref",
3598
- InputProps: {
3599
- ...params.InputProps,
3600
- endAdornment: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3601
- usersLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_CircularProgress3.default, { color: "inherit", size: 14 }) : null,
3602
- params.InputProps.endAdornment
3603
- ] })
3604
- }
3572
+ control: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
3573
+ label: "Unlimited Budget"
3605
3574
  }
3606
3575
  )
3607
- }
3608
- ),
3609
- !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3610
- import_TextField3.default,
3611
- {
3612
- label: "Max budget in team ($)",
3613
- type: "number",
3614
- value: memberBudget,
3615
- onChange: (e) => setMemberBudget(e.target.value),
3616
- disabled: memberBusy,
3617
- size: "small",
3618
- sx: { width: 160 }
3619
- }
3620
- ),
3621
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3622
- import_Button4.default,
3623
- {
3624
- onClick: handleAddMember,
3625
- disabled: memberBusy || !memberRef.trim(),
3626
- variant: "outlined",
3627
- sx: { mt: 0.5 },
3628
- children: "Add"
3629
- }
3630
- )
3631
- ] })
3632
- ] }),
3576
+ ] }),
3577
+ showMembers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
3578
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: creating ? 0.25 : 1 }, children: creating ? "Initial members" : "Members" }),
3579
+ 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." }),
3580
+ memberError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null), children: memberError }),
3581
+ 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: [
3582
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableHead2.default, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
3583
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: "User" }),
3584
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: "Role" }),
3585
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", sx: { width: 40 } })
3586
+ ] }) }),
3587
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableBody2.default, { children: members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
3588
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: m.user_email ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3589
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", children: m.user_email }),
3590
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3591
+ import_Typography8.default,
3592
+ {
3593
+ variant: "caption",
3594
+ color: "text.secondary",
3595
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 },
3596
+ children: m.user_id
3597
+ }
3598
+ )
3599
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3600
+ import_Typography8.default,
3601
+ {
3602
+ variant: "body2",
3603
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" },
3604
+ children: m.user_id
3605
+ }
3606
+ ) }),
3607
+ /* @__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 }) }),
3608
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: (() => {
3609
+ const { disabled, reason } = isRemovalDisabled(m.user_id, m.role);
3610
+ 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)(
3611
+ import_IconButton4.default,
3612
+ {
3613
+ edge: "end",
3614
+ size: "small",
3615
+ "aria-label": `remove ${m.user_id}`,
3616
+ disabled: memberBusy || disabled,
3617
+ onClick: () => handleRemoveMember(m.user_id),
3618
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_icons_material5.Delete, { fontSize: "small" })
3619
+ }
3620
+ ) }) });
3621
+ })() })
3622
+ ] }, m.user_id)) })
3623
+ ] }) }),
3624
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" }, children: [
3625
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3626
+ import_Autocomplete2.default,
3627
+ {
3628
+ freeSolo: true,
3629
+ autoHighlight: true,
3630
+ options: userOptions,
3631
+ loading: usersLoading,
3632
+ inputValue: memberQuery,
3633
+ onInputChange: (_, newInputValue, reason) => {
3634
+ setMemberQuery(newInputValue);
3635
+ if (reason === "input" || reason === "clear") {
3636
+ setMemberRef(newInputValue);
3637
+ handleUserSearch(newInputValue);
3638
+ }
3639
+ },
3640
+ onChange: (_, newValue) => {
3641
+ if (newValue && typeof newValue !== "string") {
3642
+ setMemberQuery(newValue.label);
3643
+ setMemberRef(newValue.ref);
3644
+ }
3645
+ },
3646
+ getOptionLabel: (o) => typeof o === "string" ? o : o.label,
3647
+ isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
3648
+ disabled: memberBusy,
3649
+ sx: { flex: "1 1 240px" },
3650
+ renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3651
+ import_TextField3.default,
3652
+ {
3653
+ ...params,
3654
+ label: "Add member",
3655
+ placeholder: "Search by name or email\u2026",
3656
+ size: "small",
3657
+ helperText: "Pick a catalog user, or paste a user entity ref",
3658
+ InputProps: {
3659
+ ...params.InputProps,
3660
+ endAdornment: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3661
+ usersLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_CircularProgress3.default, { color: "inherit", size: 14 }) : null,
3662
+ params.InputProps.endAdornment
3663
+ ] })
3664
+ }
3665
+ }
3666
+ )
3667
+ }
3668
+ ),
3669
+ !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3670
+ import_TextField3.default,
3671
+ {
3672
+ label: "Max budget in team ($)",
3673
+ type: "number",
3674
+ value: memberBudget,
3675
+ onChange: (e) => setMemberBudget(e.target.value),
3676
+ disabled: memberBusy,
3677
+ size: "small",
3678
+ sx: { width: 150 }
3679
+ }
3680
+ ),
3681
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3682
+ import_Button4.default,
3683
+ {
3684
+ onClick: handleAddMember,
3685
+ disabled: memberBusy || !memberRef.trim(),
3686
+ variant: "outlined",
3687
+ sx: { mt: 0.5 },
3688
+ children: "Add"
3689
+ }
3690
+ )
3691
+ ] })
3692
+ ] })
3693
+ ]
3694
+ }
3695
+ ),
3633
3696
  !membersOnly && showKnowledgeBases && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
3634
3697
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
3635
3698
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "Knowledge Bases" }),
@@ -3687,7 +3750,7 @@ var init_ManageTeamDialog = __esm({
3687
3750
  ] }),
3688
3751
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_DialogActions3.default, { children: [
3689
3752
  /* @__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" })
3753
+ !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained", children: submitting ? "Saving..." : submitLabel })
3691
3754
  ] })
3692
3755
  ] });
3693
3756
  };
@@ -5833,15 +5896,6 @@ var init_dates = __esm({
5833
5896
  }
5834
5897
  });
5835
5898
 
5836
- // src/permissions.ts
5837
- var import_backstage_plugin_litellm_common5;
5838
- var init_permissions = __esm({
5839
- "src/permissions.ts"() {
5840
- "use strict";
5841
- import_backstage_plugin_litellm_common5 = require("@acarmisc/backstage-plugin-litellm-common");
5842
- }
5843
- });
5844
-
5845
5899
  // src/feedback.ts
5846
5900
  function toastFor(kind, errorMsg) {
5847
5901
  switch (kind) {
@@ -5950,7 +6004,7 @@ function initDateRange() {
5950
6004
  else start.setDate(start.getDate() - 7);
5951
6005
  return { start, end };
5952
6006
  }
5953
- var import_react13, import_Box17, import_Alert6, import_Button7, import_Typography15, import_Tabs2, import_Tab2, import_Grid2, import_Accordion2, import_AccordionSummary2, import_AccordionDetails2, import_ExpandMore, import_react_router_dom, import_react_use2, import_core_plugin_api5, import_core_components3, import_plugin_permission_react3, import_jsx_runtime18, PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
6007
+ var import_react13, import_Box17, import_Alert6, import_Button7, import_Typography15, import_Tabs2, import_Tab2, import_Grid2, import_Accordion2, import_AccordionSummary2, import_AccordionDetails2, import_ExpandMore, import_react_router_dom, import_react_use2, import_core_plugin_api5, import_core_components3, import_plugin_permission_react3, import_backstage_plugin_litellm_common5, import_jsx_runtime18, PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
5954
6008
  var init_LiteLLMPage = __esm({
5955
6009
  "src/components/LiteLLMPage.tsx"() {
5956
6010
  "use strict";
@@ -5982,7 +6036,7 @@ var init_LiteLLMPage = __esm({
5982
6036
  init_AuditLog();
5983
6037
  init_api();
5984
6038
  init_dates();
5985
- init_permissions();
6039
+ import_backstage_plugin_litellm_common5 = require("@acarmisc/backstage-plugin-litellm-common");
5986
6040
  init_feedback();
5987
6041
  init_teamMemberManager();
5988
6042
  init_unprovisionedCopy();
@@ -6505,7 +6559,7 @@ var init_LiteLLMPage = __esm({
6505
6559
  membersOnly: manageTeam?.mode === "members",
6506
6560
  currentUserId: userInfo?.user_id,
6507
6561
  memberManagerRoles: liteLlmConfig?.teamManagement?.memberManagerRoles,
6508
- onSubmit: async (payload) => {
6562
+ onSubmit: async (payload, initialMembers) => {
6509
6563
  try {
6510
6564
  if (manageTeam?.mode === "edit" && manageTeam.team) {
6511
6565
  const loadedAt = manageTeam.team.metadata?.updated_at_iso;
@@ -6515,7 +6569,23 @@ var init_LiteLLMPage = __esm({
6515
6569
  ...typeof loadedAt === "string" ? { expectedUpdatedAtIso: loadedAt } : {}
6516
6570
  });
6517
6571
  } else {
6518
- await api.createTeam(payload);
6572
+ const created = await api.createTeam(payload);
6573
+ const failed = [];
6574
+ for (const m of initialMembers ?? []) {
6575
+ try {
6576
+ await api.addTeamMember(created.team_id, m);
6577
+ } catch (e) {
6578
+ failed.push(`${m.userEntityRef} (${e.message})`);
6579
+ }
6580
+ }
6581
+ if (failed.length) {
6582
+ alertApi.post({
6583
+ message: `Team created, but some members could not be added: ${failed.join(", ")}`,
6584
+ severity: "warning"
6585
+ });
6586
+ refreshTeams();
6587
+ return;
6588
+ }
6519
6589
  }
6520
6590
  const alert = toastFor("teamSaveSuccess");
6521
6591
  if (alert) alertApi.post(alert);
@@ -6592,7 +6662,6 @@ var index_exports = {};
6592
6662
  __export(index_exports, {
6593
6663
  DashboardHeader: () => DashboardHeader,
6594
6664
  GenerateKeyButton: () => GenerateKeyButton,
6595
- GenerateKeyDialog: () => GenerateKeyDialog,
6596
6665
  KeyFormDialog: () => KeyFormDialog,
6597
6666
  KeysTable: () => KeysTable,
6598
6667
  LiteLLMBudgetGauges: () => LiteLLMBudgetGauges,
@@ -6603,13 +6672,14 @@ __export(index_exports, {
6603
6672
  ManageTeamDialog: () => ManageTeamDialog,
6604
6673
  TeamUsage: () => TeamUsage,
6605
6674
  UsageStats: () => UsageStats,
6675
+ default: () => litellmPlugin,
6606
6676
  liteLlmApiRef: () => liteLlmApiRef,
6607
6677
  litellmPlugin: () => litellmPlugin,
6608
- litellmTeamCreatePermission: () => import_backstage_plugin_litellm_common5.litellmTeamCreatePermission,
6609
- litellmTeamKnowledgebaseManagePermission: () => import_backstage_plugin_litellm_common5.litellmTeamKnowledgebaseManagePermission,
6610
- litellmTeamManagePermission: () => import_backstage_plugin_litellm_common5.litellmTeamManagePermission,
6611
- litellmTeamMcpManagePermission: () => import_backstage_plugin_litellm_common5.litellmTeamMcpManagePermission,
6612
- litellmTeamMembersManagePermission: () => import_backstage_plugin_litellm_common5.litellmTeamMembersManagePermission
6678
+ litellmTeamCreatePermission: () => import_backstage_plugin_litellm_common6.litellmTeamCreatePermission,
6679
+ litellmTeamKnowledgebaseManagePermission: () => import_backstage_plugin_litellm_common6.litellmTeamKnowledgebaseManagePermission,
6680
+ litellmTeamManagePermission: () => import_backstage_plugin_litellm_common6.litellmTeamManagePermission,
6681
+ litellmTeamMcpManagePermission: () => import_backstage_plugin_litellm_common6.litellmTeamMcpManagePermission,
6682
+ litellmTeamMembersManagePermission: () => import_backstage_plugin_litellm_common6.litellmTeamMembersManagePermission
6613
6683
  });
6614
6684
  module.exports = __toCommonJS(index_exports);
6615
6685
 
@@ -7540,30 +7610,7 @@ var LiteLLMBudgetGauges = ({
7540
7610
  // src/index.ts
7541
7611
  init_KeyFormDialog();
7542
7612
  init_GenerateKeyButton();
7543
-
7544
- // src/components/GenerateKeyDialog.tsx
7545
- init_KeyFormDialog();
7546
- var import_jsx_runtime22 = require("react/jsx-runtime");
7547
- var GenerateKeyDialog = (props) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7548
- KeyFormDialog,
7549
- {
7550
- open: props.open,
7551
- onClose: props.onClose,
7552
- mode: "create",
7553
- keys: props.keys,
7554
- models: props.models,
7555
- teams: props.teams,
7556
- username: props.username,
7557
- keyGenerationSettings: props.keyGenerationSettings,
7558
- onCreateKey: props.onGenerateKey,
7559
- onUpdateKey: async () => {
7560
- },
7561
- onGetConfig: props.onGetConfig
7562
- }
7563
- );
7564
-
7565
- // src/index.ts
7566
7613
  init_ManageTeamDialog();
7567
7614
  init_api();
7568
- init_permissions();
7615
+ var import_backstage_plugin_litellm_common6 = require("@acarmisc/backstage-plugin-litellm-common");
7569
7616
  //# sourceMappingURL=index.cjs.js.map