@acarmisc/backstage-plugin-litellm 0.6.0 → 0.7.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 +8 -0
- package/dist/components/KeysTable.d.ts +3 -0
- package/dist/index.cjs.js +76 -10
- package/dist/index.cjs.js.map +2 -2
- package/dist/index.esm.js +76 -10
- package/dist/index.esm.js.map +2 -2
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -15,6 +15,14 @@ var __export = (target, all) => {
|
|
|
15
15
|
|
|
16
16
|
// src/api.ts
|
|
17
17
|
import { createApiRef } from "@backstage/core-plugin-api";
|
|
18
|
+
function expiryStatus(expiresAt) {
|
|
19
|
+
if (!expiresAt) return null;
|
|
20
|
+
const diff = new Date(expiresAt).getTime() - Date.now();
|
|
21
|
+
const days = diff / (1e3 * 60 * 60 * 24);
|
|
22
|
+
if (days < 0) return "expired";
|
|
23
|
+
if (days < 7) return "soon";
|
|
24
|
+
return "ok";
|
|
25
|
+
}
|
|
18
26
|
var ApiError, liteLlmApiRef, LiteLlmApi;
|
|
19
27
|
var init_api = __esm({
|
|
20
28
|
"src/api.ts"() {
|
|
@@ -100,6 +108,19 @@ var init_api = __esm({
|
|
|
100
108
|
async resetKeySpend(keyId) {
|
|
101
109
|
await this.post(`/keys/${encodeURIComponent(keyId)}/reset_spend`, {});
|
|
102
110
|
}
|
|
111
|
+
async pruneExpiredKeys() {
|
|
112
|
+
const keys = await this.get("/keys");
|
|
113
|
+
const expired = keys.filter((k) => expiryStatus(k.expires_at) === "expired");
|
|
114
|
+
if (expired.length === 0) return { pruned: 0 };
|
|
115
|
+
for (const key of expired) {
|
|
116
|
+
try {
|
|
117
|
+
await this.del(`/keys/${encodeURIComponent(key.token ?? key.key)}`);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.warn(`Failed to delete expired key ${key.token ?? key.key}:`, err);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { pruned: expired.length };
|
|
123
|
+
}
|
|
103
124
|
async getAuditLogs(params) {
|
|
104
125
|
const strParams = {};
|
|
105
126
|
if (params.page !== void 0) strParams.page = String(params.page);
|
|
@@ -183,14 +204,6 @@ import {
|
|
|
183
204
|
InputAdornment
|
|
184
205
|
} from "@mui/material";
|
|
185
206
|
import { ContentCopy, Delete, Add, Edit, Autorenew, Search, Warning, Lock, LockOpen } from "@mui/icons-material";
|
|
186
|
-
function expiryStatus(expiresAt) {
|
|
187
|
-
if (!expiresAt) return null;
|
|
188
|
-
const diff = new Date(expiresAt).getTime() - Date.now();
|
|
189
|
-
const days = diff / (1e3 * 60 * 60 * 24);
|
|
190
|
-
if (days < 0) return "expired";
|
|
191
|
-
if (days < 7) return "soon";
|
|
192
|
-
return "ok";
|
|
193
|
-
}
|
|
194
207
|
function ExpiryChip({ expiresAt }) {
|
|
195
208
|
const status = expiryStatus(expiresAt);
|
|
196
209
|
if (!status) return /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2", color: "text.secondary" }, "-");
|
|
@@ -214,6 +227,7 @@ var maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable;
|
|
|
214
227
|
var init_KeysTable = __esm({
|
|
215
228
|
"src/components/KeysTable.tsx"() {
|
|
216
229
|
"use strict";
|
|
230
|
+
init_api();
|
|
217
231
|
maskKey = (key) => {
|
|
218
232
|
if (key.length <= 8) return "***";
|
|
219
233
|
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
@@ -258,7 +272,8 @@ var init_KeysTable = __esm({
|
|
|
258
272
|
onBlockKey,
|
|
259
273
|
onUnblockKey,
|
|
260
274
|
onResetKeySpend,
|
|
261
|
-
onDeleteKey
|
|
275
|
+
onDeleteKey,
|
|
276
|
+
onPruneExpiredKeys
|
|
262
277
|
}) => {
|
|
263
278
|
const [generateModalOpen, setGenerateModalOpen] = useState(false);
|
|
264
279
|
const [newKeyValue, setNewKeyValue] = useState(null);
|
|
@@ -371,6 +386,23 @@ var init_KeysTable = __esm({
|
|
|
371
386
|
const copyToClipboard = (text) => {
|
|
372
387
|
navigator.clipboard.writeText(text);
|
|
373
388
|
};
|
|
389
|
+
const [pruneConfirmCount, setPruneConfirmCount] = useState(null);
|
|
390
|
+
const [pruneSubmitting, setPruneSubmitting] = useState(false);
|
|
391
|
+
const expiredKeys = useMemo(() => {
|
|
392
|
+
return keys.filter((k) => expiryStatus(k.expires_at) === "expired");
|
|
393
|
+
}, [keys]);
|
|
394
|
+
const handlePruneExpired = async () => {
|
|
395
|
+
if (pruneConfirmCount === null || pruneConfirmCount === 0) return;
|
|
396
|
+
setPruneSubmitting(true);
|
|
397
|
+
try {
|
|
398
|
+
await onPruneExpiredKeys();
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error("Failed to prune expired keys:", error);
|
|
401
|
+
} finally {
|
|
402
|
+
setPruneSubmitting(false);
|
|
403
|
+
setPruneConfirmCount(null);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
374
406
|
const modelOption = (m) => {
|
|
375
407
|
const inCost = fmtCost(m.input_cost_per_token);
|
|
376
408
|
const outCost = fmtCost(m.output_cost_per_token);
|
|
@@ -388,6 +420,17 @@ var init_KeysTable = __esm({
|
|
|
388
420
|
startAdornment: /* @__PURE__ */ React2.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React2.createElement(Search, { fontSize: "small" }))
|
|
389
421
|
}
|
|
390
422
|
}
|
|
423
|
+
), expiredKeys.length > 0 && /* @__PURE__ */ React2.createElement(
|
|
424
|
+
Button,
|
|
425
|
+
{
|
|
426
|
+
variant: "outlined",
|
|
427
|
+
color: "error",
|
|
428
|
+
startIcon: /* @__PURE__ */ React2.createElement(Autorenew, null),
|
|
429
|
+
onClick: () => setPruneConfirmCount(expiredKeys.length)
|
|
430
|
+
},
|
|
431
|
+
"Prune expired (",
|
|
432
|
+
expiredKeys.length,
|
|
433
|
+
")"
|
|
391
434
|
), /* @__PURE__ */ React2.createElement(
|
|
392
435
|
Button,
|
|
393
436
|
{
|
|
@@ -651,6 +694,15 @@ var init_KeysTable = __esm({
|
|
|
651
694
|
disabled: deleteSubmitting
|
|
652
695
|
},
|
|
653
696
|
deleteSubmitting ? /* @__PURE__ */ React2.createElement(CircularProgress, { size: 20 }) : "Revoke"
|
|
697
|
+
))), /* @__PURE__ */ React2.createElement(Dialog, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React2.createElement(DialogTitle, null, "Prune Expired Keys?"), /* @__PURE__ */ React2.createElement(DialogContent, null, /* @__PURE__ */ React2.createElement(Typography2, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ React2.createElement(DialogActions, null, /* @__PURE__ */ React2.createElement(Button, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ React2.createElement(
|
|
698
|
+
Button,
|
|
699
|
+
{
|
|
700
|
+
onClick: handlePruneExpired,
|
|
701
|
+
variant: "contained",
|
|
702
|
+
color: "error",
|
|
703
|
+
disabled: pruneSubmitting
|
|
704
|
+
},
|
|
705
|
+
pruneSubmitting ? /* @__PURE__ */ React2.createElement(CircularProgress, { size: 20 }) : "Prune"
|
|
654
706
|
))));
|
|
655
707
|
};
|
|
656
708
|
}
|
|
@@ -1312,6 +1364,19 @@ var init_LiteLLMPage = __esm({
|
|
|
1312
1364
|
},
|
|
1313
1365
|
[api, refreshKeys]
|
|
1314
1366
|
);
|
|
1367
|
+
const handlePruneExpiredKeys = useCallback2(
|
|
1368
|
+
async () => {
|
|
1369
|
+
try {
|
|
1370
|
+
const result = await api.pruneExpiredKeys();
|
|
1371
|
+
setSnackbar({ message: `Pruned ${result.pruned} expired key${result.pruned !== 1 ? "s" : ""}`, severity: "success" });
|
|
1372
|
+
refreshKeys();
|
|
1373
|
+
} catch (e) {
|
|
1374
|
+
setSnackbar({ message: `Failed to prune expired keys: ${e.message}`, severity: "error" });
|
|
1375
|
+
}
|
|
1376
|
+
return { pruned: 0 };
|
|
1377
|
+
},
|
|
1378
|
+
[api, refreshKeys]
|
|
1379
|
+
);
|
|
1315
1380
|
const isInitialLoading = userLoading && !userInfo;
|
|
1316
1381
|
if (isInitialLoading) {
|
|
1317
1382
|
return /* @__PURE__ */ React6.createElement(Box6, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React6.createElement(CircularProgress4, null));
|
|
@@ -1355,7 +1420,8 @@ var init_LiteLLMPage = __esm({
|
|
|
1355
1420
|
onBlockKey: handleBlockKey,
|
|
1356
1421
|
onUnblockKey: handleUnblockKey,
|
|
1357
1422
|
onResetKeySpend: handleResetKeySpend,
|
|
1358
|
-
onDeleteKey: handleDeleteKey
|
|
1423
|
+
onDeleteKey: handleDeleteKey,
|
|
1424
|
+
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
1359
1425
|
}
|
|
1360
1426
|
), activeTab === "teams" && /* @__PURE__ */ React6.createElement(
|
|
1361
1427
|
TeamUsage,
|