@acarmisc/backstage-plugin-litellm 0.15.2 → 0.15.3

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/README.md CHANGED
@@ -8,6 +8,17 @@ Pair this package with the backend, [`@acarmisc/backstage-plugin-litellm-backend
8
8
 
9
9
  Full setup docs, configuration reference, and architecture notes live in the [project README](https://github.com/acarmisc/backstage-plugin-litellm-govai#readme).
10
10
 
11
+ ## Screenshots
12
+
13
+ | View | Preview |
14
+ |------|---------|
15
+ | Home widget — `LiteLLMHomeWidget` | ![Home widget](../../docs/screenshots/home-widget.png) |
16
+ | Overview / Usage Analytics | ![Usage analytics](../../docs/screenshots/usage-analytics.png) |
17
+ | Keys tab — virtual key inventory | ![Keys tab](../../docs/screenshots/keys-tab.png) |
18
+ | Generate New Key dialog | ![Generate New Key dialog](../../docs/screenshots/generate-key-dialog.png) |
19
+ | Models tab | ![Models tab](../../docs/screenshots/models-tab.png) |
20
+ | Compact "Create Key" card | ![Create Key card](../../docs/screenshots/key-card.png) |
21
+
11
22
  ## Installation
12
23
 
13
24
  This plugin is designed to be used **within a Backstage monorepo**.
@@ -22,6 +33,8 @@ You'll also need the backend package installed and configured — see its [READM
22
33
 
23
34
  ### Full page
24
35
 
36
+ The `LiteLLMPage` component renders the four-tab governance view: **Overview** (usage KPIs + charts), **Keys** (virtual key inventory), **Teams** (team-scoped usage), and **Models** (proxy model catalog).
37
+
25
38
  ```tsx
26
39
  import { litellmPlugin, LiteLLMPage } from '@acarmisc/backstage-plugin-litellm';
27
40
 
@@ -29,9 +42,11 @@ import { litellmPlugin, LiteLLMPage } from '@acarmisc/backstage-plugin-litellm';
29
42
  <Route path="/litellm" element={<LiteLLMPage />} />
30
43
  ```
31
44
 
45
+ ![Usage Analytics full page](../../docs/screenshots/usage-analytics-overview.png)
46
+
32
47
  ### Home page widget
33
48
 
34
- `LiteLLMHomeWidget` is a compact card for the Backstage homepage. It shows the signed-in user's own usage — identity is resolved server-side from the Backstage token, so no `userId` prop is required.
49
+ `LiteLLMHomeWidget` is a compact card for the Backstage homepage. It shows the signed-in user's own usage — identity is resolved server-side from the Backstage token, so no `userId` prop is required. The header includes a `Today` / `7d` / `30d` period selector and a daily-spend sparkline.
35
50
 
36
51
  ```tsx
37
52
  import { LiteLLMHomeWidget } from '@acarmisc/backstage-plugin-litellm';
@@ -44,6 +59,28 @@ import { LiteLLMHomeWidget } from '@acarmisc/backstage-plugin-litellm';
44
59
  | `defaultPeriod` | `'today' \| '7d' \| '30d'` | `'7d'` | Period shown on first render |
45
60
  | `title` | `string` | `'LiteLLM Usage'` | Card title override |
46
61
 
62
+ A slimmer **Create Key** card variant is available for surfaces that only need a one-click shortcut into the key-mint flow:
63
+
64
+ ![Create Key card](../../docs/screenshots/key-card.png)
65
+
66
+ ### Keys tab
67
+
68
+ The **Keys** tab lists every virtual key you own with its alias, key ID, creation and expiry dates, budget bar (`$spent / $limit`), TPM/RPM, and model scope. Each row exposes inline actions: edit, block/unblock, reset spend, and delete.
69
+
70
+ ![Keys tab](../../docs/screenshots/keys-tab.png)
71
+
72
+ ### Generate New Key dialog
73
+
74
+ The dialog walks the user through aliasing the key, picking a duration, binding a team, restricting model access, setting a max budget, and configuring per-key TPM/RPM caps. Failures (e.g. duplicate alias) surface inline with the upstream error code preserved.
75
+
76
+ ![Generate New Key dialog](../../docs/screenshots/generate-key-dialog.png)
77
+
78
+ ### Models tab
79
+
80
+ The **Models** tab lists every model the LiteLLM proxy exposes, with per-model input/output cost, max input/output token limits, and capabilities. The team selector scopes the list to the models the bound team is allowed to call.
81
+
82
+ ![Models tab](../../docs/screenshots/models-tab.png)
83
+
47
84
  ## Exports
48
85
 
49
86
  - `litellmPlugin` — the frontend plugin instance
@@ -4,9 +4,11 @@ interface UsageStatsProps {
4
4
  usage: UsageMetrics | null;
5
5
  models: ModelInfo[];
6
6
  dateRange: DateRange;
7
- onDateRangeChange: (range: DateRange) => void;
7
+ currentPreset: DatePreset;
8
+ onDateRangeChange: (range: DateRange, preset?: DatePreset) => void;
8
9
  loading: boolean;
9
10
  userInfo?: UserInfo;
10
11
  }
12
+ type DatePreset = 'today' | '24h' | '7d' | '30d';
11
13
  export declare const UsageStats: React.FC<UsageStatsProps>;
12
14
  export {};
package/dist/index.cjs.js CHANGED
@@ -1937,6 +1937,7 @@ var init_UsageStats = __esm({
1937
1937
  usage,
1938
1938
  models,
1939
1939
  dateRange,
1940
+ currentPreset,
1940
1941
  onDateRangeChange,
1941
1942
  loading,
1942
1943
  userInfo
@@ -1944,15 +1945,6 @@ var init_UsageStats = __esm({
1944
1945
  const chart = useChartTheme();
1945
1946
  const [selectedModel, setSelectedModel] = (0, import_react5.useState)("all");
1946
1947
  const [tab, setTab] = (0, import_react5.useState)("costs");
1947
- const selectedPreset = (0, import_react5.useMemo)(() => {
1948
- if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
1949
- const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
1950
- const diffHours = diffMs / (1e3 * 60 * 60);
1951
- if (diffHours <= 26) return "24h";
1952
- const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
1953
- if (diffDays <= 7) return "7d";
1954
- return "30d";
1955
- }, [dateRange]);
1956
1948
  const handlePresetChange = (preset) => {
1957
1949
  const end = /* @__PURE__ */ new Date();
1958
1950
  const start = /* @__PURE__ */ new Date();
@@ -1964,7 +1956,7 @@ var init_UsageStats = __esm({
1964
1956
  localStorage.setItem(PERIOD_LS_KEY, preset);
1965
1957
  } catch {
1966
1958
  }
1967
- onDateRangeChange({ start, end });
1959
+ onDateRangeChange({ start, end }, preset);
1968
1960
  };
1969
1961
  const dailyData = (0, import_react5.useMemo)(
1970
1962
  () => (usage?.daily_usage ?? []).map((d) => ({
@@ -2103,7 +2095,7 @@ var init_UsageStats = __esm({
2103
2095
  select: true,
2104
2096
  size: "small",
2105
2097
  label: "Period",
2106
- value: selectedPreset,
2098
+ value: currentPreset,
2107
2099
  onChange: (e) => handlePresetChange(e.target.value),
2108
2100
  sx: { minWidth: 160 }
2109
2101
  },
@@ -2113,7 +2105,7 @@ var init_UsageStats = __esm({
2113
2105
  SectionCard,
2114
2106
  {
2115
2107
  title: "Usage Analytics",
2116
- subtitle: `${PRESET_LABELS[selectedPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
2108
+ subtitle: `${PRESET_LABELS[currentPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
2117
2109
  actions: periodSelect
2118
2110
  },
2119
2111
  /* @__PURE__ */ import_react5.default.createElement(MetricStrip, { metrics }),
@@ -2880,6 +2872,13 @@ var init_LiteLLMPage = __esm({
2880
2872
  LiteLLMPage = () => {
2881
2873
  const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
2882
2874
  const [dateRange, setDateRange] = (0, import_react9.useState)(initDateRange);
2875
+ const [currentPreset, setCurrentPreset] = (0, import_react9.useState)(() => {
2876
+ try {
2877
+ return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
2878
+ } catch {
2879
+ return "7d";
2880
+ }
2881
+ });
2883
2882
  const [activeTab, setActiveTab] = (0, import_react9.useState)("overview");
2884
2883
  const [snackbar, setSnackbar] = (0, import_react9.useState)(null);
2885
2884
  const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react9.useState)(false);
@@ -2926,11 +2925,14 @@ var init_LiteLLMPage = __esm({
2926
2925
  const endDate = dateRange.end.toISOString().split("T")[0];
2927
2926
  return api.getUsage(startDate, endDate);
2928
2927
  }, [api, dateRange]);
2929
- const handleDateRangeChange = (0, import_react9.useCallback)((range) => {
2928
+ const handleDateRangeChange = (0, import_react9.useCallback)((range, preset) => {
2930
2929
  setDateRange(range);
2930
+ if (preset) {
2931
+ setCurrentPreset(preset);
2932
+ }
2931
2933
  setTeamUsageCache({});
2932
2934
  setTeamUsageLoading({});
2933
- }, [setDateRange]);
2935
+ }, [setDateRange, setCurrentPreset]);
2934
2936
  const loadTeamUsage = (0, import_react9.useCallback)(async (teamId) => {
2935
2937
  if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
2936
2938
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
@@ -3095,6 +3097,7 @@ var init_LiteLLMPage = __esm({
3095
3097
  usage: usage ?? null,
3096
3098
  models: allModels ?? [],
3097
3099
  dateRange,
3100
+ currentPreset,
3098
3101
  onDateRangeChange: handleDateRangeChange,
3099
3102
  loading: usageLoading,
3100
3103
  userInfo