@burdenoff/microfe-assethandler 2026.1103.1 → 2026.1103.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.
@@ -1 +1 @@
1
- {"version":3,"file":"AdminPage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/admin/AdminPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAY,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAqD1C,eAAO,MAAM,SAAS,EAAE,EA+RvB,CAAC"}
1
+ {"version":3,"file":"AdminPage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/admin/AdminPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAY,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAkD1C,eAAO,MAAM,SAAS,EAAE,EA2NvB,CAAC"}
@@ -2,178 +2,161 @@ import { StatTile as e } from "../components/StatTile.js";
2
2
  import { SectionHeader as t } from "../components/SectionHeader.js";
3
3
  import "../components/index.js";
4
4
  import { useAssetHandlerAdmin as n } from "./AdminContext.js";
5
- import { ADMIN_FEATURE_FLAGS as r, ADMIN_QUOTA_OVERVIEW as i, ADMIN_WORKSPACE_STATE as a, ASSETHANDLER_QUOTAS as o, OVERRIDE_QUOTA as s, REMOVE_FEATURE_FLAG_OVERRIDE as c, SET_FEATURE_FLAG_OVERRIDE as l, parseQuotaLimit as u, quotaLabel as d } from "./adminOperations.js";
6
- import { useState as f } from "react";
7
- import { Boxes as p, CreditCard as m, Flag as h, Gauge as g, LifeBuoy as _, ShieldAlert as v } from "lucide-react";
8
- import { useMutation as y, useQuery as b } from "@apollo/client/react";
9
- import { jsx as x, jsxs as S } from "react/jsx-runtime";
10
- import { Button as C, GlassCard as w, Input as T, Label as E } from "@burdenoff/fe-libs/ui";
5
+ import { ADMIN_FEATURE_FLAGS as r, ADMIN_QUOTA_OVERVIEW as i, ADMIN_WORKSPACE_STATE as a, REMOVE_FEATURE_FLAG_OVERRIDE as o, SET_FEATURE_FLAG_OVERRIDE as s, summarizeQuotas as c } from "./adminOperations.js";
6
+ import { useState as l } from "react";
7
+ import { Boxes as u, CreditCard as d, Flag as f, Gauge as p, LifeBuoy as m, ShieldAlert as h } from "lucide-react";
8
+ import { useMutation as g, useQuery as _ } from "@apollo/client/react";
9
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
10
+ import { Button as b, GlassCard as x } from "@burdenoff/fe-libs/ui";
11
11
  //#region src/assethandler/admin/AdminPage.tsx
