@acarmisc/backstage-plugin-litellm 0.15.5 → 0.17.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/api.d.ts +27 -1
- package/dist/components/ManageTeamDialog.d.ts +31 -0
- package/dist/components/TeamUsage.d.ts +2 -0
- package/dist/index.cjs.js +825 -252
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +794 -221
- package/dist/index.esm.js.map +4 -4
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.test.d.ts +1 -0
- package/dist/types.d.ts +49 -0
- package/package.json +5 -1
package/dist/index.esm.js
CHANGED
|
@@ -80,6 +80,24 @@ var init_api = __esm({
|
|
|
80
80
|
await this.throwIfNotOk(response);
|
|
81
81
|
return response.json();
|
|
82
82
|
}
|
|
83
|
+
async patch(path, body) {
|
|
84
|
+
const response = await this.fetchApi.fetch(`${this.basePath}${path}`, {
|
|
85
|
+
method: "PATCH",
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
body: JSON.stringify(body)
|
|
88
|
+
});
|
|
89
|
+
await this.throwIfNotOk(response);
|
|
90
|
+
return response.json();
|
|
91
|
+
}
|
|
92
|
+
async put(path, body) {
|
|
93
|
+
const response = await this.fetchApi.fetch(`${this.basePath}${path}`, {
|
|
94
|
+
method: "PUT",
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify(body)
|
|
97
|
+
});
|
|
98
|
+
await this.throwIfNotOk(response);
|
|
99
|
+
return response.json();
|
|
100
|
+
}
|
|
83
101
|
// User identity is resolved server-side from the Backstage Bearer token.
|
|
84
102
|
// No user_id param needed on the frontend.
|
|
85
103
|
async getUserInfo() {
|
|
@@ -136,6 +154,44 @@ var init_api = __esm({
|
|
|
136
154
|
async getTeams() {
|
|
137
155
|
return this.get("/teams");
|
|
138
156
|
}
|
|
157
|
+
// Teams whose owning_group the caller administers (server-scoped). Distinct
|
|
158
|
+
// from getTeams(), which returns only the teams the caller is a member of.
|
|
159
|
+
async getManagedTeams() {
|
|
160
|
+
return this.get("/teams/managed");
|
|
161
|
+
}
|
|
162
|
+
async addTeamMember(teamId, body) {
|
|
163
|
+
return this.post(
|
|
164
|
+
`/teams/${encodeURIComponent(teamId)}/members`,
|
|
165
|
+
body
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
async removeTeamMember(teamId, userEntityRef) {
|
|
169
|
+
return this.del(
|
|
170
|
+
`/teams/${encodeURIComponent(teamId)}/members?userEntityRef=${encodeURIComponent(
|
|
171
|
+
userEntityRef
|
|
172
|
+
)}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
// Vector stores (knowledge bases) the caller may attach — already filtered
|
|
176
|
+
// server-side to the operator's allowlist.
|
|
177
|
+
async getVectorStores() {
|
|
178
|
+
return this.get("/vector-stores");
|
|
179
|
+
}
|
|
180
|
+
async setTeamKnowledgeBases(teamId, vectorStores) {
|
|
181
|
+
return this.put(
|
|
182
|
+
`/teams/${encodeURIComponent(teamId)}/knowledge-bases`,
|
|
183
|
+
{ vector_stores: vectorStores }
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
async getMcpServers() {
|
|
187
|
+
return this.get("/mcp-servers");
|
|
188
|
+
}
|
|
189
|
+
async setTeamMcpServers(teamId, mcpServers) {
|
|
190
|
+
return this.put(
|
|
191
|
+
`/teams/${encodeURIComponent(teamId)}/mcp-servers`,
|
|
192
|
+
{ mcp_servers: mcpServers }
|
|
193
|
+
);
|
|
194
|
+
}
|
|
139
195
|
async getUsage(startDate, endDate) {
|
|
140
196
|
return this.get("/usage", { start_date: startDate, end_date: endDate });
|
|
141
197
|
}
|
|
@@ -148,6 +204,12 @@ var init_api = __esm({
|
|
|
148
204
|
async getConfig() {
|
|
149
205
|
return this.get("/config");
|
|
150
206
|
}
|
|
207
|
+
async createTeam(request) {
|
|
208
|
+
return this.post("/teams", request);
|
|
209
|
+
}
|
|
210
|
+
async updateTeam(teamId, request) {
|
|
211
|
+
return this.patch(`/teams/${encodeURIComponent(teamId)}`, request);
|
|
212
|
+
}
|
|
151
213
|
};
|
|
152
214
|
}
|
|
153
215
|
});
|
|
@@ -1857,13 +1919,368 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1857
1919
|
}
|
|
1858
1920
|
});
|
|
1859
1921
|
|
|
1860
|
-
// src/components/
|
|
1861
|
-
import React5, {
|
|
1862
|
-
import
|
|
1863
|
-
import
|
|
1864
|
-
import
|
|
1922
|
+
// src/components/ManageTeamDialog.tsx
|
|
1923
|
+
import React5, { useState as useState3, useEffect as useEffect2 } from "react";
|
|
1924
|
+
import Dialog3 from "@mui/material/Dialog";
|
|
1925
|
+
import DialogTitle3 from "@mui/material/DialogTitle";
|
|
1926
|
+
import DialogContent3 from "@mui/material/DialogContent";
|
|
1927
|
+
import DialogActions3 from "@mui/material/DialogActions";
|
|
1865
1928
|
import TextField3 from "@mui/material/TextField";
|
|
1929
|
+
import Button4 from "@mui/material/Button";
|
|
1930
|
+
import Alert2 from "@mui/material/Alert";
|
|
1931
|
+
import Checkbox2 from "@mui/material/Checkbox";
|
|
1932
|
+
import FormControlLabel2 from "@mui/material/FormControlLabel";
|
|
1933
|
+
import Autocomplete3 from "@mui/material/Autocomplete";
|
|
1866
1934
|
import MenuItem2 from "@mui/material/MenuItem";
|
|
1935
|
+
import Box5 from "@mui/material/Box";
|
|
1936
|
+
import Divider2 from "@mui/material/Divider";
|
|
1937
|
+
import Typography5 from "@mui/material/Typography";
|
|
1938
|
+
import IconButton3 from "@mui/material/IconButton";
|
|
1939
|
+
import List from "@mui/material/List";
|
|
1940
|
+
import ListItem from "@mui/material/ListItem";
|
|
1941
|
+
import ListItemText from "@mui/material/ListItemText";
|
|
1942
|
+
import { Delete as DeleteIcon } from "@mui/icons-material";
|
|
1943
|
+
var BUDGET_DURATION_OPTIONS, DEFAULT_BUDGET_DURATION, FALLBACK_BUDGET_CEILING, ManageTeamDialog;
|
|
1944
|
+
var init_ManageTeamDialog = __esm({
|
|
1945
|
+
"src/components/ManageTeamDialog.tsx"() {
|
|
1946
|
+
"use strict";
|
|
1947
|
+
BUDGET_DURATION_OPTIONS = [
|
|
1948
|
+
{ value: "1d", label: "Daily (1d)" },
|
|
1949
|
+
{ value: "7d", label: "Weekly (7d)" },
|
|
1950
|
+
{ value: "30d", label: "Monthly (30d)" },
|
|
1951
|
+
{ value: "90d", label: "Quarterly (90d)" },
|
|
1952
|
+
{ value: "365d", label: "Yearly (365d)" }
|
|
1953
|
+
];
|
|
1954
|
+
DEFAULT_BUDGET_DURATION = "30d";
|
|
1955
|
+
FALLBACK_BUDGET_CEILING = 1e3;
|
|
1956
|
+
ManageTeamDialog = ({
|
|
1957
|
+
open,
|
|
1958
|
+
onClose,
|
|
1959
|
+
mode,
|
|
1960
|
+
team,
|
|
1961
|
+
allModels,
|
|
1962
|
+
config,
|
|
1963
|
+
onSubmit,
|
|
1964
|
+
canManageMembers,
|
|
1965
|
+
onAddMember,
|
|
1966
|
+
onRemoveMember,
|
|
1967
|
+
canManageKnowledgeBases,
|
|
1968
|
+
vectorStores,
|
|
1969
|
+
onSaveKnowledgeBases,
|
|
1970
|
+
canManageMcpServers,
|
|
1971
|
+
mcpServers,
|
|
1972
|
+
onSaveMcpServers
|
|
1973
|
+
}) => {
|
|
1974
|
+
const [alias, setAlias] = useState3("");
|
|
1975
|
+
const [models, setModels] = useState3([]);
|
|
1976
|
+
const [maxBudget, setMaxBudget] = useState3("");
|
|
1977
|
+
const [unlimited, setUnlimited] = useState3(false);
|
|
1978
|
+
const [budgetDuration, setBudgetDuration] = useState3("");
|
|
1979
|
+
const [submitting, setSubmitting] = useState3(false);
|
|
1980
|
+
const [error, setError] = useState3(null);
|
|
1981
|
+
const [memberRef, setMemberRef] = useState3("");
|
|
1982
|
+
const [memberBudget, setMemberBudget] = useState3("");
|
|
1983
|
+
const [memberBusy, setMemberBusy] = useState3(false);
|
|
1984
|
+
const [memberError, setMemberError] = useState3(null);
|
|
1985
|
+
const [kbIds, setKbIds] = useState3([]);
|
|
1986
|
+
const [kbBusy, setKbBusy] = useState3(false);
|
|
1987
|
+
const [kbError, setKbError] = useState3(null);
|
|
1988
|
+
const [mcpIds, setMcpIds] = useState3([]);
|
|
1989
|
+
const [mcpBusy, setMcpBusy] = useState3(false);
|
|
1990
|
+
const [mcpError, setMcpError] = useState3(null);
|
|
1991
|
+
const allowUnlimitedBudget = config?.teamManagement?.allowUnlimitedBudget ?? false;
|
|
1992
|
+
const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
|
|
1993
|
+
useEffect2(() => {
|
|
1994
|
+
if (open) {
|
|
1995
|
+
if (mode === "edit" && team) {
|
|
1996
|
+
setAlias(team.team_alias ?? "");
|
|
1997
|
+
setModels(team.models ?? []);
|
|
1998
|
+
setMaxBudget(
|
|
1999
|
+
team.max_budget ? String(team.max_budget) : String(maxBudgetCeiling)
|
|
2000
|
+
);
|
|
2001
|
+
setUnlimited(allowUnlimitedBudget && !team.max_budget);
|
|
2002
|
+
setBudgetDuration(team.budget_duration ?? DEFAULT_BUDGET_DURATION);
|
|
2003
|
+
} else {
|
|
2004
|
+
setAlias("");
|
|
2005
|
+
setModels([]);
|
|
2006
|
+
setMaxBudget(String(maxBudgetCeiling));
|
|
2007
|
+
setUnlimited(allowUnlimitedBudget);
|
|
2008
|
+
setBudgetDuration(DEFAULT_BUDGET_DURATION);
|
|
2009
|
+
}
|
|
2010
|
+
setError(null);
|
|
2011
|
+
setMemberRef("");
|
|
2012
|
+
setMemberBudget("");
|
|
2013
|
+
setMemberError(null);
|
|
2014
|
+
setKbIds(team?.object_permission?.vector_stores ?? []);
|
|
2015
|
+
setKbError(null);
|
|
2016
|
+
setMcpIds(team?.object_permission?.mcp_servers ?? []);
|
|
2017
|
+
setMcpError(null);
|
|
2018
|
+
}
|
|
2019
|
+
}, [open, mode, team, allowUnlimitedBudget, maxBudgetCeiling]);
|
|
2020
|
+
const handleSubmit = async () => {
|
|
2021
|
+
setError(null);
|
|
2022
|
+
if (!alias.trim()) {
|
|
2023
|
+
setError("Team alias is required");
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
if (models.length === 0) {
|
|
2027
|
+
setError("At least one model is required");
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
if (!unlimited) {
|
|
2031
|
+
if (!maxBudget) {
|
|
2032
|
+
setError("Budget is required when unlimited budgets are disabled");
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const budget = parseFloat(maxBudget);
|
|
2036
|
+
if (isNaN(budget) || budget <= 0) {
|
|
2037
|
+
setError("Budget must be a positive number");
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
if (budget > maxBudgetCeiling) {
|
|
2041
|
+
setError(`Budget cannot exceed $${maxBudgetCeiling}`);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
try {
|
|
2046
|
+
setSubmitting(true);
|
|
2047
|
+
let payload;
|
|
2048
|
+
if (mode === "create") {
|
|
2049
|
+
payload = {
|
|
2050
|
+
team_alias: alias.trim(),
|
|
2051
|
+
models,
|
|
2052
|
+
...unlimited ? { max_budget: null } : { max_budget: parseFloat(maxBudget) },
|
|
2053
|
+
...budgetDuration && { budget_duration: budgetDuration }
|
|
2054
|
+
};
|
|
2055
|
+
} else {
|
|
2056
|
+
payload = {
|
|
2057
|
+
team_alias: alias.trim(),
|
|
2058
|
+
models,
|
|
2059
|
+
...unlimited ? { max_budget: null } : { max_budget: parseFloat(maxBudget) },
|
|
2060
|
+
...budgetDuration && { budget_duration: budgetDuration }
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
await onSubmit(payload);
|
|
2064
|
+
onClose();
|
|
2065
|
+
} catch (err) {
|
|
2066
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
2067
|
+
} finally {
|
|
2068
|
+
setSubmitting(false);
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
const handleAddMember = async () => {
|
|
2072
|
+
if (!onAddMember || !memberRef.trim()) return;
|
|
2073
|
+
setMemberError(null);
|
|
2074
|
+
const budget = memberBudget ? parseFloat(memberBudget) : void 0;
|
|
2075
|
+
if (budget !== void 0 && (isNaN(budget) || budget <= 0)) {
|
|
2076
|
+
setMemberError("Max budget in team must be a positive number");
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
try {
|
|
2080
|
+
setMemberBusy(true);
|
|
2081
|
+
await onAddMember(memberRef.trim(), budget);
|
|
2082
|
+
setMemberRef("");
|
|
2083
|
+
setMemberBudget("");
|
|
2084
|
+
} catch (err) {
|
|
2085
|
+
setMemberError(err instanceof Error ? err.message : "Failed to add member");
|
|
2086
|
+
} finally {
|
|
2087
|
+
setMemberBusy(false);
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
const handleRemoveMember = async (userId) => {
|
|
2091
|
+
if (!onRemoveMember) return;
|
|
2092
|
+
setMemberError(null);
|
|
2093
|
+
try {
|
|
2094
|
+
setMemberBusy(true);
|
|
2095
|
+
await onRemoveMember(userId);
|
|
2096
|
+
} catch (err) {
|
|
2097
|
+
setMemberError(err instanceof Error ? err.message : "Failed to remove member");
|
|
2098
|
+
} finally {
|
|
2099
|
+
setMemberBusy(false);
|
|
2100
|
+
}
|
|
2101
|
+
};
|
|
2102
|
+
const handleSaveKnowledgeBases = async () => {
|
|
2103
|
+
if (!onSaveKnowledgeBases) return;
|
|
2104
|
+
setKbError(null);
|
|
2105
|
+
try {
|
|
2106
|
+
setKbBusy(true);
|
|
2107
|
+
await onSaveKnowledgeBases(kbIds);
|
|
2108
|
+
} catch (err) {
|
|
2109
|
+
setKbError(err instanceof Error ? err.message : "Failed to save knowledge bases");
|
|
2110
|
+
} finally {
|
|
2111
|
+
setKbBusy(false);
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
const handleSaveMcpServers = async () => {
|
|
2115
|
+
if (!onSaveMcpServers) return;
|
|
2116
|
+
setMcpError(null);
|
|
2117
|
+
try {
|
|
2118
|
+
setMcpBusy(true);
|
|
2119
|
+
await onSaveMcpServers(mcpIds);
|
|
2120
|
+
} catch (err) {
|
|
2121
|
+
setMcpError(err instanceof Error ? err.message : "Failed to save MCP servers");
|
|
2122
|
+
} finally {
|
|
2123
|
+
setMcpBusy(false);
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
const modelNames = allModels.map((m) => m.model_name);
|
|
2127
|
+
const durationOptions = BUDGET_DURATION_OPTIONS.some((o) => o.value === budgetDuration) ? BUDGET_DURATION_OPTIONS : [...BUDGET_DURATION_OPTIONS, { value: budgetDuration, label: budgetDuration }];
|
|
2128
|
+
const members = team?.members_with_roles ?? [];
|
|
2129
|
+
const showMembers = mode === "edit" && !!canManageMembers;
|
|
2130
|
+
const showKnowledgeBases = mode === "edit" && !!canManageKnowledgeBases;
|
|
2131
|
+
const kbOptions = (vectorStores ?? []).map((v) => v.id);
|
|
2132
|
+
const kbLabel = (id) => (vectorStores ?? []).find((v) => v.id === id)?.name ?? id;
|
|
2133
|
+
const showMcpServers = mode === "edit" && !!canManageMcpServers;
|
|
2134
|
+
const mcpOptions = (mcpServers ?? []).map((s) => s.id);
|
|
2135
|
+
const mcpLabel = (id) => (mcpServers ?? []).find((s) => s.id === id)?.name ?? id;
|
|
2136
|
+
return /* @__PURE__ */ React5.createElement(Dialog3, { open, onClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React5.createElement(DialogTitle3, null, mode === "create" ? "Create Team" : "Edit Team"), /* @__PURE__ */ React5.createElement(DialogContent3, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 } }, error && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error" }, error), /* @__PURE__ */ React5.createElement(
|
|
2137
|
+
TextField3,
|
|
2138
|
+
{
|
|
2139
|
+
label: "Team Alias",
|
|
2140
|
+
value: alias,
|
|
2141
|
+
onChange: (e) => setAlias(e.target.value),
|
|
2142
|
+
disabled: submitting,
|
|
2143
|
+
fullWidth: true
|
|
2144
|
+
}
|
|
2145
|
+
), /* @__PURE__ */ React5.createElement(
|
|
2146
|
+
Autocomplete3,
|
|
2147
|
+
{
|
|
2148
|
+
multiple: true,
|
|
2149
|
+
options: modelNames,
|
|
2150
|
+
value: models,
|
|
2151
|
+
onChange: (_, newValue) => setModels(newValue),
|
|
2152
|
+
disabled: submitting,
|
|
2153
|
+
renderInput: (params) => /* @__PURE__ */ React5.createElement(TextField3, { ...params, label: "Models" })
|
|
2154
|
+
}
|
|
2155
|
+
), /* @__PURE__ */ React5.createElement(
|
|
2156
|
+
TextField3,
|
|
2157
|
+
{
|
|
2158
|
+
label: "Max Budget ($)",
|
|
2159
|
+
type: "number",
|
|
2160
|
+
value: maxBudget,
|
|
2161
|
+
onChange: (e) => setMaxBudget(e.target.value),
|
|
2162
|
+
disabled: submitting || unlimited,
|
|
2163
|
+
inputProps: { min: 0, max: maxBudgetCeiling },
|
|
2164
|
+
helperText: `Maximum: $${maxBudgetCeiling}`,
|
|
2165
|
+
fullWidth: true
|
|
2166
|
+
}
|
|
2167
|
+
), allowUnlimitedBudget && /* @__PURE__ */ React5.createElement(
|
|
2168
|
+
FormControlLabel2,
|
|
2169
|
+
{
|
|
2170
|
+
control: /* @__PURE__ */ React5.createElement(Checkbox2, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
|
|
2171
|
+
label: "Unlimited Budget"
|
|
2172
|
+
}
|
|
2173
|
+
), /* @__PURE__ */ React5.createElement(
|
|
2174
|
+
TextField3,
|
|
2175
|
+
{
|
|
2176
|
+
select: true,
|
|
2177
|
+
label: "Budget Duration",
|
|
2178
|
+
value: budgetDuration,
|
|
2179
|
+
onChange: (e) => setBudgetDuration(e.target.value),
|
|
2180
|
+
disabled: submitting || unlimited,
|
|
2181
|
+
helperText: "Spend-reset period for the team budget",
|
|
2182
|
+
fullWidth: true
|
|
2183
|
+
},
|
|
2184
|
+
durationOptions.map((o) => /* @__PURE__ */ React5.createElement(MenuItem2, { key: o.value, value: o.value }, o.label))
|
|
2185
|
+
), showMembers && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Members"), memberError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null) }, memberError), members.length === 0 ? /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary" }, "No members yet.") : /* @__PURE__ */ React5.createElement(List, { dense: true, disablePadding: true }, members.map((m) => /* @__PURE__ */ React5.createElement(
|
|
2186
|
+
ListItem,
|
|
2187
|
+
{
|
|
2188
|
+
key: m.user_id,
|
|
2189
|
+
disableGutters: true,
|
|
2190
|
+
secondaryAction: /* @__PURE__ */ React5.createElement(
|
|
2191
|
+
IconButton3,
|
|
2192
|
+
{
|
|
2193
|
+
edge: "end",
|
|
2194
|
+
size: "small",
|
|
2195
|
+
"aria-label": `remove ${m.user_id}`,
|
|
2196
|
+
disabled: memberBusy,
|
|
2197
|
+
onClick: () => handleRemoveMember(m.user_id)
|
|
2198
|
+
},
|
|
2199
|
+
/* @__PURE__ */ React5.createElement(DeleteIcon, { fontSize: "small" })
|
|
2200
|
+
)
|
|
2201
|
+
},
|
|
2202
|
+
/* @__PURE__ */ React5.createElement(ListItemText, { primary: m.user_id, secondary: m.role })
|
|
2203
|
+
))), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", gap: 1, alignItems: "flex-start", mt: 1 } }, /* @__PURE__ */ React5.createElement(
|
|
2204
|
+
TextField3,
|
|
2205
|
+
{
|
|
2206
|
+
label: "Backstage user ref",
|
|
2207
|
+
placeholder: "user:default/alice",
|
|
2208
|
+
value: memberRef,
|
|
2209
|
+
onChange: (e) => setMemberRef(e.target.value),
|
|
2210
|
+
disabled: memberBusy,
|
|
2211
|
+
size: "small",
|
|
2212
|
+
sx: { flex: 1 }
|
|
2213
|
+
}
|
|
2214
|
+
), /* @__PURE__ */ React5.createElement(
|
|
2215
|
+
TextField3,
|
|
2216
|
+
{
|
|
2217
|
+
label: "Max budget in team ($)",
|
|
2218
|
+
type: "number",
|
|
2219
|
+
value: memberBudget,
|
|
2220
|
+
onChange: (e) => setMemberBudget(e.target.value),
|
|
2221
|
+
disabled: memberBusy,
|
|
2222
|
+
size: "small",
|
|
2223
|
+
sx: { width: 160 }
|
|
2224
|
+
}
|
|
2225
|
+
), /* @__PURE__ */ React5.createElement(
|
|
2226
|
+
Button4,
|
|
2227
|
+
{
|
|
2228
|
+
onClick: handleAddMember,
|
|
2229
|
+
disabled: memberBusy || !memberRef.trim(),
|
|
2230
|
+
variant: "outlined",
|
|
2231
|
+
sx: { mt: 0.5 }
|
|
2232
|
+
},
|
|
2233
|
+
"Add"
|
|
2234
|
+
))), showKnowledgeBases && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Knowledge Bases"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching a knowledge base exposes its documents to every key in this team."), kbError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setKbError(null) }, kbError), /* @__PURE__ */ React5.createElement(
|
|
2235
|
+
Autocomplete3,
|
|
2236
|
+
{
|
|
2237
|
+
multiple: true,
|
|
2238
|
+
options: kbOptions,
|
|
2239
|
+
value: kbIds,
|
|
2240
|
+
getOptionLabel: kbLabel,
|
|
2241
|
+
onChange: (_, v) => setKbIds(v),
|
|
2242
|
+
disabled: kbBusy,
|
|
2243
|
+
renderInput: (params) => /* @__PURE__ */ React5.createElement(TextField3, { ...params, label: "Attached knowledge bases" })
|
|
2244
|
+
}
|
|
2245
|
+
), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ React5.createElement(
|
|
2246
|
+
Button4,
|
|
2247
|
+
{
|
|
2248
|
+
onClick: handleSaveKnowledgeBases,
|
|
2249
|
+
disabled: kbBusy,
|
|
2250
|
+
variant: "outlined"
|
|
2251
|
+
},
|
|
2252
|
+
kbBusy ? "Saving\u2026" : "Save knowledge bases"
|
|
2253
|
+
))), showMcpServers && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "MCP Servers"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "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."), mcpError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMcpError(null) }, mcpError), /* @__PURE__ */ React5.createElement(
|
|
2254
|
+
Autocomplete3,
|
|
2255
|
+
{
|
|
2256
|
+
multiple: true,
|
|
2257
|
+
options: mcpOptions,
|
|
2258
|
+
value: mcpIds,
|
|
2259
|
+
getOptionLabel: mcpLabel,
|
|
2260
|
+
onChange: (_, v) => setMcpIds(v),
|
|
2261
|
+
disabled: mcpBusy,
|
|
2262
|
+
renderInput: (params) => /* @__PURE__ */ React5.createElement(TextField3, { ...params, label: "Attached MCP servers" })
|
|
2263
|
+
}
|
|
2264
|
+
), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ React5.createElement(
|
|
2265
|
+
Button4,
|
|
2266
|
+
{
|
|
2267
|
+
onClick: handleSaveMcpServers,
|
|
2268
|
+
disabled: mcpBusy,
|
|
2269
|
+
variant: "outlined"
|
|
2270
|
+
},
|
|
2271
|
+
mcpBusy ? "Saving\u2026" : "Save MCP servers"
|
|
2272
|
+
)))), /* @__PURE__ */ React5.createElement(DialogActions3, null, /* @__PURE__ */ React5.createElement(Button4, { onClick: onClose, disabled: submitting }, "Cancel"), /* @__PURE__ */ React5.createElement(Button4, { onClick: handleSubmit, disabled: submitting, variant: "contained" }, submitting ? "Saving..." : "Save")));
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2276
|
+
|
|
2277
|
+
// src/components/UsageStats.tsx
|
|
2278
|
+
import React6, { useMemo as useMemo4, useState as useState4 } from "react";
|
|
2279
|
+
import Paper3 from "@mui/material/Paper";
|
|
2280
|
+
import Box6 from "@mui/material/Box";
|
|
2281
|
+
import Typography6 from "@mui/material/Typography";
|
|
2282
|
+
import TextField4 from "@mui/material/TextField";
|
|
2283
|
+
import MenuItem3 from "@mui/material/MenuItem";
|
|
1867
2284
|
import Grid from "@mui/material/Grid";
|
|
1868
2285
|
import Table2 from "@mui/material/Table";
|
|
1869
2286
|
import TableBody2 from "@mui/material/TableBody";
|
|
@@ -1908,17 +2325,17 @@ var init_UsageStats = __esm({
|
|
|
1908
2325
|
"30d": "Last 30 days"
|
|
1909
2326
|
};
|
|
1910
2327
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
1911
|
-
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */
|
|
2328
|
+
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height });
|
|
1912
2329
|
ChartOrFallback = ({ loading, empty, height = 240, children }) => {
|
|
1913
|
-
if (loading) return /* @__PURE__ */
|
|
1914
|
-
if (empty) return /* @__PURE__ */
|
|
1915
|
-
return /* @__PURE__ */
|
|
2330
|
+
if (loading) return /* @__PURE__ */ React6.createElement(ChartSkeleton, { height });
|
|
2331
|
+
if (empty) return /* @__PURE__ */ React6.createElement(EmptyState, { message: "No data for this period", height });
|
|
2332
|
+
return /* @__PURE__ */ React6.createElement(React6.Fragment, null, children);
|
|
1916
2333
|
};
|
|
1917
2334
|
SuccessRateCell = ({ rate, requests }) => {
|
|
1918
|
-
if (requests === 0) return /* @__PURE__ */
|
|
2335
|
+
if (requests === 0) return /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", color: "text.secondary" }, "\u2014");
|
|
1919
2336
|
const tone = rateTone(rate);
|
|
1920
|
-
return /* @__PURE__ */
|
|
1921
|
-
|
|
2337
|
+
return /* @__PURE__ */ React6.createElement(Box6, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React6.createElement(Box6, { width: 72, flexShrink: 0 }, /* @__PURE__ */ React6.createElement(Meter, { value: rate * 100, tone, height: 4 })), /* @__PURE__ */ React6.createElement(
|
|
2338
|
+
Typography6,
|
|
1922
2339
|
{
|
|
1923
2340
|
variant: "caption",
|
|
1924
2341
|
sx: { fontVariantNumeric: "tabular-nums", minWidth: 44 }
|
|
@@ -1936,8 +2353,8 @@ var init_UsageStats = __esm({
|
|
|
1936
2353
|
userInfo
|
|
1937
2354
|
}) => {
|
|
1938
2355
|
const chart = useChartTheme();
|
|
1939
|
-
const [selectedModel, setSelectedModel] =
|
|
1940
|
-
const [tab, setTab] =
|
|
2356
|
+
const [selectedModel, setSelectedModel] = useState4("all");
|
|
2357
|
+
const [tab, setTab] = useState4("costs");
|
|
1941
2358
|
const handlePresetChange = (preset) => {
|
|
1942
2359
|
const end = /* @__PURE__ */ new Date();
|
|
1943
2360
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -2066,24 +2483,24 @@ var init_UsageStats = __esm({
|
|
|
2066
2483
|
];
|
|
2067
2484
|
const renderKeyRows = () => {
|
|
2068
2485
|
if (loading) {
|
|
2069
|
-
return /* @__PURE__ */
|
|
2486
|
+
return /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height: 80 })));
|
|
2070
2487
|
}
|
|
2071
2488
|
if (keyRows.length === 0) {
|
|
2072
|
-
return /* @__PURE__ */
|
|
2489
|
+
return /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, { colSpan: 7 }, /* @__PURE__ */ React6.createElement(EmptyState, { message: "No key activity in this period" })));
|
|
2073
2490
|
}
|
|
2074
|
-
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */
|
|
2491
|
+
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React6.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ React6.createElement(Typography6, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ React6.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ React6.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
2075
2492
|
};
|
|
2076
2493
|
const renderModelRows = () => {
|
|
2077
2494
|
if (loading) {
|
|
2078
|
-
return /* @__PURE__ */
|
|
2495
|
+
return /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height: 80 })));
|
|
2079
2496
|
}
|
|
2080
2497
|
if (modelRows.length === 0) {
|
|
2081
|
-
return /* @__PURE__ */
|
|
2498
|
+
return /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, { colSpan: 8 }, /* @__PURE__ */ React6.createElement(EmptyState, { message: "No model activity in this period" })));
|
|
2082
2499
|
}
|
|
2083
|
-
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */
|
|
2500
|
+
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React6.createElement(TableRow2, { key: r.model }, /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React6.createElement(Box6, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
2084
2501
|
};
|
|
2085
|
-
const periodSelect = /* @__PURE__ */
|
|
2086
|
-
|
|
2502
|
+
const periodSelect = /* @__PURE__ */ React6.createElement(
|
|
2503
|
+
TextField4,
|
|
2087
2504
|
{
|
|
2088
2505
|
select: true,
|
|
2089
2506
|
size: "small",
|
|
@@ -2092,18 +2509,18 @@ var init_UsageStats = __esm({
|
|
|
2092
2509
|
onChange: (e) => handlePresetChange(e.target.value),
|
|
2093
2510
|
sx: { minWidth: 160 }
|
|
2094
2511
|
},
|
|
2095
|
-
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */
|
|
2512
|
+
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ React6.createElement(MenuItem3, { key: p, value: p }, PRESET_LABELS[p]))
|
|
2096
2513
|
);
|
|
2097
|
-
return /* @__PURE__ */
|
|
2514
|
+
return /* @__PURE__ */ React6.createElement(
|
|
2098
2515
|
SectionCard,
|
|
2099
2516
|
{
|
|
2100
2517
|
title: "Usage Analytics",
|
|
2101
2518
|
subtitle: `${PRESET_LABELS[currentPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
2102
2519
|
actions: periodSelect
|
|
2103
2520
|
},
|
|
2104
|
-
/* @__PURE__ */
|
|
2105
|
-
/* @__PURE__ */
|
|
2106
|
-
|
|
2521
|
+
/* @__PURE__ */ React6.createElement(MetricStrip, { metrics }),
|
|
2522
|
+
/* @__PURE__ */ React6.createElement(
|
|
2523
|
+
Box6,
|
|
2107
2524
|
{
|
|
2108
2525
|
sx: {
|
|
2109
2526
|
display: "flex",
|
|
@@ -2115,7 +2532,7 @@ var init_UsageStats = __esm({
|
|
|
2115
2532
|
mb: 2
|
|
2116
2533
|
}
|
|
2117
2534
|
},
|
|
2118
|
-
/* @__PURE__ */
|
|
2535
|
+
/* @__PURE__ */ React6.createElement(
|
|
2119
2536
|
SegmentedControl,
|
|
2120
2537
|
{
|
|
2121
2538
|
value: tab,
|
|
@@ -2127,8 +2544,8 @@ var init_UsageStats = __esm({
|
|
|
2127
2544
|
]
|
|
2128
2545
|
}
|
|
2129
2546
|
),
|
|
2130
|
-
tab === "models" && /* @__PURE__ */
|
|
2131
|
-
|
|
2547
|
+
tab === "models" && /* @__PURE__ */ React6.createElement(
|
|
2548
|
+
TextField4,
|
|
2132
2549
|
{
|
|
2133
2550
|
select: true,
|
|
2134
2551
|
size: "small",
|
|
@@ -2137,17 +2554,17 @@ var init_UsageStats = __esm({
|
|
|
2137
2554
|
onChange: (e) => setSelectedModel(e.target.value),
|
|
2138
2555
|
sx: { minWidth: 220 }
|
|
2139
2556
|
},
|
|
2140
|
-
/* @__PURE__ */
|
|
2141
|
-
models.map((m) => /* @__PURE__ */
|
|
2557
|
+
/* @__PURE__ */ React6.createElement(MenuItem3, { value: "all" }, "All models"),
|
|
2558
|
+
models.map((m) => /* @__PURE__ */ React6.createElement(MenuItem3, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
2142
2559
|
)
|
|
2143
2560
|
),
|
|
2144
|
-
tab === "costs" && /* @__PURE__ */
|
|
2561
|
+
tab === "costs" && /* @__PURE__ */ React6.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ React6.createElement(
|
|
2145
2562
|
Tooltip,
|
|
2146
2563
|
{
|
|
2147
2564
|
cursor: chart.cursor,
|
|
2148
|
-
content: /* @__PURE__ */
|
|
2565
|
+
content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
|
|
2149
2566
|
}
|
|
2150
|
-
), topSpendModels.map((m) => /* @__PURE__ */
|
|
2567
|
+
), topSpendModels.map((m) => /* @__PURE__ */ React6.createElement(
|
|
2151
2568
|
Area,
|
|
2152
2569
|
{
|
|
2153
2570
|
key: m,
|
|
@@ -2159,13 +2576,13 @@ var init_UsageStats = __esm({
|
|
|
2159
2576
|
fill: chartColor(m),
|
|
2160
2577
|
fillOpacity: 0.28
|
|
2161
2578
|
}
|
|
2162
|
-
))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */
|
|
2579
|
+
))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */ React6.createElement(SeriesLegend, { series: topSpendModels.map((m) => ({ name: m, color: chartColor(m) })) }))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily token usage" }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React6.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React6.createElement(Area, { type: "monotone", dataKey: "promptTokens", name: "Input", stackId: "tok", stroke: SERIES.input, strokeWidth: 1.5, fill: SERIES.input, fillOpacity: 0.25 }), /* @__PURE__ */ React6.createElement(Area, { type: "monotone", dataKey: "completionTokens", name: "Output", stackId: "tok", stroke: SERIES.output, strokeWidth: 1.5, fill: SERIES.output, fillOpacity: 0.25 })))))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily requests" }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barCategoryGap: "30%" }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React6.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: SERIES.success, stackId: "r" }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: SERIES.failure, stackId: "r", radius: [3, 3, 0, 0] })))))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily success rate" }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(LineChart, { data: successRateData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { domain: [0, 100], tickFormatter: (v) => `${v}%`, width: 44, ...chart.axis }), /* @__PURE__ */ React6.createElement(
|
|
2163
2580
|
Tooltip,
|
|
2164
2581
|
{
|
|
2165
2582
|
cursor: chart.cursor,
|
|
2166
|
-
content: /* @__PURE__ */
|
|
2583
|
+
content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
|
|
2167
2584
|
}
|
|
2168
|
-
), /* @__PURE__ */
|
|
2585
|
+
), /* @__PURE__ */ React6.createElement(ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ React6.createElement(
|
|
2169
2586
|
Line,
|
|
2170
2587
|
{
|
|
2171
2588
|
type: "monotone",
|
|
@@ -2176,13 +2593,13 @@ var init_UsageStats = __esm({
|
|
|
2176
2593
|
dot: { r: 2.5, strokeWidth: 0, fill: SERIES.input },
|
|
2177
2594
|
activeDot: { r: 4 }
|
|
2178
2595
|
}
|
|
2179
|
-
)))))), /* @__PURE__ */
|
|
2596
|
+
)))))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(
|
|
2180
2597
|
ChartCard,
|
|
2181
2598
|
{
|
|
2182
2599
|
title: maxBudget > 0 ? "Cumulative spend vs budget" : "Cumulative spend",
|
|
2183
2600
|
meta: maxBudget > 0 ? `${fmtUsd(totalCumSpend)} used \xB7 ${fmtUsd(Math.max(0, maxBudget - totalCumSpend))} left` : void 0
|
|
2184
2601
|
},
|
|
2185
|
-
/* @__PURE__ */
|
|
2602
|
+
/* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(AreaChart, { data: cumulativeData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(
|
|
2186
2603
|
YAxis,
|
|
2187
2604
|
{
|
|
2188
2605
|
tickFormatter: fmtUsdCompact,
|
|
@@ -2190,13 +2607,13 @@ var init_UsageStats = __esm({
|
|
|
2190
2607
|
domain: cumulativeDomain,
|
|
2191
2608
|
...chart.axis
|
|
2192
2609
|
}
|
|
2193
|
-
), /* @__PURE__ */
|
|
2610
|
+
), /* @__PURE__ */ React6.createElement(
|
|
2194
2611
|
Tooltip,
|
|
2195
2612
|
{
|
|
2196
2613
|
cursor: chart.cursor,
|
|
2197
|
-
content: /* @__PURE__ */
|
|
2614
|
+
content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd })
|
|
2198
2615
|
}
|
|
2199
|
-
), maxBudget > 0 && /* @__PURE__ */
|
|
2616
|
+
), maxBudget > 0 && /* @__PURE__ */ React6.createElement(
|
|
2200
2617
|
ReferenceLine,
|
|
2201
2618
|
{
|
|
2202
2619
|
y: maxBudget,
|
|
@@ -2204,7 +2621,7 @@ var init_UsageStats = __esm({
|
|
|
2204
2621
|
strokeDasharray: "5 4",
|
|
2205
2622
|
label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 10, fill: SERIES.budget }
|
|
2206
2623
|
}
|
|
2207
|
-
), /* @__PURE__ */
|
|
2624
|
+
), /* @__PURE__ */ React6.createElement(
|
|
2208
2625
|
Area,
|
|
2209
2626
|
{
|
|
2210
2627
|
type: "monotone",
|
|
@@ -2217,44 +2634,44 @@ var init_UsageStats = __esm({
|
|
|
2217
2634
|
}
|
|
2218
2635
|
))))
|
|
2219
2636
|
))),
|
|
2220
|
-
tab === "models" && /* @__PURE__ */
|
|
2637
|
+
tab === "models" && /* @__PURE__ */ React6.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Spend by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ React6.createElement(
|
|
2221
2638
|
ChartOrFallback,
|
|
2222
2639
|
{
|
|
2223
2640
|
loading,
|
|
2224
2641
|
empty: topModelSpendBars.length === 0,
|
|
2225
2642
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2226
2643
|
},
|
|
2227
|
-
/* @__PURE__ */
|
|
2228
|
-
))), /* @__PURE__ */
|
|
2644
|
+
/* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React6.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "spend", name: "Spend", radius: [0, 3, 3, 0], barSize: 14 }, topModelSpendBars.map((r) => /* @__PURE__ */ React6.createElement(Cell, { key: r.model, fill: chartColor(r.model) })))))
|
|
2645
|
+
))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Tokens by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ React6.createElement(
|
|
2229
2646
|
ChartOrFallback,
|
|
2230
2647
|
{
|
|
2231
2648
|
loading,
|
|
2232
2649
|
empty: modelRows.length === 0,
|
|
2233
2650
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2234
2651
|
},
|
|
2235
|
-
/* @__PURE__ */
|
|
2236
|
-
))), /* @__PURE__ */
|
|
2237
|
-
tab === "keys" && /* @__PURE__ */
|
|
2652
|
+
/* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React6.createElement(XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ React6.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
|
|
2653
|
+
))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React6.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead2, null, /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, null, "Model"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Success"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Input"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Output"), /* @__PURE__ */ React6.createElement(TableCell2, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React6.createElement(TableBody2, null, renderModelRows()))))),
|
|
2654
|
+
tab === "keys" && /* @__PURE__ */ React6.createElement(Grid, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ React6.createElement(
|
|
2238
2655
|
ChartOrFallback,
|
|
2239
2656
|
{
|
|
2240
2657
|
loading,
|
|
2241
2658
|
empty: topKeySpendBars.length === 0,
|
|
2242
2659
|
height: Math.max(160, topKeySpendBars.length * 32)
|
|
2243
2660
|
},
|
|
2244
|
-
/* @__PURE__ */
|
|
2245
|
-
))), /* @__PURE__ */
|
|
2661
|
+
/* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React6.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
|
|
2662
|
+
))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React6.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead2, null, /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React6.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React6.createElement(TableCell2, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React6.createElement(TableBody2, null, renderKeyRows())))))
|
|
2246
2663
|
);
|
|
2247
2664
|
};
|
|
2248
2665
|
}
|
|
2249
2666
|
});
|
|
2250
2667
|
|
|
2251
2668
|
// src/components/TeamUsage.tsx
|
|
2252
|
-
import
|
|
2669
|
+
import React7, { useState as useState5 } from "react";
|
|
2253
2670
|
import Paper4 from "@mui/material/Paper";
|
|
2254
|
-
import
|
|
2255
|
-
import
|
|
2671
|
+
import Box7 from "@mui/material/Box";
|
|
2672
|
+
import Typography7 from "@mui/material/Typography";
|
|
2256
2673
|
import Skeleton4 from "@mui/material/Skeleton";
|
|
2257
|
-
import
|
|
2674
|
+
import Divider3 from "@mui/material/Divider";
|
|
2258
2675
|
import Table3 from "@mui/material/Table";
|
|
2259
2676
|
import TableBody3 from "@mui/material/TableBody";
|
|
2260
2677
|
import TableCell3 from "@mui/material/TableCell";
|
|
@@ -2262,7 +2679,8 @@ import TableHead3 from "@mui/material/TableHead";
|
|
|
2262
2679
|
import TableRow3 from "@mui/material/TableRow";
|
|
2263
2680
|
import TableContainer3 from "@mui/material/TableContainer";
|
|
2264
2681
|
import Collapse from "@mui/material/Collapse";
|
|
2265
|
-
import
|
|
2682
|
+
import IconButton4 from "@mui/material/IconButton";
|
|
2683
|
+
import Button5 from "@mui/material/Button";
|
|
2266
2684
|
import CircularProgress3 from "@mui/material/CircularProgress";
|
|
2267
2685
|
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2268
2686
|
import { ExpandMore as ExpandMore2, Group } from "@mui/icons-material";
|
|
@@ -2286,8 +2704,8 @@ var init_TeamUsage = __esm({
|
|
|
2286
2704
|
"use strict";
|
|
2287
2705
|
init_ui();
|
|
2288
2706
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
2289
|
-
TeamCard = ({ team, usage, usageLoading }) => {
|
|
2290
|
-
const [expanded, setExpanded] =
|
|
2707
|
+
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
2708
|
+
const [expanded, setExpanded] = useState5(false);
|
|
2291
2709
|
const chart = useChartTheme();
|
|
2292
2710
|
const budget = team.max_budget ?? 0;
|
|
2293
2711
|
const spend = team.spend ?? 0;
|
|
@@ -2297,24 +2715,24 @@ var init_TeamUsage = __esm({
|
|
|
2297
2715
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2298
2716
|
const renderDailySpendSection = () => {
|
|
2299
2717
|
if (usageLoading) {
|
|
2300
|
-
return /* @__PURE__ */
|
|
2718
|
+
return /* @__PURE__ */ React7.createElement(Box7, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React7.createElement(CircularProgress3, { size: 22 }));
|
|
2301
2719
|
}
|
|
2302
2720
|
if (dailyData.length === 0) return null;
|
|
2303
|
-
return /* @__PURE__ */
|
|
2304
|
-
|
|
2721
|
+
return /* @__PURE__ */ React7.createElement(Box7, { mb: 2.5 }, /* @__PURE__ */ React7.createElement(
|
|
2722
|
+
Typography7,
|
|
2305
2723
|
{
|
|
2306
2724
|
variant: "caption",
|
|
2307
2725
|
color: "text.secondary",
|
|
2308
2726
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2309
2727
|
},
|
|
2310
2728
|
"Daily spend"
|
|
2311
|
-
), /* @__PURE__ */
|
|
2729
|
+
), /* @__PURE__ */ React7.createElement(Box7, { height: 150 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React7.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React7.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React7.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React7.createElement(
|
|
2312
2730
|
Tooltip2,
|
|
2313
2731
|
{
|
|
2314
2732
|
cursor: chart.cursor,
|
|
2315
|
-
content: /* @__PURE__ */
|
|
2733
|
+
content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
2316
2734
|
}
|
|
2317
|
-
), /* @__PURE__ */
|
|
2735
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2318
2736
|
Area2,
|
|
2319
2737
|
{
|
|
2320
2738
|
type: "monotone",
|
|
@@ -2327,8 +2745,8 @@ var init_TeamUsage = __esm({
|
|
|
2327
2745
|
}
|
|
2328
2746
|
)))));
|
|
2329
2747
|
};
|
|
2330
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
2331
|
-
|
|
2748
|
+
const sectionLabel = (label) => /* @__PURE__ */ React7.createElement(
|
|
2749
|
+
Typography7,
|
|
2332
2750
|
{
|
|
2333
2751
|
variant: "caption",
|
|
2334
2752
|
color: "text.secondary",
|
|
@@ -2336,8 +2754,8 @@ var init_TeamUsage = __esm({
|
|
|
2336
2754
|
},
|
|
2337
2755
|
label
|
|
2338
2756
|
);
|
|
2339
|
-
return /* @__PURE__ */
|
|
2340
|
-
|
|
2757
|
+
return /* @__PURE__ */ React7.createElement(Paper4, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React7.createElement(Box7, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React7.createElement(
|
|
2758
|
+
Box7,
|
|
2341
2759
|
{
|
|
2342
2760
|
sx: (theme) => ({
|
|
2343
2761
|
width: 36,
|
|
@@ -2351,17 +2769,26 @@ var init_TeamUsage = __esm({
|
|
|
2351
2769
|
color: theme.palette.primary.main
|
|
2352
2770
|
})
|
|
2353
2771
|
},
|
|
2354
|
-
/* @__PURE__ */
|
|
2355
|
-
), /* @__PURE__ */
|
|
2356
|
-
|
|
2772
|
+
/* @__PURE__ */ React7.createElement(Group, { fontSize: "small" })
|
|
2773
|
+
), /* @__PURE__ */ React7.createElement(Box7, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React7.createElement(
|
|
2774
|
+
Typography7,
|
|
2357
2775
|
{
|
|
2358
2776
|
variant: "caption",
|
|
2359
2777
|
color: "text.secondary",
|
|
2360
2778
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2361
2779
|
},
|
|
2362
2780
|
team.team_id
|
|
2363
|
-
)), isOver && /* @__PURE__ */
|
|
2364
|
-
|
|
2781
|
+
)), isOver && /* @__PURE__ */ React7.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React7.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ React7.createElement(
|
|
2782
|
+
Button5,
|
|
2783
|
+
{
|
|
2784
|
+
size: "small",
|
|
2785
|
+
variant: "text",
|
|
2786
|
+
onClick: () => onEditTeam(team),
|
|
2787
|
+
sx: { textTransform: "none" }
|
|
2788
|
+
},
|
|
2789
|
+
"Edit"
|
|
2790
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2791
|
+
IconButton4,
|
|
2365
2792
|
{
|
|
2366
2793
|
size: "small",
|
|
2367
2794
|
onClick: () => setExpanded((e) => !e),
|
|
@@ -2373,9 +2800,9 @@ var init_TeamUsage = __esm({
|
|
|
2373
2800
|
transition: theme.transitions.create("transform")
|
|
2374
2801
|
})
|
|
2375
2802
|
},
|
|
2376
|
-
/* @__PURE__ */
|
|
2377
|
-
)), /* @__PURE__ */
|
|
2378
|
-
|
|
2803
|
+
/* @__PURE__ */ React7.createElement(ExpandMore2, null)
|
|
2804
|
+
)), /* @__PURE__ */ React7.createElement(
|
|
2805
|
+
Box7,
|
|
2379
2806
|
{
|
|
2380
2807
|
sx: {
|
|
2381
2808
|
display: "grid",
|
|
@@ -2385,35 +2812,35 @@ var init_TeamUsage = __esm({
|
|
|
2385
2812
|
maxWidth: 640
|
|
2386
2813
|
}
|
|
2387
2814
|
},
|
|
2388
|
-
/* @__PURE__ */
|
|
2389
|
-
/* @__PURE__ */
|
|
2390
|
-
/* @__PURE__ */
|
|
2391
|
-
/* @__PURE__ */
|
|
2392
|
-
/* @__PURE__ */
|
|
2393
|
-
/* @__PURE__ */
|
|
2394
|
-
), budget > 0 && /* @__PURE__ */
|
|
2815
|
+
/* @__PURE__ */ React7.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
2816
|
+
/* @__PURE__ */ React7.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
2817
|
+
/* @__PURE__ */ React7.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
|
|
2818
|
+
/* @__PURE__ */ React7.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
2819
|
+
/* @__PURE__ */ React7.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
2820
|
+
/* @__PURE__ */ React7.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
2821
|
+
), budget > 0 && /* @__PURE__ */ React7.createElement(Box7, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React7.createElement(Box7, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React7.createElement(Meter, { value: budgetPct, tone: budgetTone2(isOver, isNear), height: 5 })), /* @__PURE__ */ React7.createElement(Collapse, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React7.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React7.createElement(Box7, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React7.createElement(Box7, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React7.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React7.createElement(Box7, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React7.createElement(
|
|
2395
2822
|
TableContainer3,
|
|
2396
2823
|
{
|
|
2397
2824
|
component: Paper4,
|
|
2398
2825
|
variant: "outlined",
|
|
2399
2826
|
sx: { borderRadius: 1.5 }
|
|
2400
2827
|
},
|
|
2401
|
-
/* @__PURE__ */
|
|
2402
|
-
|
|
2828
|
+
/* @__PURE__ */ React7.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead3, null, /* @__PURE__ */ React7.createElement(TableRow3, null, /* @__PURE__ */ React7.createElement(TableCell3, null, "User"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Role"))), /* @__PURE__ */ React7.createElement(TableBody3, null, team.members_with_roles.map((m) => /* @__PURE__ */ React7.createElement(TableRow3, { key: m.user_id }, /* @__PURE__ */ React7.createElement(TableCell3, null, m.user_email ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2" }, m.user_email), /* @__PURE__ */ React7.createElement(
|
|
2829
|
+
Typography7,
|
|
2403
2830
|
{
|
|
2404
2831
|
variant: "caption",
|
|
2405
2832
|
color: "text.secondary",
|
|
2406
2833
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2407
2834
|
},
|
|
2408
2835
|
m.user_id
|
|
2409
|
-
)) : /* @__PURE__ */
|
|
2410
|
-
|
|
2836
|
+
)) : /* @__PURE__ */ React7.createElement(
|
|
2837
|
+
Typography7,
|
|
2411
2838
|
{
|
|
2412
2839
|
variant: "body2",
|
|
2413
2840
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2414
2841
|
},
|
|
2415
2842
|
m.user_id
|
|
2416
|
-
)), /* @__PURE__ */
|
|
2843
|
+
)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, /* @__PURE__ */ React7.createElement(
|
|
2417
2844
|
StatusPill,
|
|
2418
2845
|
{
|
|
2419
2846
|
label: m.role,
|
|
@@ -2421,19 +2848,21 @@ var init_TeamUsage = __esm({
|
|
|
2421
2848
|
dot: false
|
|
2422
2849
|
}
|
|
2423
2850
|
))))))
|
|
2424
|
-
) : /* @__PURE__ */
|
|
2851
|
+
) : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
2425
2852
|
};
|
|
2426
2853
|
TeamUsage = ({
|
|
2427
2854
|
teams,
|
|
2428
2855
|
loading,
|
|
2429
2856
|
getTeamUsage,
|
|
2430
|
-
getTeamUsageLoading
|
|
2857
|
+
getTeamUsageLoading,
|
|
2858
|
+
canManage,
|
|
2859
|
+
onEditTeam
|
|
2431
2860
|
}) => {
|
|
2432
2861
|
if (loading) {
|
|
2433
|
-
return /* @__PURE__ */
|
|
2862
|
+
return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ React7.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React7.createElement(Skeleton4, { variant: "rounded", height: 120 }));
|
|
2434
2863
|
}
|
|
2435
2864
|
if (!teams.length) {
|
|
2436
|
-
return /* @__PURE__ */
|
|
2865
|
+
return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ React7.createElement(
|
|
2437
2866
|
EmptyState,
|
|
2438
2867
|
{
|
|
2439
2868
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2441,13 +2870,15 @@ var init_TeamUsage = __esm({
|
|
|
2441
2870
|
}
|
|
2442
2871
|
));
|
|
2443
2872
|
}
|
|
2444
|
-
return /* @__PURE__ */
|
|
2873
|
+
return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams", subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}` }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React7.createElement(
|
|
2445
2874
|
TeamCard,
|
|
2446
2875
|
{
|
|
2447
2876
|
key: team.team_id,
|
|
2448
2877
|
team,
|
|
2449
2878
|
usage: getTeamUsage(team.team_id),
|
|
2450
|
-
usageLoading: getTeamUsageLoading(team.team_id)
|
|
2879
|
+
usageLoading: getTeamUsageLoading(team.team_id),
|
|
2880
|
+
canManage,
|
|
2881
|
+
onEditTeam
|
|
2451
2882
|
}
|
|
2452
2883
|
))));
|
|
2453
2884
|
};
|
|
@@ -2455,23 +2886,23 @@ var init_TeamUsage = __esm({
|
|
|
2455
2886
|
});
|
|
2456
2887
|
|
|
2457
2888
|
// src/components/ModelsTable.tsx
|
|
2458
|
-
import
|
|
2459
|
-
import
|
|
2889
|
+
import React8, { useState as useState6, useMemo as useMemo5, useCallback } from "react";
|
|
2890
|
+
import Box8 from "@mui/material/Box";
|
|
2460
2891
|
import Table4 from "@mui/material/Table";
|
|
2461
2892
|
import TableBody4 from "@mui/material/TableBody";
|
|
2462
2893
|
import TableCell4 from "@mui/material/TableCell";
|
|
2463
2894
|
import TableContainer4 from "@mui/material/TableContainer";
|
|
2464
2895
|
import TableHead4 from "@mui/material/TableHead";
|
|
2465
2896
|
import TableRow4 from "@mui/material/TableRow";
|
|
2466
|
-
import
|
|
2467
|
-
import
|
|
2897
|
+
import Typography8 from "@mui/material/Typography";
|
|
2898
|
+
import MenuItem4 from "@mui/material/MenuItem";
|
|
2468
2899
|
import Select from "@mui/material/Select";
|
|
2469
2900
|
import FormControl from "@mui/material/FormControl";
|
|
2470
2901
|
import InputLabel from "@mui/material/InputLabel";
|
|
2471
2902
|
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
|
2472
2903
|
import Tooltip3 from "@mui/material/Tooltip";
|
|
2473
2904
|
import Snackbar from "@mui/material/Snackbar";
|
|
2474
|
-
import
|
|
2905
|
+
import Alert3 from "@mui/material/Alert";
|
|
2475
2906
|
import { alpha as alpha5, useTheme as useTheme2 } from "@mui/material/styles";
|
|
2476
2907
|
function isModelAllowedByTeam2(model, teamModels) {
|
|
2477
2908
|
if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
|
|
@@ -2498,8 +2929,8 @@ var init_ModelsTable = __esm({
|
|
|
2498
2929
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
2499
2930
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
2500
2931
|
const theme = useTheme2();
|
|
2501
|
-
const [selectedTeamId, setSelectedTeamId] =
|
|
2502
|
-
const [copiedSnackbar, setCopiedSnackbar] =
|
|
2932
|
+
const [selectedTeamId, setSelectedTeamId] = useState6("");
|
|
2933
|
+
const [copiedSnackbar, setCopiedSnackbar] = useState6(false);
|
|
2503
2934
|
const selectedTeam = useMemo5(
|
|
2504
2935
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
2505
2936
|
[teams, selectedTeamId]
|
|
@@ -2525,13 +2956,13 @@ var init_ModelsTable = __esm({
|
|
|
2525
2956
|
return theme.palette.info.main;
|
|
2526
2957
|
}
|
|
2527
2958
|
};
|
|
2528
|
-
return /* @__PURE__ */
|
|
2959
|
+
return /* @__PURE__ */ React8.createElement(
|
|
2529
2960
|
SectionCard,
|
|
2530
2961
|
{
|
|
2531
2962
|
title: "Available Models",
|
|
2532
2963
|
subtitle: selectedTeam ? `${filteredModels.length} model${filteredModels.length !== 1 ? "s" : ""} in ${selectedTeam.team_alias ?? selectedTeam.team_id}` : "Select a team to view its models"
|
|
2533
2964
|
},
|
|
2534
|
-
/* @__PURE__ */
|
|
2965
|
+
/* @__PURE__ */ React8.createElement(Box8, { sx: { px: 2.5, pb: 2.5, pt: 1 } }, /* @__PURE__ */ React8.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React8.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React8.createElement(
|
|
2535
2966
|
Select,
|
|
2536
2967
|
{
|
|
2537
2968
|
labelId: "team-select-label",
|
|
@@ -2539,25 +2970,25 @@ var init_ModelsTable = __esm({
|
|
|
2539
2970
|
label: "Team",
|
|
2540
2971
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
2541
2972
|
},
|
|
2542
|
-
teams.map((t) => /* @__PURE__ */
|
|
2973
|
+
teams.map((t) => /* @__PURE__ */ React8.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
2543
2974
|
))),
|
|
2544
|
-
loading && /* @__PURE__ */
|
|
2545
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
2975
|
+
loading && /* @__PURE__ */ React8.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
2976
|
+
!loading && !selectedTeamId && /* @__PURE__ */ React8.createElement(
|
|
2546
2977
|
EmptyState,
|
|
2547
2978
|
{
|
|
2548
2979
|
message: "Select a team to see available models",
|
|
2549
2980
|
hint: "Each team may have a different set of models assigned"
|
|
2550
2981
|
}
|
|
2551
2982
|
),
|
|
2552
|
-
!loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */
|
|
2983
|
+
!loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */ React8.createElement(
|
|
2553
2984
|
EmptyState,
|
|
2554
2985
|
{
|
|
2555
2986
|
message: "No models available for this team",
|
|
2556
2987
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
2557
2988
|
}
|
|
2558
2989
|
),
|
|
2559
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
2560
|
-
|
|
2990
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ React8.createElement(TableContainer4, null, /* @__PURE__ */ React8.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React8.createElement(TableHead4, null, /* @__PURE__ */ React8.createElement(TableRow4, null, /* @__PURE__ */ React8.createElement(TableCell4, null, "Model ID"), /* @__PURE__ */ React8.createElement(TableCell4, null, "Mode"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Input Cost"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Output Cost"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Max Input"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Max Output"), /* @__PURE__ */ React8.createElement(TableCell4, null, "Capabilities"))), /* @__PURE__ */ React8.createElement(TableBody4, null, filteredModels.map((m) => /* @__PURE__ */ React8.createElement(TableRow4, { key: m.model_name, hover: true }, /* @__PURE__ */ React8.createElement(TableCell4, null, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React8.createElement(TagChip, { label: m.model_name, title: m.model_name }), /* @__PURE__ */ React8.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React8.createElement(
|
|
2991
|
+
Box8,
|
|
2561
2992
|
{
|
|
2562
2993
|
component: "span",
|
|
2563
2994
|
role: "button",
|
|
@@ -2567,9 +2998,9 @@ var init_ModelsTable = __esm({
|
|
|
2567
2998
|
sx: quietIconButtonSx("accent"),
|
|
2568
2999
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
2569
3000
|
},
|
|
2570
|
-
/* @__PURE__ */
|
|
2571
|
-
)))), /* @__PURE__ */
|
|
2572
|
-
|
|
3001
|
+
/* @__PURE__ */ React8.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
|
|
3002
|
+
)))), /* @__PURE__ */ React8.createElement(TableCell4, null, /* @__PURE__ */ React8.createElement(
|
|
3003
|
+
Box8,
|
|
2573
3004
|
{
|
|
2574
3005
|
component: "span",
|
|
2575
3006
|
sx: {
|
|
@@ -2585,8 +3016,8 @@ var init_ModelsTable = __esm({
|
|
|
2585
3016
|
}
|
|
2586
3017
|
},
|
|
2587
3018
|
m.mode
|
|
2588
|
-
)), /* @__PURE__ */
|
|
2589
|
-
/* @__PURE__ */
|
|
3019
|
+
)), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React8.createElement(TableCell4, null, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React8.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React8.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
3020
|
+
/* @__PURE__ */ React8.createElement(
|
|
2590
3021
|
Snackbar,
|
|
2591
3022
|
{
|
|
2592
3023
|
open: copiedSnackbar,
|
|
@@ -2594,7 +3025,7 @@ var init_ModelsTable = __esm({
|
|
|
2594
3025
|
onClose: () => setCopiedSnackbar(false),
|
|
2595
3026
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
2596
3027
|
},
|
|
2597
|
-
/* @__PURE__ */
|
|
3028
|
+
/* @__PURE__ */ React8.createElement(Alert3, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
2598
3029
|
)
|
|
2599
3030
|
);
|
|
2600
3031
|
};
|
|
@@ -2602,9 +3033,9 @@ var init_ModelsTable = __esm({
|
|
|
2602
3033
|
});
|
|
2603
3034
|
|
|
2604
3035
|
// src/components/AuditLog.tsx
|
|
2605
|
-
import
|
|
2606
|
-
import
|
|
2607
|
-
import
|
|
3036
|
+
import React9, { useState as useState7, useCallback as useCallback2 } from "react";
|
|
3037
|
+
import Box9 from "@mui/material/Box";
|
|
3038
|
+
import Typography9 from "@mui/material/Typography";
|
|
2608
3039
|
import Table5 from "@mui/material/Table";
|
|
2609
3040
|
import TableBody5 from "@mui/material/TableBody";
|
|
2610
3041
|
import TableCell5 from "@mui/material/TableCell";
|
|
@@ -2612,12 +3043,12 @@ import TableContainer5 from "@mui/material/TableContainer";
|
|
|
2612
3043
|
import TableHead5 from "@mui/material/TableHead";
|
|
2613
3044
|
import TableRow5 from "@mui/material/TableRow";
|
|
2614
3045
|
import TablePagination from "@mui/material/TablePagination";
|
|
2615
|
-
import
|
|
2616
|
-
import
|
|
3046
|
+
import TextField5 from "@mui/material/TextField";
|
|
3047
|
+
import MenuItem5 from "@mui/material/MenuItem";
|
|
2617
3048
|
import Skeleton5 from "@mui/material/Skeleton";
|
|
2618
3049
|
import Collapse2 from "@mui/material/Collapse";
|
|
2619
|
-
import
|
|
2620
|
-
import
|
|
3050
|
+
import IconButton5 from "@mui/material/IconButton";
|
|
3051
|
+
import Alert4 from "@mui/material/Alert";
|
|
2621
3052
|
import { alpha as alpha6 } from "@mui/material/styles";
|
|
2622
3053
|
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
2623
3054
|
import { useAsync } from "react-use";
|
|
@@ -2641,10 +3072,10 @@ function formatDateTime(iso) {
|
|
|
2641
3072
|
}
|
|
2642
3073
|
function renderAuditLogBody(loading, entries) {
|
|
2643
3074
|
if (loading) {
|
|
2644
|
-
return /* @__PURE__ */
|
|
3075
|
+
return /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React9.createElement(Skeleton5, { variant: "rounded", height: 160 })));
|
|
2645
3076
|
}
|
|
2646
3077
|
if (entries.length === 0) {
|
|
2647
|
-
return /* @__PURE__ */
|
|
3078
|
+
return /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { colSpan: 6 }, /* @__PURE__ */ React9.createElement(
|
|
2648
3079
|
EmptyState,
|
|
2649
3080
|
{
|
|
2650
3081
|
message: "No audit events found",
|
|
@@ -2652,7 +3083,7 @@ function renderAuditLogBody(loading, entries) {
|
|
|
2652
3083
|
}
|
|
2653
3084
|
)));
|
|
2654
3085
|
}
|
|
2655
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3086
|
+
return entries.map((entry) => /* @__PURE__ */ React9.createElement(DetailRow, { key: entry.id, entry }));
|
|
2656
3087
|
}
|
|
2657
3088
|
var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
2658
3089
|
var init_AuditLog = __esm({
|
|
@@ -2669,13 +3100,15 @@ var init_AuditLog = __esm({
|
|
|
2669
3100
|
TABLE_LABELS = {
|
|
2670
3101
|
LiteLLM_VerificationToken: "Key",
|
|
2671
3102
|
LiteLLM_TeamTable: "Team",
|
|
3103
|
+
LiteLLM_TeamMembership: "Team member",
|
|
3104
|
+
LiteLLM_ObjectPermissionTable: "Team access (KB / MCP)",
|
|
2672
3105
|
LiteLLM_UserTable: "User"
|
|
2673
3106
|
};
|
|
2674
3107
|
DetailRow = ({ entry }) => {
|
|
2675
|
-
const [open, setOpen] =
|
|
3108
|
+
const [open, setOpen] = useState7(false);
|
|
2676
3109
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
2677
|
-
return /* @__PURE__ */
|
|
2678
|
-
|
|
3110
|
+
return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React9.createElement(
|
|
3111
|
+
IconButton5,
|
|
2679
3112
|
{
|
|
2680
3113
|
size: "small",
|
|
2681
3114
|
onClick: () => setOpen((o) => !o),
|
|
@@ -2687,9 +3120,9 @@ var init_AuditLog = __esm({
|
|
|
2687
3120
|
transition: theme.transitions.create("transform")
|
|
2688
3121
|
})
|
|
2689
3122
|
},
|
|
2690
|
-
/* @__PURE__ */
|
|
2691
|
-
)), /* @__PURE__ */
|
|
2692
|
-
|
|
3123
|
+
/* @__PURE__ */ React9.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
3124
|
+
)), /* @__PURE__ */ React9.createElement(TableCell5, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React9.createElement(TableCell5, null, entry.action && /* @__PURE__ */ React9.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(
|
|
3125
|
+
Typography9,
|
|
2693
3126
|
{
|
|
2694
3127
|
variant: "body2",
|
|
2695
3128
|
component: "code",
|
|
@@ -2698,8 +3131,8 @@ var init_AuditLog = __esm({
|
|
|
2698
3131
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
2699
3132
|
},
|
|
2700
3133
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
2701
|
-
)), /* @__PURE__ */
|
|
2702
|
-
|
|
3134
|
+
)), /* @__PURE__ */ React9.createElement(TableCell5, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React9.createElement(TableRow5, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React9.createElement(TableCell5, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React9.createElement(Collapse2, { in: open, unmountOnExit: true }, /* @__PURE__ */ React9.createElement(
|
|
3135
|
+
Box9,
|
|
2703
3136
|
{
|
|
2704
3137
|
display: "flex",
|
|
2705
3138
|
gap: 2,
|
|
@@ -2711,8 +3144,8 @@ var init_AuditLog = __esm({
|
|
|
2711
3144
|
bgcolor: alpha6(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
2712
3145
|
})
|
|
2713
3146
|
},
|
|
2714
|
-
entry.before_value && /* @__PURE__ */
|
|
2715
|
-
|
|
3147
|
+
entry.before_value && /* @__PURE__ */ React9.createElement(Box9, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React9.createElement(
|
|
3148
|
+
Typography9,
|
|
2716
3149
|
{
|
|
2717
3150
|
variant: "caption",
|
|
2718
3151
|
color: "text.secondary",
|
|
@@ -2721,9 +3154,9 @@ var init_AuditLog = __esm({
|
|
|
2721
3154
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2722
3155
|
},
|
|
2723
3156
|
"Before"
|
|
2724
|
-
), /* @__PURE__ */
|
|
2725
|
-
entry.updated_values && /* @__PURE__ */
|
|
2726
|
-
|
|
3157
|
+
), /* @__PURE__ */ React9.createElement(Typography9, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.before_value, null, 2))),
|
|
3158
|
+
entry.updated_values && /* @__PURE__ */ React9.createElement(Box9, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React9.createElement(
|
|
3159
|
+
Typography9,
|
|
2727
3160
|
{
|
|
2728
3161
|
variant: "caption",
|
|
2729
3162
|
color: "text.secondary",
|
|
@@ -2732,13 +3165,13 @@ var init_AuditLog = __esm({
|
|
|
2732
3165
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2733
3166
|
},
|
|
2734
3167
|
"After"
|
|
2735
|
-
), /* @__PURE__ */
|
|
3168
|
+
), /* @__PURE__ */ React9.createElement(Typography9, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.updated_values, null, 2)))
|
|
2736
3169
|
)))));
|
|
2737
3170
|
};
|
|
2738
3171
|
AuditLog = ({ api }) => {
|
|
2739
|
-
const [page, setPage] =
|
|
2740
|
-
const [pageSize, setPageSize] =
|
|
2741
|
-
const [filters, setFilters] =
|
|
3172
|
+
const [page, setPage] = useState7(0);
|
|
3173
|
+
const [pageSize, setPageSize] = useState7(25);
|
|
3174
|
+
const [filters, setFilters] = useState7({});
|
|
2742
3175
|
const fetchParams = useCallback2(
|
|
2743
3176
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
2744
3177
|
[page, pageSize, filters]
|
|
@@ -2749,14 +3182,14 @@ var init_AuditLog = __esm({
|
|
|
2749
3182
|
);
|
|
2750
3183
|
const entries = value?.audit_logs ?? [];
|
|
2751
3184
|
const total = value?.total ?? 0;
|
|
2752
|
-
return /* @__PURE__ */
|
|
3185
|
+
return /* @__PURE__ */ React9.createElement(
|
|
2753
3186
|
SectionCard,
|
|
2754
3187
|
{
|
|
2755
3188
|
title: "Audit Log",
|
|
2756
3189
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
2757
3190
|
flush: true,
|
|
2758
|
-
actions: /* @__PURE__ */
|
|
2759
|
-
|
|
3191
|
+
actions: /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(
|
|
3192
|
+
TextField5,
|
|
2760
3193
|
{
|
|
2761
3194
|
size: "small",
|
|
2762
3195
|
label: "Action",
|
|
@@ -2768,13 +3201,13 @@ var init_AuditLog = __esm({
|
|
|
2768
3201
|
},
|
|
2769
3202
|
sx: { minWidth: 150 }
|
|
2770
3203
|
},
|
|
2771
|
-
/* @__PURE__ */
|
|
2772
|
-
/* @__PURE__ */
|
|
2773
|
-
/* @__PURE__ */
|
|
2774
|
-
/* @__PURE__ */
|
|
2775
|
-
/* @__PURE__ */
|
|
2776
|
-
), /* @__PURE__ */
|
|
2777
|
-
|
|
3204
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "" }, "All actions"),
|
|
3205
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "created" }, "Created"),
|
|
3206
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "updated" }, "Updated"),
|
|
3207
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
|
|
3208
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "blocked" }, "Blocked")
|
|
3209
|
+
), /* @__PURE__ */ React9.createElement(
|
|
3210
|
+
TextField5,
|
|
2778
3211
|
{
|
|
2779
3212
|
size: "small",
|
|
2780
3213
|
label: "Table",
|
|
@@ -2786,12 +3219,14 @@ var init_AuditLog = __esm({
|
|
|
2786
3219
|
},
|
|
2787
3220
|
sx: { minWidth: 150 }
|
|
2788
3221
|
},
|
|
2789
|
-
/* @__PURE__ */
|
|
2790
|
-
/* @__PURE__ */
|
|
2791
|
-
/* @__PURE__ */
|
|
2792
|
-
/* @__PURE__ */
|
|
2793
|
-
|
|
2794
|
-
|
|
3222
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "" }, "All tables"),
|
|
3223
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3224
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3225
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3226
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3227
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
|
|
3228
|
+
), /* @__PURE__ */ React9.createElement(
|
|
3229
|
+
TextField5,
|
|
2795
3230
|
{
|
|
2796
3231
|
size: "small",
|
|
2797
3232
|
label: "Changed by",
|
|
@@ -2804,9 +3239,9 @@ var init_AuditLog = __esm({
|
|
|
2804
3239
|
}
|
|
2805
3240
|
))
|
|
2806
3241
|
},
|
|
2807
|
-
error && /* @__PURE__ */
|
|
2808
|
-
/* @__PURE__ */
|
|
2809
|
-
/* @__PURE__ */
|
|
3242
|
+
error && /* @__PURE__ */ React9.createElement(Box9, { px: 2.5, pb: 2 }, /* @__PURE__ */ React9.createElement(Alert4, { severity: "error" }, error.message)),
|
|
3243
|
+
/* @__PURE__ */ React9.createElement(TableContainer5, null, /* @__PURE__ */ React9.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead5, null, /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { sx: { width: 40 } }), /* @__PURE__ */ React9.createElement(TableCell5, null, "Time"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Action"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Table"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Object ID"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Changed By"))), /* @__PURE__ */ React9.createElement(TableBody5, null, renderAuditLogBody(loading, entries)))),
|
|
3244
|
+
/* @__PURE__ */ React9.createElement(
|
|
2810
3245
|
TablePagination,
|
|
2811
3246
|
{
|
|
2812
3247
|
component: "div",
|
|
@@ -2827,22 +3262,53 @@ var init_AuditLog = __esm({
|
|
|
2827
3262
|
}
|
|
2828
3263
|
});
|
|
2829
3264
|
|
|
3265
|
+
// src/permissions.ts
|
|
3266
|
+
import { createPermission } from "@backstage/plugin-permission-common";
|
|
3267
|
+
var litellmTeamCreatePermission, litellmTeamManagePermission, litellmTeamMembersManagePermission, litellmTeamKnowledgebaseManagePermission, litellmTeamMcpManagePermission;
|
|
3268
|
+
var init_permissions = __esm({
|
|
3269
|
+
"src/permissions.ts"() {
|
|
3270
|
+
"use strict";
|
|
3271
|
+
litellmTeamCreatePermission = createPermission({
|
|
3272
|
+
name: "litellm.team.create",
|
|
3273
|
+
attributes: { action: "create" }
|
|
3274
|
+
});
|
|
3275
|
+
litellmTeamManagePermission = createPermission({
|
|
3276
|
+
name: "litellm.team.manage",
|
|
3277
|
+
attributes: { action: "update" }
|
|
3278
|
+
});
|
|
3279
|
+
litellmTeamMembersManagePermission = createPermission({
|
|
3280
|
+
name: "litellm.team.members.manage",
|
|
3281
|
+
attributes: { action: "update" }
|
|
3282
|
+
});
|
|
3283
|
+
litellmTeamKnowledgebaseManagePermission = createPermission({
|
|
3284
|
+
name: "litellm.team.knowledgebase.manage",
|
|
3285
|
+
attributes: { action: "update" }
|
|
3286
|
+
});
|
|
3287
|
+
litellmTeamMcpManagePermission = createPermission({
|
|
3288
|
+
name: "litellm.team.mcp.manage",
|
|
3289
|
+
attributes: { action: "update" }
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
|
|
2830
3294
|
// src/components/LiteLLMPage.tsx
|
|
2831
3295
|
var LiteLLMPage_exports = {};
|
|
2832
3296
|
__export(LiteLLMPage_exports, {
|
|
2833
3297
|
LiteLLMPage: () => LiteLLMPage
|
|
2834
3298
|
});
|
|
2835
|
-
import
|
|
2836
|
-
import
|
|
3299
|
+
import React10, { useState as useState8, useCallback as useCallback3, useMemo as useMemo6 } from "react";
|
|
3300
|
+
import Box10 from "@mui/material/Box";
|
|
2837
3301
|
import Snackbar2 from "@mui/material/Snackbar";
|
|
2838
|
-
import
|
|
3302
|
+
import Alert5 from "@mui/material/Alert";
|
|
2839
3303
|
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
2840
|
-
import
|
|
3304
|
+
import Typography10 from "@mui/material/Typography";
|
|
2841
3305
|
import Paper5 from "@mui/material/Paper";
|
|
2842
3306
|
import Tabs2 from "@mui/material/Tabs";
|
|
2843
3307
|
import Tab2 from "@mui/material/Tab";
|
|
3308
|
+
import Button6 from "@mui/material/Button";
|
|
2844
3309
|
import { useAsync as useAsync2, useAsyncRetry } from "react-use";
|
|
2845
3310
|
import { useApi } from "@backstage/core-plugin-api";
|
|
3311
|
+
import { usePermission } from "@backstage/plugin-permission-react";
|
|
2846
3312
|
function initDateRange() {
|
|
2847
3313
|
let preset = "7d";
|
|
2848
3314
|
try {
|
|
@@ -2864,27 +3330,30 @@ var init_LiteLLMPage = __esm({
|
|
|
2864
3330
|
init_DashboardHeader();
|
|
2865
3331
|
init_KeysTable();
|
|
2866
3332
|
init_GenerateKeyDialog();
|
|
3333
|
+
init_ManageTeamDialog();
|
|
2867
3334
|
init_UsageStats();
|
|
2868
3335
|
init_TeamUsage();
|
|
2869
3336
|
init_ModelsTable();
|
|
2870
3337
|
init_AuditLog();
|
|
2871
3338
|
init_api();
|
|
3339
|
+
init_permissions();
|
|
2872
3340
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
2873
3341
|
LiteLLMPage = () => {
|
|
2874
3342
|
const api = useApi(liteLlmApiRef);
|
|
2875
|
-
const [dateRange, setDateRange] =
|
|
2876
|
-
const [currentPreset, setCurrentPreset] =
|
|
3343
|
+
const [dateRange, setDateRange] = useState8(initDateRange);
|
|
3344
|
+
const [currentPreset, setCurrentPreset] = useState8(() => {
|
|
2877
3345
|
try {
|
|
2878
3346
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
2879
3347
|
} catch {
|
|
2880
3348
|
return "7d";
|
|
2881
3349
|
}
|
|
2882
3350
|
});
|
|
2883
|
-
const [activeTab, setActiveTab] =
|
|
2884
|
-
const [snackbar, setSnackbar] =
|
|
2885
|
-
const [generateDialogOpen, setGenerateDialogOpen] =
|
|
2886
|
-
const [
|
|
2887
|
-
const [
|
|
3351
|
+
const [activeTab, setActiveTab] = useState8("overview");
|
|
3352
|
+
const [snackbar, setSnackbar] = useState8(null);
|
|
3353
|
+
const [generateDialogOpen, setGenerateDialogOpen] = useState8(false);
|
|
3354
|
+
const [manageTeam, setManageTeam] = useState8(null);
|
|
3355
|
+
const [teamUsageCache, setTeamUsageCache] = useState8({});
|
|
3356
|
+
const [teamUsageLoading, setTeamUsageLoading] = useState8({});
|
|
2888
3357
|
const { value: userInfo, loading: userLoading, error: userError } = useAsync2(
|
|
2889
3358
|
() => api.getUserInfo(),
|
|
2890
3359
|
[api]
|
|
@@ -2904,11 +3373,30 @@ var init_LiteLLMPage = __esm({
|
|
|
2904
3373
|
() => api.listModels().catch(() => []),
|
|
2905
3374
|
[api]
|
|
2906
3375
|
);
|
|
2907
|
-
const { value: allTeams, loading: teamsLoading } =
|
|
3376
|
+
const { value: allTeams, loading: teamsLoading, retry: refreshTeams } = useAsyncRetry(
|
|
2908
3377
|
() => api.getTeams().catch(() => []),
|
|
2909
3378
|
[api]
|
|
2910
3379
|
);
|
|
2911
3380
|
const { value: liteLlmConfig } = useAsync2(() => api.getConfig(), [api]);
|
|
3381
|
+
const { value: managedTeams } = useAsync2(
|
|
3382
|
+
async () => liteLlmConfig?.teamManagement?.enabled ? api.getManagedTeams().catch(() => []) : [],
|
|
3383
|
+
[api, liteLlmConfig]
|
|
3384
|
+
);
|
|
3385
|
+
const { allowed: canCreateTeam } = usePermission({ permission: litellmTeamCreatePermission });
|
|
3386
|
+
const { allowed: canManageTeam } = usePermission({ permission: litellmTeamManagePermission });
|
|
3387
|
+
const { allowed: canManageMembers } = usePermission({ permission: litellmTeamMembersManagePermission });
|
|
3388
|
+
const { allowed: canManageKnowledgeBases } = usePermission({ permission: litellmTeamKnowledgebaseManagePermission });
|
|
3389
|
+
const { allowed: canManageMcpServers } = usePermission({ permission: litellmTeamMcpManagePermission });
|
|
3390
|
+
const teamMgmtEnabled = liteLlmConfig?.teamManagement?.enabled ?? false;
|
|
3391
|
+
const objectPermsEnabled = liteLlmConfig?.teamManagement?.objectPermissionsEnabled ?? false;
|
|
3392
|
+
const { value: vectorStores } = useAsync2(
|
|
3393
|
+
async () => objectPermsEnabled ? api.getVectorStores().catch(() => []) : [],
|
|
3394
|
+
[api, objectPermsEnabled]
|
|
3395
|
+
);
|
|
3396
|
+
const { value: mcpServers } = useAsync2(
|
|
3397
|
+
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3398
|
+
[api, objectPermsEnabled]
|
|
3399
|
+
);
|
|
2912
3400
|
const teams = useMemo6(() => {
|
|
2913
3401
|
if (!allTeams?.length) return [];
|
|
2914
3402
|
if (!userInfo) return allTeams;
|
|
@@ -3056,14 +3544,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3056
3544
|
);
|
|
3057
3545
|
const isInitialLoading = userLoading && !userInfo;
|
|
3058
3546
|
if (isInitialLoading) {
|
|
3059
|
-
return /* @__PURE__ */
|
|
3547
|
+
return /* @__PURE__ */ React10.createElement(Box10, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React10.createElement(CircularProgress4, null));
|
|
3060
3548
|
}
|
|
3061
3549
|
if (userError || !userInfo) {
|
|
3062
3550
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3063
3551
|
const hint = userError?.body?.hint;
|
|
3064
|
-
return /* @__PURE__ */
|
|
3552
|
+
return /* @__PURE__ */ React10.createElement(Box10, { p: 3 }, /* @__PURE__ */ React10.createElement(Paper5, { sx: { p: 3 } }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React10.createElement(Typography10, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
|
|
3065
3553
|
}
|
|
3066
|
-
const pageTabs = /* @__PURE__ */
|
|
3554
|
+
const pageTabs = /* @__PURE__ */ React10.createElement(
|
|
3067
3555
|
Tabs2,
|
|
3068
3556
|
{
|
|
3069
3557
|
value: activeTab,
|
|
@@ -3076,13 +3564,13 @@ var init_LiteLLMPage = __esm({
|
|
|
3076
3564
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
3077
3565
|
}
|
|
3078
3566
|
},
|
|
3079
|
-
/* @__PURE__ */
|
|
3080
|
-
/* @__PURE__ */
|
|
3081
|
-
/* @__PURE__ */
|
|
3082
|
-
/* @__PURE__ */
|
|
3083
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
3567
|
+
/* @__PURE__ */ React10.createElement(Tab2, { label: "Overview", value: "overview" }),
|
|
3568
|
+
/* @__PURE__ */ React10.createElement(Tab2, { label: "Keys", value: "keys" }),
|
|
3569
|
+
/* @__PURE__ */ React10.createElement(Tab2, { label: "Teams", value: "teams" }),
|
|
3570
|
+
/* @__PURE__ */ React10.createElement(Tab2, { label: "Models", value: "models" }),
|
|
3571
|
+
userInfo.can_view_audit && /* @__PURE__ */ React10.createElement(Tab2, { label: "Audit Log", value: "audit" })
|
|
3084
3572
|
);
|
|
3085
|
-
return /* @__PURE__ */
|
|
3573
|
+
return /* @__PURE__ */ React10.createElement(Box10, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React10.createElement(
|
|
3086
3574
|
DashboardHeader,
|
|
3087
3575
|
{
|
|
3088
3576
|
userInfo,
|
|
@@ -3092,7 +3580,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3092
3580
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
3093
3581
|
tabs: pageTabs
|
|
3094
3582
|
}
|
|
3095
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
3583
|
+
), activeTab === "overview" && /* @__PURE__ */ React10.createElement(
|
|
3096
3584
|
UsageStats,
|
|
3097
3585
|
{
|
|
3098
3586
|
usage: usage ?? null,
|
|
@@ -3103,7 +3591,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3103
3591
|
loading: usageLoading,
|
|
3104
3592
|
userInfo
|
|
3105
3593
|
}
|
|
3106
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
3594
|
+
), activeTab === "keys" && /* @__PURE__ */ React10.createElement(
|
|
3107
3595
|
KeysTable,
|
|
3108
3596
|
{
|
|
3109
3597
|
keys: keys ?? [],
|
|
@@ -3117,25 +3605,40 @@ var init_LiteLLMPage = __esm({
|
|
|
3117
3605
|
onDeleteKey: handleDeleteKey,
|
|
3118
3606
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
3119
3607
|
}
|
|
3120
|
-
), activeTab === "teams" &&
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3608
|
+
), activeTab === "teams" && (() => {
|
|
3609
|
+
const teamsById = /* @__PURE__ */ new Map();
|
|
3610
|
+
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3611
|
+
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3612
|
+
const visibleTeams = Array.from(teamsById.values());
|
|
3613
|
+
return /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teamMgmtEnabled && canCreateTeam && /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", justifyContent: "flex-end" } }, /* @__PURE__ */ React10.createElement(
|
|
3614
|
+
Button6,
|
|
3615
|
+
{
|
|
3616
|
+
variant: "contained",
|
|
3617
|
+
onClick: () => setManageTeam({ mode: "create" })
|
|
3128
3618
|
},
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3619
|
+
"Create Team"
|
|
3620
|
+
)), /* @__PURE__ */ React10.createElement(
|
|
3621
|
+
TeamUsage,
|
|
3622
|
+
{
|
|
3623
|
+
teams: visibleTeams,
|
|
3624
|
+
loading: teamsLoading,
|
|
3625
|
+
getTeamUsage: (teamId) => {
|
|
3626
|
+
if (teamUsageCache[teamId] === void 0) loadTeamUsage(teamId);
|
|
3627
|
+
return teamUsageCache[teamId] ?? null;
|
|
3628
|
+
},
|
|
3629
|
+
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
3630
|
+
canManage: teamMgmtEnabled && canManageTeam,
|
|
3631
|
+
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t })
|
|
3632
|
+
}
|
|
3633
|
+
));
|
|
3634
|
+
})(), activeTab === "models" && /* @__PURE__ */ React10.createElement(
|
|
3132
3635
|
ModelsTable,
|
|
3133
3636
|
{
|
|
3134
3637
|
allModels: allModels ?? [],
|
|
3135
3638
|
teams: teams ?? [],
|
|
3136
3639
|
loading: modelsLoading
|
|
3137
3640
|
}
|
|
3138
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
3641
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React10.createElement(AuditLog, { api }), /* @__PURE__ */ React10.createElement(
|
|
3139
3642
|
GenerateKeyDialog,
|
|
3140
3643
|
{
|
|
3141
3644
|
open: generateDialogOpen,
|
|
@@ -3148,7 +3651,69 @@ var init_LiteLLMPage = __esm({
|
|
|
3148
3651
|
onGenerateKey: handleGenerateKey,
|
|
3149
3652
|
onGetConfig: () => api.getConfig()
|
|
3150
3653
|
}
|
|
3151
|
-
), /* @__PURE__ */
|
|
3654
|
+
), /* @__PURE__ */ React10.createElement(
|
|
3655
|
+
ManageTeamDialog,
|
|
3656
|
+
{
|
|
3657
|
+
open: !!manageTeam,
|
|
3658
|
+
onClose: () => setManageTeam(null),
|
|
3659
|
+
mode: manageTeam?.mode ?? "create",
|
|
3660
|
+
team: manageTeam?.team,
|
|
3661
|
+
allModels: allModels ?? [],
|
|
3662
|
+
config: liteLlmConfig,
|
|
3663
|
+
onSubmit: async (payload) => {
|
|
3664
|
+
if (manageTeam?.mode === "edit" && manageTeam.team) {
|
|
3665
|
+
await api.updateTeam(manageTeam.team.team_id, payload);
|
|
3666
|
+
} else {
|
|
3667
|
+
await api.createTeam(payload);
|
|
3668
|
+
}
|
|
3669
|
+
setSnackbar({ message: "Team saved", severity: "success" });
|
|
3670
|
+
refreshTeams();
|
|
3671
|
+
},
|
|
3672
|
+
canManageMembers: teamMgmtEnabled && canManageMembers,
|
|
3673
|
+
onAddMember: async (userEntityRef, maxBudgetInTeam) => {
|
|
3674
|
+
if (!manageTeam?.team) return;
|
|
3675
|
+
const updated = await api.addTeamMember(manageTeam.team.team_id, {
|
|
3676
|
+
userEntityRef,
|
|
3677
|
+
...maxBudgetInTeam ? { maxBudgetInTeam } : {}
|
|
3678
|
+
});
|
|
3679
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3680
|
+
refreshTeams();
|
|
3681
|
+
},
|
|
3682
|
+
onRemoveMember: async (userEntityRef) => {
|
|
3683
|
+
if (!manageTeam?.team) return;
|
|
3684
|
+
const updated = await api.removeTeamMember(
|
|
3685
|
+
manageTeam.team.team_id,
|
|
3686
|
+
userEntityRef
|
|
3687
|
+
);
|
|
3688
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3689
|
+
refreshTeams();
|
|
3690
|
+
},
|
|
3691
|
+
canManageKnowledgeBases: teamMgmtEnabled && objectPermsEnabled && canManageKnowledgeBases,
|
|
3692
|
+
vectorStores: vectorStores ?? [],
|
|
3693
|
+
onSaveKnowledgeBases: async (vectorStoreIds) => {
|
|
3694
|
+
if (!manageTeam?.team) return;
|
|
3695
|
+
const updated = await api.setTeamKnowledgeBases(
|
|
3696
|
+
manageTeam.team.team_id,
|
|
3697
|
+
vectorStoreIds
|
|
3698
|
+
);
|
|
3699
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3700
|
+
setSnackbar({ message: "Knowledge bases updated", severity: "success" });
|
|
3701
|
+
refreshTeams();
|
|
3702
|
+
},
|
|
3703
|
+
canManageMcpServers: teamMgmtEnabled && objectPermsEnabled && canManageMcpServers,
|
|
3704
|
+
mcpServers: mcpServers ?? [],
|
|
3705
|
+
onSaveMcpServers: async (mcpServerIds) => {
|
|
3706
|
+
if (!manageTeam?.team) return;
|
|
3707
|
+
const updated = await api.setTeamMcpServers(
|
|
3708
|
+
manageTeam.team.team_id,
|
|
3709
|
+
mcpServerIds
|
|
3710
|
+
);
|
|
3711
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3712
|
+
setSnackbar({ message: "MCP servers updated", severity: "success" });
|
|
3713
|
+
refreshTeams();
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
), /* @__PURE__ */ React10.createElement(
|
|
3152
3717
|
Snackbar2,
|
|
3153
3718
|
{
|
|
3154
3719
|
open: !!snackbar,
|
|
@@ -3156,7 +3721,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3156
3721
|
onClose: () => setSnackbar(null),
|
|
3157
3722
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
3158
3723
|
},
|
|
3159
|
-
snackbar ? /* @__PURE__ */
|
|
3724
|
+
snackbar ? /* @__PURE__ */ React10.createElement(Alert5, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
3160
3725
|
));
|
|
3161
3726
|
};
|
|
3162
3727
|
}
|
|
@@ -3164,7 +3729,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3164
3729
|
|
|
3165
3730
|
// src/plugin.tsx
|
|
3166
3731
|
init_api();
|
|
3167
|
-
import
|
|
3732
|
+
import React11 from "react";
|
|
3168
3733
|
import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
|
|
3169
3734
|
import {
|
|
3170
3735
|
createFrontendPlugin,
|
|
@@ -3183,10 +3748,10 @@ var liteLlmPage = PageBlueprint.make({
|
|
|
3183
3748
|
params: {
|
|
3184
3749
|
path: "/litellm",
|
|
3185
3750
|
title: "LiteLLM",
|
|
3186
|
-
icon: /* @__PURE__ */
|
|
3751
|
+
icon: /* @__PURE__ */ React11.createElement(TrendingUpIcon, null),
|
|
3187
3752
|
loader: async () => {
|
|
3188
3753
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
3189
|
-
return /* @__PURE__ */
|
|
3754
|
+
return /* @__PURE__ */ React11.createElement(LiteLLMPage2, null);
|
|
3190
3755
|
}
|
|
3191
3756
|
}
|
|
3192
3757
|
});
|
|
@@ -3205,16 +3770,16 @@ init_TeamUsage();
|
|
|
3205
3770
|
// src/components/LiteLLMHomeWidget.tsx
|
|
3206
3771
|
init_api();
|
|
3207
3772
|
init_format();
|
|
3208
|
-
import
|
|
3773
|
+
import React12, { useState as useState9, useEffect as useEffect3 } from "react";
|
|
3209
3774
|
import Paper6 from "@mui/material/Paper";
|
|
3210
|
-
import
|
|
3211
|
-
import
|
|
3775
|
+
import Box11 from "@mui/material/Box";
|
|
3776
|
+
import Typography11 from "@mui/material/Typography";
|
|
3212
3777
|
import FormControl2 from "@mui/material/FormControl";
|
|
3213
3778
|
import Select2 from "@mui/material/Select";
|
|
3214
|
-
import
|
|
3779
|
+
import MenuItem6 from "@mui/material/MenuItem";
|
|
3215
3780
|
import Grid2 from "@mui/material/Grid";
|
|
3216
3781
|
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
3217
|
-
import
|
|
3782
|
+
import Alert6 from "@mui/material/Alert";
|
|
3218
3783
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
3219
3784
|
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
3220
3785
|
function presetToDateRange(preset) {
|
|
@@ -3229,19 +3794,19 @@ function presetToDateRange(preset) {
|
|
|
3229
3794
|
}
|
|
3230
3795
|
return { start, end };
|
|
3231
3796
|
}
|
|
3232
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
3797
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Typography11, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React12.createElement(Typography11, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
3233
3798
|
var LiteLLMHomeWidget = ({
|
|
3234
3799
|
defaultPeriod = "7d",
|
|
3235
3800
|
title = "LiteLLM Usage"
|
|
3236
3801
|
}) => {
|
|
3237
3802
|
const api = useApi2(liteLlmApiRef);
|
|
3238
|
-
const [period, setPeriod] =
|
|
3239
|
-
const [loading, setLoading] =
|
|
3240
|
-
const [usageError, setUsageError] =
|
|
3241
|
-
const [keysError, setKeysError] =
|
|
3242
|
-
const [usage, setUsage] =
|
|
3243
|
-
const [keys, setKeys] =
|
|
3244
|
-
|
|
3803
|
+
const [period, setPeriod] = useState9(defaultPeriod);
|
|
3804
|
+
const [loading, setLoading] = useState9(true);
|
|
3805
|
+
const [usageError, setUsageError] = useState9(null);
|
|
3806
|
+
const [keysError, setKeysError] = useState9(null);
|
|
3807
|
+
const [usage, setUsage] = useState9(null);
|
|
3808
|
+
const [keys, setKeys] = useState9([]);
|
|
3809
|
+
useEffect3(() => {
|
|
3245
3810
|
let cancelled = false;
|
|
3246
3811
|
setLoading(true);
|
|
3247
3812
|
setUsageError(null);
|
|
@@ -3278,17 +3843,17 @@ var LiteLLMHomeWidget = ({
|
|
|
3278
3843
|
spend: d.spend
|
|
3279
3844
|
}));
|
|
3280
3845
|
const hasSparkline = dailyData.length > 0;
|
|
3281
|
-
return /* @__PURE__ */
|
|
3846
|
+
return /* @__PURE__ */ React12.createElement(Paper6, { sx: { p: 2 } }, /* @__PURE__ */ React12.createElement(Box11, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React12.createElement(Typography11, { variant: "h6" }, title), /* @__PURE__ */ React12.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React12.createElement(
|
|
3282
3847
|
Select2,
|
|
3283
3848
|
{
|
|
3284
3849
|
value: period,
|
|
3285
3850
|
onChange: (e) => setPeriod(e.target.value),
|
|
3286
3851
|
displayEmpty: true
|
|
3287
3852
|
},
|
|
3288
|
-
/* @__PURE__ */
|
|
3289
|
-
/* @__PURE__ */
|
|
3290
|
-
/* @__PURE__ */
|
|
3291
|
-
))), loading && /* @__PURE__ */
|
|
3853
|
+
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "today" }, "Today"),
|
|
3854
|
+
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "7d" }, "7d"),
|
|
3855
|
+
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "30d" }, "30d")
|
|
3856
|
+
))), loading && /* @__PURE__ */ React12.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React12.createElement(CircularProgress5, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React12.createElement(Alert6, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React12.createElement(React12.Fragment, null, partialFailure && /* @__PURE__ */ React12.createElement(Alert6, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React12.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React12.createElement(Box11, { height: 120 }, /* @__PURE__ */ React12.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React12.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React12.createElement(
|
|
3292
3857
|
Area3,
|
|
3293
3858
|
{
|
|
3294
3859
|
type: "monotone",
|
|
@@ -3304,7 +3869,9 @@ var LiteLLMHomeWidget = ({
|
|
|
3304
3869
|
|
|
3305
3870
|
// src/index.ts
|
|
3306
3871
|
init_GenerateKeyDialog();
|
|
3872
|
+
init_ManageTeamDialog();
|
|
3307
3873
|
init_api();
|
|
3874
|
+
init_permissions();
|
|
3308
3875
|
export {
|
|
3309
3876
|
DashboardHeader,
|
|
3310
3877
|
GenerateKeyDialog,
|
|
@@ -3312,9 +3879,15 @@ export {
|
|
|
3312
3879
|
LiteLLMHomeWidget,
|
|
3313
3880
|
LiteLLMPage,
|
|
3314
3881
|
LiteLlmApi,
|
|
3882
|
+
ManageTeamDialog,
|
|
3315
3883
|
TeamUsage,
|
|
3316
3884
|
UsageStats,
|
|
3317
3885
|
liteLlmApiRef,
|
|
3318
|
-
litellmPlugin
|
|
3886
|
+
litellmPlugin,
|
|
3887
|
+
litellmTeamCreatePermission,
|
|
3888
|
+
litellmTeamKnowledgebaseManagePermission,
|
|
3889
|
+
litellmTeamManagePermission,
|
|
3890
|
+
litellmTeamMcpManagePermission,
|
|
3891
|
+
litellmTeamMembersManagePermission
|
|
3319
3892
|
};
|
|
3320
3893
|
//# sourceMappingURL=index.esm.js.map
|