12
- var D = [
12
+ var S = [
13
13
  {
14
14
  path: "/admin/billing",
15
15
  label: "Billing & plan assignment",
16
16
  description: "View the workspace billing account and change its plan.",
17
- icon: m
17
+ icon: d
18
18
  },
19
19
  {
20
20
  path: "/support",
21
21
  label: "User support",
22
22
  description: "Support tickets and user assistance for this workspace.",
23
- icon: _
23
+ icon: m
24
24
  },
25
25
  {
26
26
  path: "/products/feature-flags",
27
27
  label: "All feature flags",
28
28
  description: "Platform-wide feature-flag management.",
29
- icon: h
29
+ icon: f
30
30
  }
31
31
  ];
32
- function O(e) {
32
+ function C(e) {
33
33
  return e instanceof Error ? e.message : "Request failed";
34
34
  }
35
- var k = () => {
36
- let { workspaceId: m = "", navigate: _ } = n(), [k, A] = f(null), [j, M] = f(o[0]?.name ?? ""), [N, P] = f(""), [F, I] = f(!1), L = !m, R = { workspaceId: m }, z = b(a, {
37
- variables: R,
38
- skip: L
39
- }), B = b(r, {
40
- variables: R,
41
- skip: L
42
- }), V = b(i, {
43
- variables: R,
44
- skip: L
45
- }), [H, { loading: U }] = y(l), [W, { loading: G }] = y(c), [K, { loading: q }] = y(s);
46
- if (!m) return /* @__PURE__ */ S("div", {
35
+ var w = () => {
36
+ let { workspaceId: d = "", navigate: m } = n(), [w, T] = l(null), E = !d, D = { workspaceId: d }, O = _(a, {
37
+ variables: D,
38
+ skip: E
39
+ }), k = _(r, {
40
+ variables: D,
41
+ skip: E
42
+ }), A = _(i, {
43
+ variables: D,
44
+ skip: E
45
+ }), [j, { loading: M }] = g(s), [N, { loading: P }] = g(o);
46
+ if (!d) return /* @__PURE__ */ y("div", {
47
47
  className: "flex min-h-[40vh] items-center justify-center text-text-secondary",
48
- children: [/* @__PURE__ */ x(v, { className: "mr-2 h-5 w-5" }), "Workspace context is required."]
48
+ children: [/* @__PURE__ */ v(h, { className: "mr-2 h-5 w-5" }), "Workspace context is required."]
49
49
  });
50
- let J = z.data?.assetHandlerAdminWorkspaceState, Y = B.data?.assetHandlerFeatureFlags.items ?? [], X = V.data?.assetHandlerWorkspaceQuotaOverview.assignments ?? [], Z = [
51
- z.error,
52
- B.error,
53
- V.error
54
- ].filter(Boolean), Q = u(N), $ = !q && !!j && (F || Q !== null), ee = async (e, t) => {
50
+ let F = O.data?.assetHandlerAdminWorkspaceState, I = k.data?.assetHandlerFeatureFlags.items ?? [], L = c(A.data?.assetHandlerWorkspaceQuotaOverview.assignments ?? []), R = [
51
+ O.error,
52
+ k.error,
53
+ A.error
54
+ ].filter(Boolean), z = async (e, t) => {
55
55
  try {
56
- await H({ variables: { input: {
57
- workspaceId: m,
56
+ await j({ variables: { input: {
57
+ workspaceId: d,
58
58
  key: e,
59
59
  enabled: t
60
- } } }), await B.refetch(), A({
60
+ } } }), await k.refetch(), T({
61
61
  tone: "info",
62
62
  text: `${e} ${t ? "enabled" : "disabled"} for this workspace.`
63
63
  });
64
64
  } catch (e) {
65
- A({
65
+ T({
66
66
  tone: "error",
67
- text: O(e)
67
+ text: C(e)
68
68
  });
69
69
  }
70
- }, te = async (e) => {
70
+ }, B = async (e) => {
71
71
  try {
72
- await W({ variables: {
73
- workspaceId: m,
72
+ await N({ variables: {
73
+ workspaceId: d,
74
74
  key: e
75
- } }), await B.refetch(), A({
75
+ } }), await k.refetch(), T({
76
76
  tone: "info",
77
77
  text: `Override for ${e} removed.`
78
78
  });
79
79
  } catch (e) {
80
- A({
81
- tone: "error",
82
- text: O(e)
83
- });
84
- }
85
- }, ne = async () => {
86
- if ($) try {
87
- await K({ variables: { input: {
88
- workspaceId: m,
89
- quotaName: j,
90
- noLimit: F,
91
- limit: F ? null : Q
92
- } } }), await V.refetch(), A({
93
- tone: "info",
94
- text: `${d(j)} set to ${F ? "unlimited" : String(Q)}.`
95
- });
96
- } catch (e) {
97
- A({
80
+ T({
98
81
  tone: "error",
99
- text: O(e)
82
+ text: C(e)
100
83
  });
101
84
  }
102
85
  };
103
- return /* @__PURE__ */ S("div", {
86
+ return /* @__PURE__ */ y("div", {
104
87
  className: "space-y-8 p-6",
105
88
  children: [
106
- /* @__PURE__ */ x(t, {
89
+ /* @__PURE__ */ v(t, {
107
90
  hero: !0,
108
91
  title: "AssetHandler Admin",
109
92
  description: "Workspace state, feature flags, asset limits, and shortcuts to billing and support."
110
93
  }),
111
- k && /* @__PURE__ */ S("div", {
112
- role: k.tone === "error" ? "alert" : "status",
113
- className: k.tone === "error" ? "rounded-md bg-status-error-subtle px-4 py-3 text-sm text-status-error-text" : "rounded-md bg-status-info-subtle px-4 py-3 text-sm text-status-info-text",
114
- children: [k.text, /* @__PURE__ */ x("button", {
94
+ w && /* @__PURE__ */ y("div", {
95
+ role: w.tone === "error" ? "alert" : "status",
96
+ className: w.tone === "error" ? "rounded-md bg-status-error-subtle px-4 py-3 text-sm text-status-error-text" : "rounded-md bg-status-info-subtle px-4 py-3 text-sm text-status-info-text",
97
+ children: [w.text, /* @__PURE__ */ v("button", {
115
98
  type: "button",
116
99
  className: "ml-4 underline",
117
- onClick: () => A(null),
100
+ onClick: () => T(null),
118
101
  children: "Dismiss"
119
102
  })]
120
103
  }),
121
- Z.length > 0 && /* @__PURE__ */ S("div", {
104
+ R.length > 0 && /* @__PURE__ */ y("div", {
122
105
  role: "alert",
123
106
  className: "rounded-md bg-status-error-subtle px-4 py-3 text-sm text-status-error-text",
124
- children: ["Failed to load admin data. ", Z.map((e) => e?.message).join("; ")]
107
+ children: ["Failed to load admin data. ", R.map((e) => e?.message).join("; ")]
125
108
  }),
126
- /* @__PURE__ */ S("section", {
109
+ /* @__PURE__ */ y("section", {
127
110
  "aria-labelledby": "ah-admin-state",
128
- children: [/* @__PURE__ */ x(t, {
111
+ children: [/* @__PURE__ */ v(t, {
129
112
  title: "Workspace state",
130
- icon: p
131
- }), /* @__PURE__ */ S("div", {
113
+ icon: u
114
+ }), /* @__PURE__ */ y("div", {
132
115
  id: "ah-admin-state",
133
116
  className: "grid grid-cols-2 gap-4 md:grid-cols-5",
134
117
  children: [
135
- /* @__PURE__ */ x(e, {
118
+ /* @__PURE__ */ v(e, {
136
119
  label: "Assets",
137
- value: J?.assetCount ?? "—"
120
+ value: F?.assetCount ?? "—"
138
121
  }),
139
- /* @__PURE__ */ x(e, {
122
+ /* @__PURE__ */ v(e, {
140
123
  label: "Open incidents",
141
- value: J?.openIncidentCount ?? "—"
124
+ value: F?.openIncidentCount ?? "—"
142
125
  }),
143
- /* @__PURE__ */ x(e, {
126
+ /* @__PURE__ */ v(e, {
144
127
  label: "Open work orders",
145
- value: J?.openWorkOrderCount ?? "—"
128
+ value: F?.openWorkOrderCount ?? "—"
146
129
  }),
147
- /* @__PURE__ */ x(e, {
130
+ /* @__PURE__ */ v(e, {
148
131
  label: "Maintenance plans",
149
- value: J?.maintenancePlanCount ?? "—"
132
+ value: F?.maintenancePlanCount ?? "—"
150
133
  }),
151
- /* @__PURE__ */ x(e, {
134
+ /* @__PURE__ */ v(e, {
152
135
  label: "Discovery jobs",
153
- value: J?.discoveryJobCount ?? "—"
136
+ value: F?.discoveryJobCount ?? "—"
154
137
  })
155
138
  ]
156
139
  })]
157
140
  }),
158
- /* @__PURE__ */ S("section", {
141
+ /* @__PURE__ */ y("section", {
159
142
  "aria-labelledby": "ah-admin-flags",
160
- children: [/* @__PURE__ */ x(t, {
143
+ children: [/* @__PURE__ */ v(t, {
161
144
  title: "Feature flags",
162
145
  description: "Override a flag for this workspace only. Removing the override restores the platform default.",
163
- icon: h
164
- }), /* @__PURE__ */ S(w, {
146
+ icon: f
147
+ }), /* @__PURE__ */ y(x, {
165
148
  className: "divide-y divide-border",
166
- children: [Y.length === 0 && !B.loading && /* @__PURE__ */ x("p", {
149
+ children: [I.length === 0 && !k.loading && /* @__PURE__ */ v("p", {
167
150
  className: "p-4 text-sm text-text-secondary",
168
151
  children: "No AssetHandler feature flags found."
169
- }), /* @__PURE__ */ x("ul", {
152
+ }), /* @__PURE__ */ v("ul", {
170
153
  id: "ah-admin-flags",
171
- children: Y.map((e) => /* @__PURE__ */ S("li", {
154
+ children: I.map((e) => /* @__PURE__ */ y("li", {
172
155
  className: "flex flex-wrap items-center justify-between gap-3 p-4",
173
- children: [/* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("p", {
156
+ children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
174
157
  className: "font-mono text-sm text-text-primary",
175
158
  children: e.key
176
- }), /* @__PURE__ */ S("p", {
159
+ }), /* @__PURE__ */ y("p", {
177
160
  className: "text-xs text-text-secondary",
178
161
  children: [
179
162
  e.category,
@@ -181,23 +164,23 @@ var k = () => {
181
164
  e.defaultValue ? "on" : "off",
182
165
  " · currently",
183
166
  " ",
184
- /* @__PURE__ */ x("strong", { children: e.value ? "on" : "off" })
167
+ /* @__PURE__ */ v("strong", { children: e.value ? "on" : "off" })
185
168
  ]
186
- })] }), /* @__PURE__ */ S("div", {
169
+ })] }), /* @__PURE__ */ y("div", {
187
170
  className: "flex gap-2",
188
- children: [/* @__PURE__ */ x(C, {
171
+ children: [/* @__PURE__ */ v(b, {
189
172
  type: "button",
190
173
  size: "sm",
191
174
  variant: "outline",
192
- disabled: U,
193
- onClick: () => void ee(e.key, !e.value),
175
+ disabled: M,
176
+ onClick: () => void z(e.key, !e.value),
194
177
  children: e.value ? "Disable" : "Enable"
195
- }), /* @__PURE__ */ x(C, {
178
+ }), /* @__PURE__ */ v(b, {
196
179
  type: "button",
197
180
  size: "sm",
198
181
  variant: "ghost",
199
- disabled: G,
200
- onClick: () => void te(e.key),
182
+ disabled: P,
183
+ onClick: () => void B(e.key),
201
184
  children: "Reset to default"
202
185
  })]
203
186
  })]
@@ -205,144 +188,97 @@ var k = () => {
205
188
  })]
206
189
  })]
207
190
  }),
208
- /* @__PURE__ */ S("section", {
191
+ /* @__PURE__ */ y("section", {
209
192
  "aria-labelledby": "ah-admin-quotas",
210
- children: [/* @__PURE__ */ x(t, {
193
+ children: [/* @__PURE__ */ v(t, {
211
194
  title: "Workspace limits",
212
- description: "Quota assignments for this workspace, including the active-asset limit.",
213
- icon: g
214
- }), /* @__PURE__ */ S(w, {
195
+ description: "The limits this workspace is held to, including the active-asset limit. Read-only: limits are a billing entitlement and are changed through billing.",
196
+ icon: p
197
+ }), /* @__PURE__ */ y(x, {
215
198
  className: "space-y-4 p-4",
216
- children: [/* @__PURE__ */ S("table", {
199
+ children: [/* @__PURE__ */ y("table", {
217
200
  id: "ah-admin-quotas",
218
201
  className: "w-full text-left text-sm",
219
- children: [/* @__PURE__ */ x("thead", {
202
+ children: [/* @__PURE__ */ v("thead", {
220
203
  className: "text-text-secondary",
221
- children: /* @__PURE__ */ S("tr", { children: [
222
- /* @__PURE__ */ x("th", {
204
+ children: /* @__PURE__ */ y("tr", { children: [
205
+ /* @__PURE__ */ v("th", {
223
206
  className: "py-2",
224
207
  children: "Quota"
225
208
  }),
226
- /* @__PURE__ */ x("th", {
209
+ /* @__PURE__ */ v("th", {
227
210
  className: "py-2",
228
211
  children: "Limit"
229
212
  }),
230
- /* @__PURE__ */ x("th", {
213
+ /* @__PURE__ */ v("th", {
231
214
  className: "py-2",
232
215
  children: "Used"
233
216
  }),
234
- /* @__PURE__ */ x("th", {
217
+ /* @__PURE__ */ v("th", {
235
218
  className: "py-2",
236
219
  children: "Resets"
237
220
  })
238
221
  ] })
239
- }), /* @__PURE__ */ S("tbody", { children: [X.length === 0 && /* @__PURE__ */ x("tr", { children: /* @__PURE__ */ x("td", {
222
+ }), /* @__PURE__ */ y("tbody", { children: [L.length === 0 && /* @__PURE__ */ v("tr", { children: /* @__PURE__ */ v("td", {
240
223
  colSpan: 4,
241
224
  className: "py-3 text-text-secondary",
242
225
  children: "No quota assignments yet — limits come from the workspace plan."
243
- }) }), X.map((e) => /* @__PURE__ */ S("tr", { children: [
244
- /* @__PURE__ */ x("td", {
226
+ }) }), L.map((e) => /* @__PURE__ */ y("tr", { children: [
227
+ /* @__PURE__ */ v("td", {
245
228
  className: "py-2",
246
- children: d(e.quotaName)
229
+ children: e.label
247
230
  }),
248
- /* @__PURE__ */ x("td", {
231
+ /* @__PURE__ */ v("td", {
249
232
  className: "py-2",
250
- children: e.noLimit ? "Unlimited" : e.limit ?? "—"
233
+ children: e.unlimited ? "Unlimited" : e.limit
251
234
  }),
252
- /* @__PURE__ */ x("td", {
235
+ /* @__PURE__ */ v("td", {
253
236
  className: "py-2",
254
237
  children: e.used
255
238
  }),
256
- /* @__PURE__ */ x("td", {
239
+ /* @__PURE__ */ v("td", {
257
240
  className: "py-2",
258
241
  children: e.resetPeriod
259
242
  })
260
- ] }, e.id))] })]
261
- }), /* @__PURE__ */ S("form", {
262
- className: "grid gap-4 border-t border-border pt-4 md:grid-cols-4",
263
- onSubmit: (e) => {
264
- e.preventDefault(), ne();
265
- },
266
- children: [
267
- /* @__PURE__ */ S("div", {
268
- className: "space-y-2",
269
- children: [/* @__PURE__ */ x(E, {
270
- htmlFor: "ah-quota-name",
271
- children: "Quota"
272
- }), /* @__PURE__ */ x("select", {
273
- id: "ah-quota-name",
274
- className: "w-full rounded-md border border-border bg-surface px-3 py-2 text-sm",
275
- value: j,
276
- onChange: (e) => M(e.target.value),
277
- children: o.map((e) => /* @__PURE__ */ x("option", {
278
- value: e.name,
279
- children: e.label
280
- }, e.name))
281
- })]
282
- }),
283
- /* @__PURE__ */ S("div", {
284
- className: "space-y-2",
285
- children: [/* @__PURE__ */ x(E, {
286
- htmlFor: "ah-quota-limit",
287
- children: "Limit"
288
- }), /* @__PURE__ */ x(T, {
289
- id: "ah-quota-limit",
290
- inputMode: "numeric",
291
- placeholder: "e.g. 500",
292
- value: N,
293
- disabled: F,
294
- onChange: (e) => P(e.target.value)
295
- })]
296
- }),
297
- /* @__PURE__ */ S("div", {
298
- className: "flex items-end gap-2 pb-2",
299
- children: [/* @__PURE__ */ x("input", {
300
- id: "ah-quota-unlimited",
301
- type: "checkbox",
302
- checked: F,
303
- onChange: (e) => I(e.target.checked)
304
- }), /* @__PURE__ */ x(E, {
305
- htmlFor: "ah-quota-unlimited",
306
- children: "Unlimited"
307
- })]
308
- }),
309
- /* @__PURE__ */ x("div", {
310
- className: "flex items-end",
311
- children: /* @__PURE__ */ x(C, {
312
- type: "submit",
313
- disabled: !$,
314
- children: q ? "Saving…" : "Save limit"
315
- })
316
- })
317
- ]
243
+ ] }, e.quotaName))] })]
244
+ }), /* @__PURE__ */ v("div", {
245
+ className: "border-t border-border pt-4",
246
+ children: /* @__PURE__ */ v(b, {
247
+ type: "button",
248
+ size: "sm",
249
+ variant: "outline",
250
+ disabled: !m,
251
+ onClick: () => m?.("/admin/billing"),
252
+ children: "Change limits in billing"
253
+ })
318
254
  })]
319
255
  })]
320
256
  }),
321
- /* @__PURE__ */ S("section", {
257
+ /* @__PURE__ */ y("section", {
322
258
  "aria-labelledby": "ah-admin-links",
323
- children: [/* @__PURE__ */ x(t, {
259
+ children: [/* @__PURE__ */ v(t, {
324
260
  title: "Billing & support",
325
261
  description: "Handled by the shared admin surfaces."
326
- }), /* @__PURE__ */ x("div", {
262
+ }), /* @__PURE__ */ v("div", {
327
263
  id: "ah-admin-links",
328
264
  className: "grid gap-4 md:grid-cols-3",
329
- children: D.map(({ path: e, label: t, description: n, icon: r }) => /* @__PURE__ */ S(w, {
265
+ children: S.map(({ path: e, label: t, description: n, icon: r }) => /* @__PURE__ */ y(x, {
330
266
  className: "space-y-2 p-4",
331
267
  children: [
332
- /* @__PURE__ */ S("p", {
268
+ /* @__PURE__ */ y("p", {
333
269
  className: "flex items-center gap-2 font-medium text-text-primary",
334
- children: [/* @__PURE__ */ x(r, { className: "h-4 w-4" }), t]
270
+ children: [/* @__PURE__ */ v(r, { className: "h-4 w-4" }), t]
335
271
  }),
336
- /* @__PURE__ */ x("p", {
272
+ /* @__PURE__ */ v("p", {
337
273
  className: "text-sm text-text-secondary",
338
274
  children: n
339
275
  }),
340
- /* @__PURE__ */ x(C, {
276
+ /* @__PURE__ */ v(b, {
341
277
  type: "button",
342
278
  size: "sm",
343
279
  variant: "outline",
344
- disabled: !_,
345
- onClick: () => _?.(e),
280
+ disabled: !m,
281
+ onClick: () => m?.(e),
346
282
  children: "Open"
347
283
  })
348
284
  ]
@@ -353,6 +289,6 @@ var k = () => {
353
289
  });
354
290
  };
355
291
  //#endregion
356
- export { k as AdminPage };
292
+ export { w as AdminPage };
357
293
 
358
294
  //# sourceMappingURL=AdminPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminPage.js","names":[],"sources":["../../../src/assethandler/admin/AdminPage.tsx"],"sourcesContent":["/**\n * AssetHandler Admin Page\n *\n * Product-specific governance surface mounted inside `admin-app` at\n * `/admin/assethandler`: workspace state, feature-flag overrides, workspace\n * quota (asset limit) overrides, and shortcuts to the shared admin surfaces for\n * billing plan assignment and user support. All writes are super-admin only\n * (enforced by `wspace-assethandler-svc`).\n */\nimport { useState, type FC } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Boxes, CreditCard, Flag, Gauge, LifeBuoy, ShieldAlert } from 'lucide-react';\nimport { Button, GlassCard, Input, Label } from '@burdenoff/fe-libs/ui';\n\nimport { SectionHeader, StatTile } from '../components';\nimport {\n ADMIN_FEATURE_FLAGS,\n ADMIN_QUOTA_OVERVIEW,\n ADMIN_WORKSPACE_STATE,\n ASSETHANDLER_QUOTAS,\n OVERRIDE_QUOTA,\n REMOVE_FEATURE_FLAG_OVERRIDE,\n SET_FEATURE_FLAG_OVERRIDE,\n parseQuotaLimit,\n quotaLabel,\n type AdminFeatureFlagsData,\n type AdminQuotaOverviewData,\n type AdminWorkspaceStateData,\n} from './adminOperations';\nimport { useAssetHandlerAdmin } from './AdminContext';\n\n/** Shared admin surfaces that already cover billing and support actions. */\nconst SHARED_ADMIN_LINKS: ReadonlyArray<{\n path: string;\n label: string;\n description: string;\n icon: typeof Flag;\n}> = [\n {\n path: '/admin/billing',\n label: 'Billing & plan assignment',\n description: 'View the workspace billing account and change its plan.',\n icon: CreditCard,\n },\n {\n path: '/support',\n label: 'User support',\n description: 'Support tickets and user assistance for this workspace.',\n icon: LifeBuoy,\n },\n {\n path: '/products/feature-flags',\n label: 'All feature flags',\n description: 'Platform-wide feature-flag management.',\n icon: Flag,\n },\n];\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : 'Request failed';\n}\n\nexport const AdminPage: FC = () => {\n const { workspaceId = '', navigate } = useAssetHandlerAdmin();\n const [message, setMessage] = useState<{ tone: 'info' | 'error'; text: string } | null>(null);\n const [quotaName, setQuotaName] = useState(ASSETHANDLER_QUOTAS[0]?.name ?? '');\n const [quotaLimit, setQuotaLimit] = useState('');\n const [quotaUnlimited, setQuotaUnlimited] = useState(false);\n\n const skip = !workspaceId;\n const variables = { workspaceId };\n\n const state = useQuery<AdminWorkspaceStateData>(ADMIN_WORKSPACE_STATE, { variables, skip });\n const flags = useQuery<AdminFeatureFlagsData>(ADMIN_FEATURE_FLAGS, { variables, skip });\n const quotas = useQuery<AdminQuotaOverviewData>(ADMIN_QUOTA_OVERVIEW, { variables, skip });\n\n const [setFlag, { loading: settingFlag }] = useMutation(SET_FEATURE_FLAG_OVERRIDE);\n const [removeFlag, { loading: removingFlag }] = useMutation(REMOVE_FEATURE_FLAG_OVERRIDE);\n const [overrideQuota, { loading: savingQuota }] = useMutation(OVERRIDE_QUOTA);\n\n if (!workspaceId) {\n return (\n <div className=\"flex min-h-[40vh] items-center justify-center text-text-secondary\">\n <ShieldAlert className=\"mr-2 h-5 w-5\" />\n Workspace context is required.\n </div>\n );\n }\n\n const workspaceState = state.data?.assetHandlerAdminWorkspaceState;\n const flagItems = flags.data?.assetHandlerFeatureFlags.items ?? [];\n const assignments = quotas.data?.assetHandlerWorkspaceQuotaOverview.assignments ?? [];\n const loadErrors = [state.error, flags.error, quotas.error].filter(Boolean);\n const parsedLimit = parseQuotaLimit(quotaLimit);\n const canSaveQuota =\n !savingQuota && Boolean(quotaName) && (quotaUnlimited || parsedLimit !== null);\n\n const toggleFlag = async (key: string, enabled: boolean): Promise<void> => {\n try {\n await setFlag({ variables: { input: { workspaceId, key, enabled } } });\n await flags.refetch();\n setMessage({\n tone: 'info',\n text: `${key} ${enabled ? 'enabled' : 'disabled'} for this workspace.`,\n });\n } catch (error) {\n setMessage({ tone: 'error', text: errorMessage(error) });\n }\n };\n\n const clearOverride = async (key: string): Promise<void> => {\n try {\n await removeFlag({ variables: { workspaceId, key } });\n await flags.refetch();\n setMessage({ tone: 'info', text: `Override for ${key} removed.` });\n } catch (error) {\n setMessage({ tone: 'error', text: errorMessage(error) });\n }\n };\n\n const saveQuota = async (): Promise<void> => {\n if (!canSaveQuota) return;\n try {\n await overrideQuota({\n variables: {\n input: {\n workspaceId,\n quotaName,\n noLimit: quotaUnlimited,\n limit: quotaUnlimited ? null : parsedLimit,\n },\n },\n });\n await quotas.refetch();\n setMessage({\n tone: 'info',\n text: `${quotaLabel(quotaName)} set to ${quotaUnlimited ? 'unlimited' : String(parsedLimit)}.`,\n });\n } catch (error) {\n setMessage({ tone: 'error', text: errorMessage(error) });\n }\n };\n\n return (\n <div className=\"space-y-8 p-6\">\n <SectionHeader\n hero\n title=\"AssetHandler Admin\"\n description=\"Workspace state, feature flags, asset limits, and shortcuts to billing and support.\"\n />\n\n {message && (\n <div\n role={message.tone === 'error' ? 'alert' : 'status'}\n className={\n message.tone === 'error'\n ? 'rounded-md bg-status-error-subtle px-4 py-3 text-sm text-status-error-text'\n : 'rounded-md bg-status-info-subtle px-4 py-3 text-sm text-status-info-text'\n }\n >\n {message.text}\n <button type=\"button\" className=\"ml-4 underline\" onClick={() => setMessage(null)}>\n Dismiss\n </button>\n </div>\n )}\n\n {loadErrors.length > 0 && (\n <div\n role=\"alert\"\n className=\"rounded-md bg-status-error-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n Failed to load admin data. {loadErrors.map((error) => error?.message).join('; ')}\n </div>\n )}\n\n <section aria-labelledby=\"ah-admin-state\">\n <SectionHeader title=\"Workspace state\" icon={Boxes} />\n <div id=\"ah-admin-state\" className=\"grid grid-cols-2 gap-4 md:grid-cols-5\">\n <StatTile label=\"Assets\" value={workspaceState?.assetCount ?? '—'} />\n <StatTile label=\"Open incidents\" value={workspaceState?.openIncidentCount ?? '—'} />\n <StatTile label=\"Open work orders\" value={workspaceState?.openWorkOrderCount ?? '—'} />\n <StatTile label=\"Maintenance plans\" value={workspaceState?.maintenancePlanCount ?? '—'} />\n <StatTile label=\"Discovery jobs\" value={workspaceState?.discoveryJobCount ?? '—'} />\n </div>\n </section>\n\n <section aria-labelledby=\"ah-admin-flags\">\n <SectionHeader\n title=\"Feature flags\"\n description=\"Override a flag for this workspace only. Removing the override restores the platform default.\"\n icon={Flag}\n />\n <GlassCard className=\"divide-y divide-border\">\n {flagItems.length === 0 && !flags.loading && (\n <p className=\"p-4 text-sm text-text-secondary\">No AssetHandler feature flags found.</p>\n )}\n <ul id=\"ah-admin-flags\">\n {flagItems.map((flag) => (\n <li key={flag.key} className=\"flex flex-wrap items-center justify-between gap-3 p-4\">\n <div>\n <p className=\"font-mono text-sm text-text-primary\">{flag.key}</p>\n <p className=\"text-xs text-text-secondary\">\n {flag.category} · default {flag.defaultValue ? 'on' : 'off'} · currently{' '}\n <strong>{flag.value ? 'on' : 'off'}</strong>\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={settingFlag}\n onClick={() => void toggleFlag(flag.key, !flag.value)}\n >\n {flag.value ? 'Disable' : 'Enable'}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n disabled={removingFlag}\n onClick={() => void clearOverride(flag.key)}\n >\n Reset to default\n </Button>\n </div>\n </li>\n ))}\n </ul>\n </GlassCard>\n </section>\n\n <section aria-labelledby=\"ah-admin-quotas\">\n <SectionHeader\n title=\"Workspace limits\"\n description=\"Quota assignments for this workspace, including the active-asset limit.\"\n icon={Gauge}\n />\n <GlassCard className=\"space-y-4 p-4\">\n <table id=\"ah-admin-quotas\" className=\"w-full text-left text-sm\">\n <thead className=\"text-text-secondary\">\n <tr>\n <th className=\"py-2\">Quota</th>\n <th className=\"py-2\">Limit</th>\n <th className=\"py-2\">Used</th>\n <th className=\"py-2\">Resets</th>\n </tr>\n </thead>\n <tbody>\n {assignments.length === 0 && (\n <tr>\n <td colSpan={4} className=\"py-3 text-text-secondary\">\n No quota assignments yet — limits come from the workspace plan.\n </td>\n </tr>\n )}\n {assignments.map((assignment) => (\n <tr key={assignment.id}>\n <td className=\"py-2\">{quotaLabel(assignment.quotaName)}</td>\n <td className=\"py-2\">\n {assignment.noLimit ? 'Unlimited' : (assignment.limit ?? '—')}\n </td>\n <td className=\"py-2\">{assignment.used}</td>\n <td className=\"py-2\">{assignment.resetPeriod}</td>\n </tr>\n ))}\n </tbody>\n </table>\n\n <form\n className=\"grid gap-4 border-t border-border pt-4 md:grid-cols-4\"\n onSubmit={(event) => {\n event.preventDefault();\n void saveQuota();\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"ah-quota-name\">Quota</Label>\n <select\n id=\"ah-quota-name\"\n className=\"w-full rounded-md border border-border bg-surface px-3 py-2 text-sm\"\n value={quotaName}\n onChange={(event) => setQuotaName(event.target.value)}\n >\n {ASSETHANDLER_QUOTAS.map((quota) => (\n <option key={quota.name} value={quota.name}>\n {quota.label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"ah-quota-limit\">Limit</Label>\n <Input\n id=\"ah-quota-limit\"\n inputMode=\"numeric\"\n placeholder=\"e.g. 500\"\n value={quotaLimit}\n disabled={quotaUnlimited}\n onChange={(event) => setQuotaLimit(event.target.value)}\n />\n </div>\n <div className=\"flex items-end gap-2 pb-2\">\n <input\n id=\"ah-quota-unlimited\"\n type=\"checkbox\"\n checked={quotaUnlimited}\n onChange={(event) => setQuotaUnlimited(event.target.checked)}\n />\n <Label htmlFor=\"ah-quota-unlimited\">Unlimited</Label>\n </div>\n <div className=\"flex items-end\">\n <Button type=\"submit\" disabled={!canSaveQuota}>\n {savingQuota ? 'Saving…' : 'Save limit'}\n </Button>\n </div>\n </form>\n </GlassCard>\n </section>\n\n <section aria-labelledby=\"ah-admin-links\">\n <SectionHeader\n title=\"Billing & support\"\n description=\"Handled by the shared admin surfaces.\"\n />\n <div id=\"ah-admin-links\" className=\"grid gap-4 md:grid-cols-3\">\n {SHARED_ADMIN_LINKS.map(({ path, label, description, icon: Icon }) => (\n <GlassCard key={path} className=\"space-y-2 p-4\">\n <p className=\"flex items-center gap-2 font-medium text-text-primary\">\n <Icon className=\"h-4 w-4\" />\n {label}\n </p>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={!navigate}\n onClick={() => navigate?.(path)}\n >\n Open\n </Button>\n </GlassCard>\n ))}\n </div>\n </section>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AdminPage.js","names":[],"sources":["../../../src/assethandler/admin/AdminPage.tsx"],"sourcesContent":["/**\n * AssetHandler Admin Page\n *\n * Product-specific governance surface mounted inside `admin-app` at\n * `/admin/assethandler`: workspace state, feature-flag overrides, the\n * workspace's effective quota limits (read-only), and shortcuts to the shared\n * admin surfaces for billing plan assignment and user support. Flag writes are\n * super-admin only (enforced by `wspace-assethandler-svc`). Limits are a billing\n * entitlement, so they are changed through billing, not from this page.\n */\nimport { useState, type FC } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Boxes, CreditCard, Flag, Gauge, LifeBuoy, ShieldAlert } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { SectionHeader, StatTile } from '../components';\nimport {\n ADMIN_FEATURE_FLAGS,\n ADMIN_QUOTA_OVERVIEW,\n ADMIN_WORKSPACE_STATE,\n REMOVE_FEATURE_FLAG_OVERRIDE,\n SET_FEATURE_FLAG_OVERRIDE,\n summarizeQuotas,\n type AdminFeatureFlagsData,\n type AdminQuotaOverviewData,\n type AdminWorkspaceStateData,\n} from './adminOperations';\nimport { useAssetHandlerAdmin } from './AdminContext';\n\n/** Shared admin surfaces that already cover billing and support actions. */\nconst SHARED_ADMIN_LINKS: ReadonlyArray<{\n path: string;\n label: string;\n description: string;\n icon: typeof Flag;\n}> = [\n {\n path: '/admin/billing',\n label: 'Billing & plan assignment',\n description: 'View the workspace billing account and change its plan.',\n icon: CreditCard,\n },\n {\n path: '/support',\n label: 'User support',\n description: 'Support tickets and user assistance for this workspace.',\n icon: LifeBuoy,\n },\n {\n path: '/products/feature-flags',\n label: 'All feature flags',\n description: 'Platform-wide feature-flag management.',\n icon: Flag,\n },\n];\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : 'Request failed';\n}\n\nexport const AdminPage: FC = () => {\n const { workspaceId = '', navigate } = useAssetHandlerAdmin();\n const [message, setMessage] = useState<{ tone: 'info' | 'error'; text: string } | null>(null);\n\n const skip = !workspaceId;\n const variables = { workspaceId };\n\n const state = useQuery<AdminWorkspaceStateData>(ADMIN_WORKSPACE_STATE, { variables, skip });\n const flags = useQuery<AdminFeatureFlagsData>(ADMIN_FEATURE_FLAGS, { variables, skip });\n const quotas = useQuery<AdminQuotaOverviewData>(ADMIN_QUOTA_OVERVIEW, { variables, skip });\n\n const [setFlag, { loading: settingFlag }] = useMutation(SET_FEATURE_FLAG_OVERRIDE);\n const [removeFlag, { loading: removingFlag }] = useMutation(REMOVE_FEATURE_FLAG_OVERRIDE);\n\n if (!workspaceId) {\n return (\n <div className=\"flex min-h-[40vh] items-center justify-center text-text-secondary\">\n <ShieldAlert className=\"mr-2 h-5 w-5\" />\n Workspace context is required.\n </div>\n );\n }\n\n const workspaceState = state.data?.assetHandlerAdminWorkspaceState;\n const flagItems = flags.data?.assetHandlerFeatureFlags.items ?? [];\n const quotaLines = summarizeQuotas(\n quotas.data?.assetHandlerWorkspaceQuotaOverview.assignments ?? []\n );\n const loadErrors = [state.error, flags.error, quotas.error].filter(Boolean);\n\n const toggleFlag = async (key: string, enabled: boolean): Promise<void> => {\n try {\n await setFlag({ variables: { input: { workspaceId, key, enabled } } });\n await flags.refetch();\n setMessage({\n tone: 'info',\n text: `${key} ${enabled ? 'enabled' : 'disabled'} for this workspace.`,\n });\n } catch (error) {\n setMessage({ tone: 'error', text: errorMessage(error) });\n }\n };\n\n const clearOverride = async (key: string): Promise<void> => {\n try {\n await removeFlag({ variables: { workspaceId, key } });\n await flags.refetch();\n setMessage({ tone: 'info', text: `Override for ${key} removed.` });\n } catch (error) {\n setMessage({ tone: 'error', text: errorMessage(error) });\n }\n };\n\n return (\n <div className=\"space-y-8 p-6\">\n <SectionHeader\n hero\n title=\"AssetHandler Admin\"\n description=\"Workspace state, feature flags, asset limits, and shortcuts to billing and support.\"\n />\n\n {message && (\n <div\n role={message.tone === 'error' ? 'alert' : 'status'}\n className={\n message.tone === 'error'\n ? 'rounded-md bg-status-error-subtle px-4 py-3 text-sm text-status-error-text'\n : 'rounded-md bg-status-info-subtle px-4 py-3 text-sm text-status-info-text'\n }\n >\n {message.text}\n <button type=\"button\" className=\"ml-4 underline\" onClick={() => setMessage(null)}>\n Dismiss\n </button>\n </div>\n )}\n\n {loadErrors.length > 0 && (\n <div\n role=\"alert\"\n className=\"rounded-md bg-status-error-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n Failed to load admin data. {loadErrors.map((error) => error?.message).join('; ')}\n </div>\n )}\n\n <section aria-labelledby=\"ah-admin-state\">\n <SectionHeader title=\"Workspace state\" icon={Boxes} />\n <div id=\"ah-admin-state\" className=\"grid grid-cols-2 gap-4 md:grid-cols-5\">\n <StatTile label=\"Assets\" value={workspaceState?.assetCount ?? '—'} />\n <StatTile label=\"Open incidents\" value={workspaceState?.openIncidentCount ?? '—'} />\n <StatTile label=\"Open work orders\" value={workspaceState?.openWorkOrderCount ?? '—'} />\n <StatTile label=\"Maintenance plans\" value={workspaceState?.maintenancePlanCount ?? '—'} />\n <StatTile label=\"Discovery jobs\" value={workspaceState?.discoveryJobCount ?? '—'} />\n </div>\n </section>\n\n <section aria-labelledby=\"ah-admin-flags\">\n <SectionHeader\n title=\"Feature flags\"\n description=\"Override a flag for this workspace only. Removing the override restores the platform default.\"\n icon={Flag}\n />\n <GlassCard className=\"divide-y divide-border\">\n {flagItems.length === 0 && !flags.loading && (\n <p className=\"p-4 text-sm text-text-secondary\">No AssetHandler feature flags found.</p>\n )}\n <ul id=\"ah-admin-flags\">\n {flagItems.map((flag) => (\n <li key={flag.key} className=\"flex flex-wrap items-center justify-between gap-3 p-4\">\n <div>\n <p className=\"font-mono text-sm text-text-primary\">{flag.key}</p>\n <p className=\"text-xs text-text-secondary\">\n {flag.category} · default {flag.defaultValue ? 'on' : 'off'} · currently{' '}\n <strong>{flag.value ? 'on' : 'off'}</strong>\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={settingFlag}\n onClick={() => void toggleFlag(flag.key, !flag.value)}\n >\n {flag.value ? 'Disable' : 'Enable'}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n disabled={removingFlag}\n onClick={() => void clearOverride(flag.key)}\n >\n Reset to default\n </Button>\n </div>\n </li>\n ))}\n </ul>\n </GlassCard>\n </section>\n\n <section aria-labelledby=\"ah-admin-quotas\">\n <SectionHeader\n title=\"Workspace limits\"\n description=\"The limits this workspace is held to, including the active-asset limit. Read-only: limits are a billing entitlement and are changed through billing.\"\n icon={Gauge}\n />\n <GlassCard className=\"space-y-4 p-4\">\n <table id=\"ah-admin-quotas\" className=\"w-full text-left text-sm\">\n <thead className=\"text-text-secondary\">\n <tr>\n <th className=\"py-2\">Quota</th>\n <th className=\"py-2\">Limit</th>\n <th className=\"py-2\">Used</th>\n <th className=\"py-2\">Resets</th>\n </tr>\n </thead>\n <tbody>\n {quotaLines.length === 0 && (\n <tr>\n <td colSpan={4} className=\"py-3 text-text-secondary\">\n No quota assignments yet — limits come from the workspace plan.\n </td>\n </tr>\n )}\n {quotaLines.map((line) => (\n <tr key={line.quotaName}>\n <td className=\"py-2\">{line.label}</td>\n <td className=\"py-2\">{line.unlimited ? 'Unlimited' : line.limit}</td>\n <td className=\"py-2\">{line.used}</td>\n <td className=\"py-2\">{line.resetPeriod}</td>\n </tr>\n ))}\n </tbody>\n </table>\n <div className=\"border-t border-border pt-4\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={!navigate}\n onClick={() => navigate?.('/admin/billing')}\n >\n Change limits in billing\n </Button>\n </div>\n </GlassCard>\n </section>\n\n <section aria-labelledby=\"ah-admin-links\">\n <SectionHeader\n title=\"Billing & support\"\n description=\"Handled by the shared admin surfaces.\"\n />\n <div id=\"ah-admin-links\" className=\"grid gap-4 md:grid-cols-3\">\n {SHARED_ADMIN_LINKS.map(({ path, label, description, icon: Icon }) => (\n <GlassCard key={path} className=\"space-y-2 p-4\">\n <p className=\"flex items-center gap-2 font-medium text-text-primary\">\n <Icon className=\"h-4 w-4\" />\n {label}\n </p>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={!navigate}\n onClick={() => navigate?.(path)}\n >\n Open\n </Button>\n </GlassCard>\n ))}\n </div>\n </section>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AdminRoot.d.ts","sourceRoot":"","sources":["../../../src/assethandler/admin/AdminRoot.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,EAAE,EAAqB,MAAM,OAAO,CAAC;AAMnD,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGnE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA+BrD,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CAIxC,CAAC"}
1
+ {"version":3,"file":"AdminRoot.d.ts","sourceRoot":"","sources":["../../../src/assethandler/admin/AdminRoot.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,EAAE,EAAqB,MAAM,OAAO,CAAC;AAKnD,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGnE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAgCrD,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CAIxC,CAAC"}
@@ -1,42 +1,41 @@
1
1
  import { AdminContext as e } from "./AdminContext.js";
2
2
  import { AdminPage as t } from "./AdminPage.js";
3
3
  import { useMemo as n } from "react";
4
- import { MemoryRouter as r } from "react-router";
5
- import { ApolloClient as i, HttpLink as a, InMemoryCache as o } from "@apollo/client";
6
- import { ApolloProvider as s } from "@apollo/client/react";
7
- import { jsx as c } from "react/jsx-runtime";
4
+ import { ApolloClient as r, HttpLink as i, InMemoryCache as a } from "@apollo/client";
5
+ import { ApolloProvider as o } from "@apollo/client/react";
6
+ import { jsx as s } from "react/jsx-runtime";
8
7
  //#region src/assethandler/admin/AdminRoot.tsx
9
- var l = ({ children: t, ...l }) => {
10
- let { apiGatewayUrl: u, authToken: d, workspaceToken: f, workspaceId: p, tenantId: m } = l;
11
- return /* @__PURE__ */ c(s, {
12
- client: n(() => new i({
13
- link: new a({
14
- uri: u ? `${u}/workspaces/graphql` : "/workspaces/graphql",
8
+ var c = ({ children: t, ...c }) => {
9
+ let { apiGatewayUrl: l, authToken: u, workspaceToken: d, workspaceId: f, tenantId: p } = c;
10
+ return /* @__PURE__ */ s(o, {
11
+ client: n(() => new r({
12
+ link: new i({
13
+ uri: l ? `${l}/workspaces/graphql` : "/workspaces/graphql",
15
14
  headers: {
16
- ...d ? { Authorization: `Bearer ${d}` } : {},
17
- ...f ? { "X-Workspace-Authorization": `Bearer ${f}` } : {},
18
- ...p ? { "x-workspace-id": p } : {},
19
- ...m ? { "x-tenant-id": m } : {}
15
+ ...u ? { Authorization: `Bearer ${u}` } : {},
16
+ ...d ? { "X-Workspace-Authorization": `Bearer ${d}` } : {},
17
+ ...f ? { "x-workspace-id": f } : {},
18
+ ...p ? { "x-tenant-id": p } : {}
20
19
  }
21
20
  }),
22
- cache: new o()
21
+ cache: new a()
23
22
  }), [
23
+ l,
24
24
  u,
25
25
  d,
26
26
  f,
27
- p,
28
- m
27
+ p
29
28
  ]),
30
- children: /* @__PURE__ */ c(r, { children: /* @__PURE__ */ c(e.Provider, {
31
- value: l,
29
+ children: /* @__PURE__ */ s(e.Provider, {
30
+ value: c,
32
31
  children: t
33
- }) })
32
+ })
34
33
  });
35
- }, u = (e) => /* @__PURE__ */ c(l, {
34
+ }, l = (e) => /* @__PURE__ */ s(c, {
36
35
  ...e,
37
- children: /* @__PURE__ */ c(t, {})
36
+ children: /* @__PURE__ */ s(t, {})
38
37
  });
39
38
  //#endregion
40
- export { u as AdminRoot };
39
+ export { l as AdminRoot };
41
40
 
42
41
  //# sourceMappingURL=AdminRoot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminRoot.js","names":[],"sources":["../../../src/assethandler/admin/AdminRoot.tsx"],"sourcesContent":["/**\n * AssetHandler Admin Root\n *\n * Host-contract entry point for the AssetHandler admin surface inside\n * `admin-app` at `/admin/assethandler` (BOFF-3362).\n *\n * Deliberately does NOT reuse `AssetHandlerProvider`: that provider hydrates\n * the whole product store (thirteen bootstrap queries) which an operations\n * console never needs. This provider only builds the workspace-gateway Apollo\n * client and exposes the host context to the admin page.\n */\nimport type { FC, PropsWithChildren } from 'react';\nimport { useMemo } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';\nimport { ApolloProvider } from '@apollo/client/react';\n\nimport { AdminContext, type AdminRootProps } from './AdminContext';\nimport { AdminPage } from './AdminPage';\n\nexport type { AdminRootProps } from './AdminContext';\n\nconst AdminProvider: FC<PropsWithChildren<AdminRootProps>> = ({ children, ...props }) => {\n const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId } = props;\n\n const client = useMemo(\n () =>\n new ApolloClient({\n link: new HttpLink({\n uri: apiGatewayUrl ? `${apiGatewayUrl}/workspaces/graphql` : '/workspaces/graphql',\n headers: {\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n ...(workspaceToken ? { 'X-Workspace-Authorization': `Bearer ${workspaceToken}` } : {}),\n ...(workspaceId ? { 'x-workspace-id': workspaceId } : {}),\n ...(tenantId ? { 'x-tenant-id': tenantId } : {}),\n },\n }),\n cache: new InMemoryCache(),\n }),\n [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId]\n );\n\n return (\n <ApolloProvider client={client}>\n <MemoryRouter>\n <AdminContext.Provider value={props}>{children}</AdminContext.Provider>\n </MemoryRouter>\n </ApolloProvider>\n );\n};\n\nexport const AdminRoot: FC<AdminRootProps> = (props) => (\n <AdminProvider {...props}>\n <AdminPage />\n </AdminProvider>\n);\n"],"mappings":";;;;;;;;AAsBA,IAAM,KAAwD,EAAE,aAAU,GAAG,QAAY;CACvF,IAAM,EAAE,kBAAe,cAAW,mBAAgB,gBAAa,gBAAa;CAmB5E,OACE,kBAAC,GAAD;EAAwB,QAlBX,QAEX,IAAI,EAAa;GACf,MAAM,IAAI,EAAS;IACjB,KAAK,IAAgB,GAAG,EAAc,uBAAuB;IAC7D,SAAS;KACP,GAAI,IAAY,EAAE,eAAe,UAAU,IAAY,IAAI,CAAC;KAC5D,GAAI,IAAiB,EAAE,6BAA6B,UAAU,IAAiB,IAAI,CAAC;KACpF,GAAI,IAAc,EAAE,kBAAkB,EAAY,IAAI,CAAC;KACvD,GAAI,IAAW,EAAE,eAAe,EAAS,IAAI,CAAC;IAChD;GACF,CAAC;GACD,OAAO,IAAI,EAAc;EAC3B,CAAC,GACH;GAAC;GAAe;GAAW;GAAgB;GAAa;EAAQ,CAIxC;YACtB,kBAAC,GAAD,EAAA,UACE,kBAAC,EAAa,UAAd;GAAuB,OAAO;GAAQ;EAAgC,CAAA,EAC1D,CAAA;CACA,CAAA;AAEpB,GAEa,KAAiC,MAC5C,kBAAC,GAAD;CAAe,GAAI;WACjB,kBAAC,GAAD,CAAY,CAAA;AACC,CAAA"}
1
+ {"version":3,"file":"AdminRoot.js","names":[],"sources":["../../../src/assethandler/admin/AdminRoot.tsx"],"sourcesContent":["/**\n * AssetHandler Admin Root\n *\n * Host-contract entry point for the AssetHandler admin surface inside\n * `admin-app` at `/admin/assethandler` (BOFF-3362).\n *\n * Deliberately does NOT reuse `AssetHandlerProvider`: that provider hydrates\n * the whole product store (thirteen bootstrap queries) which an operations\n * console never needs. This provider only builds the workspace-gateway Apollo\n * client and exposes the host context to the admin page.\n */\nimport type { FC, PropsWithChildren } from 'react';\nimport { useMemo } from 'react';\nimport { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';\nimport { ApolloProvider } from '@apollo/client/react';\n\nimport { AdminContext, type AdminRootProps } from './AdminContext';\nimport { AdminPage } from './AdminPage';\n\nexport type { AdminRootProps } from './AdminContext';\n\nconst AdminProvider: FC<PropsWithChildren<AdminRootProps>> = ({ children, ...props }) => {\n const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId } = props;\n\n const client = useMemo(\n () =>\n new ApolloClient({\n link: new HttpLink({\n uri: apiGatewayUrl ? `${apiGatewayUrl}/workspaces/graphql` : '/workspaces/graphql',\n headers: {\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n ...(workspaceToken ? { 'X-Workspace-Authorization': `Bearer ${workspaceToken}` } : {}),\n ...(workspaceId ? { 'x-workspace-id': workspaceId } : {}),\n ...(tenantId ? { 'x-tenant-id': tenantId } : {}),\n },\n }),\n cache: new InMemoryCache(),\n }),\n [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId]\n );\n\n // No router here on purpose: admin-app already renders this inside its own\n // <Router>, and nesting another one throws. The page needs no routing —\n // navigation to other admin surfaces goes through the host's `navigate` prop.\n return (\n <ApolloProvider client={client}>\n <AdminContext.Provider value={props}>{children}</AdminContext.Provider>\n </ApolloProvider>\n );\n};\n\nexport const AdminRoot: FC<AdminRootProps> = (props) => (\n <AdminProvider {...props}>\n <AdminPage />\n </AdminProvider>\n);\n"],"mappings":";;;;;;;AAqBA,IAAM,KAAwD,EAAE,aAAU,GAAG,QAAY;CACvF,IAAM,EAAE,kBAAe,cAAW,mBAAgB,gBAAa,gBAAa;CAsB5E,OACE,kBAAC,GAAD;EAAwB,QArBX,QAEX,IAAI,EAAa;GACf,MAAM,IAAI,EAAS;IACjB,KAAK,IAAgB,GAAG,EAAc,uBAAuB;IAC7D,SAAS;KACP,GAAI,IAAY,EAAE,eAAe,UAAU,IAAY,IAAI,CAAC;KAC5D,GAAI,IAAiB,EAAE,6BAA6B,UAAU,IAAiB,IAAI,CAAC;KACpF,GAAI,IAAc,EAAE,kBAAkB,EAAY,IAAI,CAAC;KACvD,GAAI,IAAW,EAAE,eAAe,EAAS,IAAI,CAAC;IAChD;GACF,CAAC;GACD,OAAO,IAAI,EAAc;EAC3B,CAAC,GACH;GAAC;GAAe;GAAW;GAAgB;GAAa;EAAQ,CAOxC;YACtB,kBAAC,EAAa,UAAd;GAAuB,OAAO;GAAQ;EAAgC,CAAA;CACxD,CAAA;AAEpB,GAEa,KAAiC,MAC5C,kBAAC,GAAD;CAAe,GAAI;WACjB,kBAAC,GAAD,CAAY,CAAA;AACC,CAAA"}
@@ -3,7 +3,6 @@ export declare const ADMIN_FEATURE_FLAGS: import('graphql').DocumentNode;
3
3
  export declare const ADMIN_QUOTA_OVERVIEW: import('graphql').DocumentNode;
4
4
  export declare const SET_FEATURE_FLAG_OVERRIDE: import('graphql').DocumentNode;
5
5
  export declare const REMOVE_FEATURE_FLAG_OVERRIDE: import('graphql').DocumentNode;
6
- export declare const OVERRIDE_QUOTA: import('graphql').DocumentNode;
7
6
  export interface AdminWorkspaceState {
8
7
  workspaceId: string;
9
8
  assetCount: number;
@@ -52,10 +51,25 @@ export declare const ASSETHANDLER_QUOTAS: ReadonlyArray<{
52
51
  }>;
53
52
  /** Human label for a quota name; falls back to the raw name for unknown ones. */
54
53
  export declare function quotaLabel(name: string): string;
54
+ /** One line per quota: every active plan row for that quota folded together. */
55
+ export interface QuotaSummary {
56
+ quotaName: string;
57
+ label: string;
58
+ /** Effective limit; `null` when the quota is unlimited. */
59
+ limit: number | null;
60
+ unlimited: boolean;
61
+ used: number;
62
+ resetPeriod: string;
63
+ }
55
64
  /**
56
- * Parse the admin-entered quota limit. Returns `null` for anything that is not
57
- * a whole number of zero or more, so the form can refuse it before it reaches
58
- * the backend (which validates again).
65
+ * Fold a workspace's active quota assignments into one line per quota.
66
+ *
67
+ * A workspace can hold several active assignments for the same quota (one per
68
+ * subscription). The quota service enforces their combined total: the limits
69
+ * add up, and any unlimited assignment makes the whole quota unlimited. This
70
+ * mirrors that rule so the admin sees the limit that is actually enforced, not
71
+ * a row per subscription. Quotas AssetHandler knows come first, in a stable
72
+ * order; anything else follows alphabetically.
59
73
  */
60
- export declare function parseQuotaLimit(raw: string): number | null;
74
+ export declare function summarizeQuotas(assignments: readonly AdminQuotaAssignment[]): QuotaSummary[];
61
75
  //# sourceMappingURL=adminOperations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adminOperations.d.ts","sourceRoot":"","sources":["../../../src/assethandler/admin/adminOperations.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,qBAAqB,gCAYjC,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAY/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,gCAiBhC,CAAC;AAEF,eAAO,MAAM,yBAAyB,gCAUrC,CAAC;AAEF,eAAO,MAAM,4BAA4B,gCAIxC,CAAC;AAEF,eAAO,MAAM,cAAc,gCAW1B,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,+BAA+B,EAAE,mBAAmB,CAAC;CACtD;AAED,MAAM,WAAW,qBAAqB;IACpC,wBAAwB,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,gBAAgB,EAAE,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,sBAAsB;IACrC,kCAAkC,EAAE;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,oBAAoB,EAAE,CAAC;KACrC,CAAC;CACH;AAED,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAS9E,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK1D"}
1
+ {"version":3,"file":"adminOperations.d.ts","sourceRoot":"","sources":["../../../src/assethandler/admin/adminOperations.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,qBAAqB,gCAYjC,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAY/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,gCAiBhC,CAAC;AAEF,eAAO,MAAM,yBAAyB,gCAUrC,CAAC;AAEF,eAAO,MAAM,4BAA4B,gCAIxC,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,+BAA+B,EAAE,mBAAmB,CAAC;CACtD;AAED,MAAM,WAAW,qBAAqB;IACpC,wBAAwB,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,gBAAgB,EAAE,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,sBAAsB;IACrC,kCAAkC,EAAE;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,oBAAoB,EAAE,CAAC;KACrC,CAAC;CACH;AAED,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAS9E,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,SAAS,oBAAoB,EAAE,GAAG,YAAY,EAAE,CA2B5F"}
@@ -55,18 +55,7 @@ var t = e`
55
55
  mutation RemoveAssetHandlerFeatureFlagOverride($workspaceId: ID!, $key: String!) {
56
56
  removeAssetHandlerFeatureFlagOverride(workspaceId: $workspaceId, key: $key)
57
57
  }
58
- `, o = e`
59
- mutation OverrideAssetHandlerWorkspaceQuota($input: OverrideAssetHandlerWorkspaceQuotaInput!) {
60
- overrideAssetHandlerWorkspaceQuota(input: $input) {
61
- id
62
- workspaceId
63
- quotaName
64
- limit
65
- used
66
- noLimit
67
- }
68
- }
69
- `, s = [
58
+ `, o = [
70
59
  {
71
60
  name: "assethandler.assets.count",
72
61
  label: "Active assets (asset limit)"
@@ -100,16 +89,30 @@ var t = e`
100
89
  label: "ESG metrics / month"
101
90
  }
102
91
  ];
103
- function c(e) {
104
- return s.find((t) => t.name === e)?.label ?? e;
92
+ function s(e) {
93
+ return o.find((t) => t.name === e)?.label ?? e;
105
94
  }
106
- function l(e) {
107
- let t = e.trim();
108
- if (!/^\d+$/.test(t)) return null;
109
- let n = Number(t);
110
- return Number.isSafeInteger(n) ? n : null;
95
+ function c(e) {
96
+ let t = /* @__PURE__ */ new Map();
97
+ for (let n of e) {
98
+ if (!n.isActive) continue;
99
+ let e = t.get(n.quotaName);
100
+ e ? e.push(n) : t.set(n.quotaName, [n]);
101
+ }
102
+ let n = o.map((e) => e.name);
103
+ return [...n.filter((e) => t.has(e)), ...[...t.keys()].filter((e) => !n.includes(e)).sort()].map((e) => {
104
+ let n = t.get(e) ?? [], r = n.some((e) => e.noLimit || e.limit === null);
105
+ return {
106
+ quotaName: e,
107
+ label: s(e),
108
+ limit: r ? null : n.reduce((e, t) => e + (t.limit ?? 0), 0),
109
+ unlimited: r,
110
+ used: n.reduce((e, t) => e + t.used, 0),
111
+ resetPeriod: [...new Set(n.map((e) => e.resetPeriod))].join(" / ")
112
+ };
113
+ });
111
114
  }
112
115
  //#endregion
113
- export { n as ADMIN_FEATURE_FLAGS, r as ADMIN_QUOTA_OVERVIEW, t as ADMIN_WORKSPACE_STATE, s as ASSETHANDLER_QUOTAS, o as OVERRIDE_QUOTA, a as REMOVE_FEATURE_FLAG_OVERRIDE, i as SET_FEATURE_FLAG_OVERRIDE, l as parseQuotaLimit, c as quotaLabel };
116
+ export { n as ADMIN_FEATURE_FLAGS, r as ADMIN_QUOTA_OVERVIEW, t as ADMIN_WORKSPACE_STATE, a as REMOVE_FEATURE_FLAG_OVERRIDE, i as SET_FEATURE_FLAG_OVERRIDE, c as summarizeQuotas };
114
117
 
115
118
  //# sourceMappingURL=adminOperations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"adminOperations.js","names":[],"sources":["../../../src/assethandler/admin/adminOperations.ts"],"sourcesContent":["/**\n * GraphQL operations for the AssetHandler admin surface (BOFF-3362).\n *\n * Served by `wspace-assethandler-svc`'s admin module (super-admin only).\n * Typed locally rather than through codegen so this surface does not depend on\n * the composed supergraph snapshot being refreshed first.\n */\nimport { gql } from '@apollo/client';\n\nexport const ADMIN_WORKSPACE_STATE = gql`\n query AssetHandlerAdminWorkspaceState($workspaceId: ID!) {\n assetHandlerAdminWorkspaceState(workspaceId: $workspaceId) {\n workspaceId\n assetCount\n openIncidentCount\n openWorkOrderCount\n maintenancePlanCount\n discoveryJobCount\n generatedAt\n }\n }\n`;\n\nexport const ADMIN_FEATURE_FLAGS = gql`\n query AssetHandlerFeatureFlags($workspaceId: ID!) {\n assetHandlerFeatureFlags(workspaceId: $workspaceId) {\n workspaceId\n items {\n key\n defaultValue\n value\n category\n }\n }\n }\n`;\n\nexport const ADMIN_QUOTA_OVERVIEW = gql`\n query AssetHandlerWorkspaceQuotaOverview($workspaceId: ID!) {\n assetHandlerWorkspaceQuotaOverview(workspaceId: $workspaceId) {\n workspaceId\n assignments {\n id\n quotaName\n productId\n limit\n used\n noLimit\n resetPeriod\n isActive\n updatedAt\n }\n }\n }\n`;\n\nexport const SET_FEATURE_FLAG_OVERRIDE = gql`\n mutation SetAssetHandlerFeatureFlagOverride($input: SetAssetHandlerFeatureFlagOverrideInput!) {\n setAssetHandlerFeatureFlagOverride(input: $input) {\n id\n featureFlagId\n workspaceId\n enabled\n allowUserOptIn\n }\n }\n`;\n\nexport const REMOVE_FEATURE_FLAG_OVERRIDE = gql`\n mutation RemoveAssetHandlerFeatureFlagOverride($workspaceId: ID!, $key: String!) {\n removeAssetHandlerFeatureFlagOverride(workspaceId: $workspaceId, key: $key)\n }\n`;\n\nexport const OVERRIDE_QUOTA = gql`\n mutation OverrideAssetHandlerWorkspaceQuota($input: OverrideAssetHandlerWorkspaceQuotaInput!) {\n overrideAssetHandlerWorkspaceQuota(input: $input) {\n id\n workspaceId\n quotaName\n limit\n used\n noLimit\n }\n }\n`;\n\nexport interface AdminWorkspaceState {\n workspaceId: string;\n assetCount: number;\n openIncidentCount: number;\n openWorkOrderCount: number;\n maintenancePlanCount: number;\n discoveryJobCount: number;\n generatedAt: string;\n}\n\nexport interface AdminFeatureFlag {\n key: string;\n defaultValue: boolean;\n value: boolean;\n category: string;\n}\n\nexport interface AdminQuotaAssignment {\n id: string;\n quotaName: string;\n productId: string;\n limit: number | null;\n used: number;\n noLimit: boolean;\n resetPeriod: string;\n isActive: boolean;\n updatedAt: string;\n}\n\nexport interface AdminWorkspaceStateData {\n assetHandlerAdminWorkspaceState: AdminWorkspaceState;\n}\n\nexport interface AdminFeatureFlagsData {\n assetHandlerFeatureFlags: { workspaceId: string; items: AdminFeatureFlag[] };\n}\n\nexport interface AdminQuotaOverviewData {\n assetHandlerWorkspaceQuotaOverview: {\n workspaceId: string;\n assignments: AdminQuotaAssignment[];\n };\n}\n\n/** Quotas AssetHandler enforces, with admin-facing labels. */\nexport const ASSETHANDLER_QUOTAS: ReadonlyArray<{ name: string; label: string }> = [\n { name: 'assethandler.assets.count', label: 'Active assets (asset limit)' },\n { name: 'assethandler.work_orders.open', label: 'Open work orders' },\n { name: 'assethandler.cmdb.configuration_items', label: 'CMDB configuration items' },\n { name: 'assethandler.discovery.jobs.monthly', label: 'Discovery jobs / month' },\n { name: 'assethandler.incidents.monthly', label: 'Incidents / month' },\n { name: 'assethandler.maintenance_records.monthly', label: 'Maintenance records / month' },\n { name: 'assethandler.knowledge_articles.monthly', label: 'Knowledge articles / month' },\n { name: 'assethandler.esg_metrics.monthly', label: 'ESG metrics / month' },\n];\n\n/** Human label for a quota name; falls back to the raw name for unknown ones. */\nexport function quotaLabel(name: string): string {\n return ASSETHANDLER_QUOTAS.find((quota) => quota.name === name)?.label ?? name;\n}\n\n/**\n * Parse the admin-entered quota limit. Returns `null` for anything that is not\n * a whole number of zero or more, so the form can refuse it before it reaches\n * the backend (which validates again).\n */\nexport function parseQuotaLimit(raw: string): number | null {\n const trimmed = raw.trim();\n if (!/^\\d+$/.test(trimmed)) return null;\n const value = Number(trimmed);\n return Number.isSafeInteger(value) ? value : null;\n}\n"],"mappings":";;AASA,IAAa,IAAwB,CAAG;;;;;;;;;;;;GAc3B,IAAsB,CAAG;;;;;;;;;;;;GAczB,IAAuB,CAAG;;;;;;;;;;;;;;;;;GAmB1B,IAA4B,CAAG;;;;;;;;;;GAY/B,IAA+B,CAAG;;;;GAMlC,IAAiB,CAAG;;;;;;;;;;;GA0DpB,IAAsE;CACjF;EAAE,MAAM;EAA6B,OAAO;CAA8B;CAC1E;EAAE,MAAM;EAAiC,OAAO;CAAmB;CACnE;EAAE,MAAM;EAAyC,OAAO;CAA2B;CACnF;EAAE,MAAM;EAAuC,OAAO;CAAyB;CAC/E;EAAE,MAAM;EAAkC,OAAO;CAAoB;CACrE;EAAE,MAAM;EAA4C,OAAO;CAA8B;CACzF;EAAE,MAAM;EAA2C,OAAO;CAA6B;CACvF;EAAE,MAAM;EAAoC,OAAO;CAAsB;AAC3E;AAGA,SAAgB,EAAW,GAAsB;CAC/C,OAAO,EAAoB,MAAM,MAAU,EAAM,SAAS,CAAI,GAAG,SAAS;AAC5E;AAOA,SAAgB,EAAgB,GAA4B;CAC1D,IAAM,IAAU,EAAI,KAAK;CACzB,IAAI,CAAC,QAAQ,KAAK,CAAO,GAAG,OAAO;CACnC,IAAM,IAAQ,OAAO,CAAO;CAC5B,OAAO,OAAO,cAAc,CAAK,IAAI,IAAQ;AAC/C"}
1
+ {"version":3,"file":"adminOperations.js","names":[],"sources":["../../../src/assethandler/admin/adminOperations.ts"],"sourcesContent":["/**\n * GraphQL operations for the AssetHandler admin surface (BOFF-3362).\n *\n * Served by `wspace-assethandler-svc`'s admin module (super-admin only).\n * Typed locally rather than through codegen so this surface does not depend on\n * the composed supergraph snapshot being refreshed first.\n */\nimport { gql } from '@apollo/client';\n\nexport const ADMIN_WORKSPACE_STATE = gql`\n query AssetHandlerAdminWorkspaceState($workspaceId: ID!) {\n assetHandlerAdminWorkspaceState(workspaceId: $workspaceId) {\n workspaceId\n assetCount\n openIncidentCount\n openWorkOrderCount\n maintenancePlanCount\n discoveryJobCount\n generatedAt\n }\n }\n`;\n\nexport const ADMIN_FEATURE_FLAGS = gql`\n query AssetHandlerFeatureFlags($workspaceId: ID!) {\n assetHandlerFeatureFlags(workspaceId: $workspaceId) {\n workspaceId\n items {\n key\n defaultValue\n value\n category\n }\n }\n }\n`;\n\nexport const ADMIN_QUOTA_OVERVIEW = gql`\n query AssetHandlerWorkspaceQuotaOverview($workspaceId: ID!) {\n assetHandlerWorkspaceQuotaOverview(workspaceId: $workspaceId) {\n workspaceId\n assignments {\n id\n quotaName\n productId\n limit\n used\n noLimit\n resetPeriod\n isActive\n updatedAt\n }\n }\n }\n`;\n\nexport const SET_FEATURE_FLAG_OVERRIDE = gql`\n mutation SetAssetHandlerFeatureFlagOverride($input: SetAssetHandlerFeatureFlagOverrideInput!) {\n setAssetHandlerFeatureFlagOverride(input: $input) {\n id\n featureFlagId\n workspaceId\n enabled\n allowUserOptIn\n }\n }\n`;\n\nexport const REMOVE_FEATURE_FLAG_OVERRIDE = gql`\n mutation RemoveAssetHandlerFeatureFlagOverride($workspaceId: ID!, $key: String!) {\n removeAssetHandlerFeatureFlagOverride(workspaceId: $workspaceId, key: $key)\n }\n`;\n\nexport interface AdminWorkspaceState {\n workspaceId: string;\n assetCount: number;\n openIncidentCount: number;\n openWorkOrderCount: number;\n maintenancePlanCount: number;\n discoveryJobCount: number;\n generatedAt: string;\n}\n\nexport interface AdminFeatureFlag {\n key: string;\n defaultValue: boolean;\n value: boolean;\n category: string;\n}\n\nexport interface AdminQuotaAssignment {\n id: string;\n quotaName: string;\n productId: string;\n limit: number | null;\n used: number;\n noLimit: boolean;\n resetPeriod: string;\n isActive: boolean;\n updatedAt: string;\n}\n\nexport interface AdminWorkspaceStateData {\n assetHandlerAdminWorkspaceState: AdminWorkspaceState;\n}\n\nexport interface AdminFeatureFlagsData {\n assetHandlerFeatureFlags: { workspaceId: string; items: AdminFeatureFlag[] };\n}\n\nexport interface AdminQuotaOverviewData {\n assetHandlerWorkspaceQuotaOverview: {\n workspaceId: string;\n assignments: AdminQuotaAssignment[];\n };\n}\n\n/** Quotas AssetHandler enforces, with admin-facing labels. */\nexport const ASSETHANDLER_QUOTAS: ReadonlyArray<{ name: string; label: string }> = [\n { name: 'assethandler.assets.count', label: 'Active assets (asset limit)' },\n { name: 'assethandler.work_orders.open', label: 'Open work orders' },\n { name: 'assethandler.cmdb.configuration_items', label: 'CMDB configuration items' },\n { name: 'assethandler.discovery.jobs.monthly', label: 'Discovery jobs / month' },\n { name: 'assethandler.incidents.monthly', label: 'Incidents / month' },\n { name: 'assethandler.maintenance_records.monthly', label: 'Maintenance records / month' },\n { name: 'assethandler.knowledge_articles.monthly', label: 'Knowledge articles / month' },\n { name: 'assethandler.esg_metrics.monthly', label: 'ESG metrics / month' },\n];\n\n/** Human label for a quota name; falls back to the raw name for unknown ones. */\nexport function quotaLabel(name: string): string {\n return ASSETHANDLER_QUOTAS.find((quota) => quota.name === name)?.label ?? name;\n}\n\n/** One line per quota: every active plan row for that quota folded together. */\nexport interface QuotaSummary {\n quotaName: string;\n label: string;\n /** Effective limit; `null` when the quota is unlimited. */\n limit: number | null;\n unlimited: boolean;\n used: number;\n resetPeriod: string;\n}\n\n/**\n * Fold a workspace's active quota assignments into one line per quota.\n *\n * A workspace can hold several active assignments for the same quota (one per\n * subscription). The quota service enforces their combined total: the limits\n * add up, and any unlimited assignment makes the whole quota unlimited. This\n * mirrors that rule so the admin sees the limit that is actually enforced, not\n * a row per subscription. Quotas AssetHandler knows come first, in a stable\n * order; anything else follows alphabetically.\n */\nexport function summarizeQuotas(assignments: readonly AdminQuotaAssignment[]): QuotaSummary[] {\n const byQuota = new Map<string, AdminQuotaAssignment[]>();\n for (const assignment of assignments) {\n if (!assignment.isActive) continue;\n const rows = byQuota.get(assignment.quotaName);\n if (rows) rows.push(assignment);\n else byQuota.set(assignment.quotaName, [assignment]);\n }\n\n const known = ASSETHANDLER_QUOTAS.map((quota) => quota.name);\n const names = [\n ...known.filter((name) => byQuota.has(name)),\n ...[...byQuota.keys()].filter((name) => !known.includes(name)).sort(),\n ];\n\n return names.map((quotaName) => {\n const rows = byQuota.get(quotaName) ?? [];\n const unlimited = rows.some((row) => row.noLimit || row.limit === null);\n return {\n quotaName,\n label: quotaLabel(quotaName),\n limit: unlimited ? null : rows.reduce((sum, row) => sum + (row.limit ?? 0), 0),\n unlimited,\n used: rows.reduce((sum, row) => sum + row.used, 0),\n resetPeriod: [...new Set(rows.map((row) => row.resetPeriod))].join(' / '),\n };\n });\n}\n"],"mappings":";;AASA,IAAa,IAAwB,CAAG;;;;;;;;;;;;GAc3B,IAAsB,CAAG;;;;;;;;;;;;GAczB,IAAuB,CAAG;;;;;;;;;;;;;;;;;GAmB1B,IAA4B,CAAG;;;;;;;;;;GAY/B,IAA+B,CAAG;;;;GAmDlC,IAAsE;CACjF;EAAE,MAAM;EAA6B,OAAO;CAA8B;CAC1E;EAAE,MAAM;EAAiC,OAAO;CAAmB;CACnE;EAAE,MAAM;EAAyC,OAAO;CAA2B;CACnF;EAAE,MAAM;EAAuC,OAAO;CAAyB;CAC/E;EAAE,MAAM;EAAkC,OAAO;CAAoB;CACrE;EAAE,MAAM;EAA4C,OAAO;CAA8B;CACzF;EAAE,MAAM;EAA2C,OAAO;CAA6B;CACvF;EAAE,MAAM;EAAoC,OAAO;CAAsB;AAC3E;AAGA,SAAgB,EAAW,GAAsB;CAC/C,OAAO,EAAoB,MAAM,MAAU,EAAM,SAAS,CAAI,GAAG,SAAS;AAC5E;AAuBA,SAAgB,EAAgB,GAA8D;CAC5F,IAAM,oBAAU,IAAI,IAAoC;CACxD,KAAK,IAAM,KAAc,GAAa;EACpC,IAAI,CAAC,EAAW,UAAU;EAC1B,IAAM,IAAO,EAAQ,IAAI,EAAW,SAAS;EAC7C,AAAI,IAAM,EAAK,KAAK,CAAU,IACzB,EAAQ,IAAI,EAAW,WAAW,CAAC,CAAU,CAAC;CACrD;CAEA,IAAM,IAAQ,EAAoB,KAAK,MAAU,EAAM,IAAI;CAM3D,OAAO,CAJL,GAAG,EAAM,QAAQ,MAAS,EAAQ,IAAI,CAAI,CAAC,GAC3C,GAAG,CAAC,GAAG,EAAQ,KAAK,CAAC,EAAE,QAAQ,MAAS,CAAC,EAAM,SAAS,CAAI,CAAC,EAAE,KAAK,CAG/D,EAAM,KAAK,MAAc;EAC9B,IAAM,IAAO,EAAQ,IAAI,CAAS,KAAK,CAAC,GAClC,IAAY,EAAK,MAAM,MAAQ,EAAI,WAAW,EAAI,UAAU,IAAI;EACtE,OAAO;GACL;GACA,OAAO,EAAW,CAAS;GAC3B,OAAO,IAAY,OAAO,EAAK,QAAQ,GAAK,MAAQ,KAAO,EAAI,SAAS,IAAI,CAAC;GAC7E;GACA,MAAM,EAAK,QAAQ,GAAK,MAAQ,IAAM,EAAI,MAAM,CAAC;GACjD,aAAa,CAAC,GAAG,IAAI,IAAI,EAAK,KAAK,MAAQ,EAAI,WAAW,CAAC,CAAC,EAAE,KAAK,KAAK;EAC1E;CACF,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-assethandler",
3
- "version": "2026.1103.1",
3
+ "version": "2026.1103.3",
4
4
  "description": "Burdenoff AssetHandler Microfrontend",
5
5
  "type": "module",
6
6
  "files": [