@burdenoff/fluidgrids-fe-sdk 2026.1111.16 → 2026.1111.17

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.
Files changed (112) hide show
  1. package/README.md +41 -0
  2. package/dist/AiStudioPage-kJHSsIcj.js +774 -0
  3. package/dist/ApprovalInboxPage-dbBDSJ-f.js +558 -0
  4. package/dist/DataSinkDashboardPage-1h2QsMzH.js +270 -0
  5. package/dist/DataTablesPage-BZy2z0LB.js +1690 -0
  6. package/dist/EntityTagPicker-BqRNsLCQ.js +135 -0
  7. package/dist/{ExecutionDetailPage-D-0_POAf.js → ExecutionDetailPage-BdcZmLT4.js} +799 -4420
  8. package/dist/ExecutionDetailPage-DvQ667OE.d.ts +10 -0
  9. package/dist/ExecutionsListPage-BCUJoAIE.js +2742 -0
  10. package/dist/{FailureTriagePanel-BHiduL7A.js → FailureTriagePanel-B9hDQymi.js} +4 -97
  11. package/dist/{FluidGridsProvider-DmvFADFa.js → FluidGridsProvider-dvZSNtSW.js} +10 -350
  12. package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
  13. package/dist/{HistoryPage-BbUgxx1I.js → HistoryPage-U0tqupBN.js} +6 -4
  14. package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
  15. package/dist/{InsightsPage-s6f5bU-8.js → InsightsPage-CALo16eB.js} +17 -328
  16. package/dist/ManagedAiPolicyAuthority-BxwJ3ptd.js +40 -0
  17. package/dist/{ManagedAiPolicyAuthority-CGux_DaV.d.ts → ManagedAiPolicyAuthority-DM3t3wNr.d.ts} +1 -5
  18. package/dist/ManagedAiPolicyAuthorityContext-CNkxIOZ1.js +10 -0
  19. package/dist/{MissingNodesInstallModal-B-9gHy2K.js → MissingNodesInstallModal-CMybsI8h.js} +3 -2
  20. package/dist/{NodesPage-ClvhUfbh.js → NodesPage-Bf5PhfBK.js} +7 -3
  21. package/dist/NodesPage-DEbnz9iU.d.ts +6 -0
  22. package/dist/{PublishCustomNodeAction-D2F7oN0S.js → PublishCustomNodeAction-5XXnSA3H.js} +3 -45
  23. package/dist/QuotaExhaustedDialog-7IFSaLyi.js +54 -0
  24. package/dist/RunnerStatusPill-Ctg4TJcP.js +187 -0
  25. package/dist/RunnersPage-CLCOCclc.js +467 -0
  26. package/dist/{ManagedAiPolicyAuthority-DsSaz-vg.js → SettingsPage-CgkRlCM5.js} +19 -50
  27. package/dist/SettingsPage-DMHQFj5-.d.ts +9 -0
  28. package/dist/TagFilterSelect-DCBOH6Ou.js +41 -0
  29. package/dist/TaskQueuesPage-9VgSnLdL.js +951 -0
  30. package/dist/TemplateCard-DsC0KNH1.js +1416 -0
  31. package/dist/{TemplateConfigurationModal-D_Mvgxqa.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
  32. package/dist/{TemplateDetailPanel-DfFfUcOe.js → TemplateDetailPanel-D4cUVr-m.js} +3 -3
  33. package/dist/TemplatePreview-GthzCOSo.js +231 -0
  34. package/dist/TemplatesPage-BG3H-6v6.d.ts +10 -0
  35. package/dist/{TemplatesPage-Cku13bav.js → TemplatesPage-CsDNT54N.js} +11 -228
  36. package/dist/VariablesPage-67WR_lVE.js +949 -0
  37. package/dist/{VersionDiffDialog-Dq2xlPFI.js → VersionDiffDialog-DSN0mU0f.js} +2 -1
  38. package/dist/WorkflowBuilder-DEi_9YOb.js +555 -0
  39. package/dist/{WorkflowSearchRedirect-CaoOOpEl.js → WorkflowBuilder-iPqXgbYr.js} +19 -585
  40. package/dist/{WorkflowProposalCard-8Vr56kQa.js → WorkflowProposalCard-qrEeNyYG.js} +4 -405
  41. package/dist/WorkflowSearchRedirect-DwDNZ_q5.js +36 -0
  42. package/dist/{WorkflowShareDialog-6RRDe-_v.js → WorkflowShareDialog-B_gn968c.js} +6 -52
  43. package/dist/{WorkflowsList-B2j1Pehb.js → WorkflowsList-CA20dVIl.js} +11 -6
  44. package/dist/WorkflowsList-CcNDQge5.d.ts +6 -0
  45. package/dist/agent-form-Bfvm32j1.js +83 -0
  46. package/dist/{agentTools-DJ5o2ekg.js → agentTools-CkRJB7r5.js} +3 -2
  47. package/dist/{ai-usage-D5p7ZeRR.js → ai-usage-glCwHyQz.js} +1 -1
  48. package/dist/api-BrCHB5oA.js +224 -0
  49. package/dist/api-C8e0iV-Q.js +55 -0
  50. package/dist/button-DG7y_FVJ.js +48 -0
  51. package/dist/{canvasNoteTools-D3zUdEeg.js → canvasNoteTools-CQIc7Oxg.js} +2 -2
  52. package/dist/client-C9XSzSXr.js +1147 -0
  53. package/dist/{codec-client-DstqyzTN.js → codec-client-CJ95Rmqo.js} +1 -1
  54. package/dist/constants-B2lhWNoN.js +202 -0
  55. package/dist/constants-XdvUxeWK.d.ts +162 -0
  56. package/dist/dialog-Cjb4lB4D.js +105 -0
  57. package/dist/executions.d.ts +1 -1
  58. package/dist/executions.js +2 -1
  59. package/dist/feature-flags-DM5myWaD.js +296 -0
  60. package/dist/flags.d.ts +3 -0
  61. package/dist/flags.js +4 -0
  62. package/dist/{flow-builder-DEzT8TQu.js → flow-builder-y2Y1iG4Y.js} +70 -1731
  63. package/dist/{node-endpoint-configs-DHFGA5MQ.js → graphql-operation-types-kX-MMjgW.js} +1 -1194
  64. package/dist/{graphql-types-XcDLtFwU.js → graphql-types-CDKVWH2R.js} +2 -202
  65. package/dist/history.d.ts +1 -1
  66. package/dist/history.js +1 -1
  67. package/dist/index-72XFgvbn.d.ts +132 -0
  68. package/dist/index-BcdtKBM-.d.ts +56 -0
  69. package/dist/{index-B3BWqafN.d.ts → index-DjiFgEQl.d.ts} +5 -131
  70. package/dist/{index-DRf25-D3.d.ts → index-W2o__Nm6.d.ts} +32 -214
  71. package/dist/index.d.ts +13 -9
  72. package/dist/index.js +50 -5300
  73. package/dist/{node-detail-view-LNqeIqN-.js → node-detail-view-B91kXzIy.js} +11 -8
  74. package/dist/node-endpoint-configs-B5bjNMDW.js +37 -0
  75. package/dist/node-registry-refresh-Dy0uRg9f.js +84 -0
  76. package/dist/nodes.d.ts +1 -1
  77. package/dist/nodes.js +1 -1
  78. package/dist/observability.d.ts +1 -1
  79. package/dist/observability.js +2 -1
  80. package/dist/{quick-config-panel-D3ogsaFL.js → quick-config-panel-DggPl1vY.js} +2 -2
  81. package/dist/rolldown-runtime-hDbaFkMy.js +18 -0
  82. package/dist/run-display-BycKYSU0.js +46 -0
  83. package/dist/run-list-links-ByYC0OlV.js +98 -0
  84. package/dist/settings.d.ts +2 -1
  85. package/dist/settings.js +2 -1
  86. package/dist/shared-CMfn30xT.js +196 -0
  87. package/dist/templates.d.ts +1 -1
  88. package/dist/templates.js +2 -1
  89. package/dist/theme-provider-B3mIuz2d.js +11 -0
  90. package/dist/{useAgentDefinitions-NFtLytDR.js → useAgentDefinitions-Dpz0HDTq.js} +2 -1
  91. package/dist/useDurableParity-CzUQMFJ3.js +415 -0
  92. package/dist/useEntityTags-DhnsZcTl.js +236 -0
  93. package/dist/{useExecutions-DAzeWHS6.js → useExecutions-9aLuOt1c.js} +4 -3
  94. package/dist/{useObservability-COCZ0hUS.js → useObservability-CfEw3ZFD.js} +2 -2
  95. package/dist/{useRunHistory-CsYUbouO.js → useRunHistory-B1xJDwl1.js} +4 -3
  96. package/dist/{useWorkflows-w4Dx2fF2.js → useWorkflows-BlVVu8NM.js} +3 -2
  97. package/dist/utils-uaL5UVxb.js +65 -0
  98. package/dist/workflow-builder.d.ts +1 -1
  99. package/dist/workflow-builder.js +4 -2
  100. package/dist/workflow-host.d.ts +6 -0
  101. package/dist/workflow-host.js +9 -0
  102. package/dist/workflow-pages.d.ts +55 -0
  103. package/dist/workflow-pages.js +113 -0
  104. package/dist/workflows-list.d.ts +1 -1
  105. package/dist/workflows-list.js +1 -1
  106. package/package.json +13 -1
  107. package/dist/ExecutionDetailPage-MvsnOnE1.d.ts +0 -4
  108. package/dist/HistoryPage-BqUizaFv.d.ts +0 -3
  109. package/dist/NodesPage-C97-9K2Y.d.ts +0 -3
  110. package/dist/TemplatesPage-D0qnXzHh.d.ts +0 -4
  111. package/dist/WorkflowsList-0d5GSKdh.d.ts +0 -3
  112. package/dist/agent-form-BvMWhQ42.js +0 -496
@@ -0,0 +1,951 @@
1
+ "use client";
2
+ import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
3
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
4
+ import { m as describeOperationError } from "./client-C9XSzSXr.js";
5
+ import { p as useSelfHostedWorkersEnabled } from "./feature-flags-DM5myWaD.js";
6
+ import { s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
7
+ import { a as RUN_CONTROL_DIALOG_STYLE, i as NativeSelect, o as StatusPill, t as FieldError, u as useResource } from "./shared-CMfn30xT.js";
8
+ import { a as fetchTaskQueueStats, c as fetchWorkflowWorkers, d as updateTaskQueue, i as deleteWorkerToken, l as rotateWorkerToken, n as createWorkerToken, o as fetchTaskQueues, r as deleteTaskQueue, s as fetchWorkerTokens, t as createTaskQueue, u as setWorkerTokenConnectionAllowlist } from "./api-C8e0iV-Q.js";
9
+ import { SF as WorkflowEnvironment } from "./graphql-types-CDKVWH2R.js";
10
+ import { useState } from "react";
11
+ import { Icon } from "@iconify/react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ import { toast } from "sonner";
14
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Textarea } from "@burdenoff/fe-libs/ui";
15
+ import { Button as Button$1 } from "@burdenoff/fe-libs/button";
16
+
17
+ // src/modules/task-queues/utils/task-queues.ts
18
+ /** Lower-case letters, digits and dashes, at most 63, not `managed`. */
19
+ const TASK_QUEUE_NAME = /^[a-z0-9][a-z0-9-]{0,62}$/;
20
+ function taskQueueNameProblem(name) {
21
+ const value = name.trim();
22
+ if (!value) return "empty";
23
+ if (value === "managed") return "reserved";
24
+ return TASK_QUEUE_NAME.test(value) ? null : "pattern";
25
+ }
26
+ const EMPTY_TASK_QUEUE_FORM = {
27
+ name: "",
28
+ environment: "",
29
+ description: "",
30
+ maxTasksPerSecond: "",
31
+ workerUnavailableTimeoutSeconds: ""
32
+ };
33
+ /** A positive number in range, `undefined` for an empty field, null when invalid. */
34
+ function boundedNumber(text, min, max, integer = false) {
35
+ const value = text.trim();
36
+ if (!value) return void 0;
37
+ const number = Number(value);
38
+ if (!Number.isFinite(number) || number < min || number > max) return null;
39
+ if (integer && !Number.isInteger(number)) return null;
40
+ return number;
41
+ }
42
+ /** The createTaskQueue input from the form (rate 0.01 to 10000, timeout 10 s to 7 days). */
43
+ function taskQueueInputOf(form) {
44
+ if (taskQueueNameProblem(form.name)) return {
45
+ ok: false,
46
+ field: "name"
47
+ };
48
+ const rate = boundedNumber(form.maxTasksPerSecond, .01, 1e4);
49
+ if (rate === null) return {
50
+ ok: false,
51
+ field: "maxTasksPerSecond"
52
+ };
53
+ const timeout = boundedNumber(form.workerUnavailableTimeoutSeconds, 10, 604800, true);
54
+ if (timeout === null) return {
55
+ ok: false,
56
+ field: "workerUnavailableTimeoutSeconds"
57
+ };
58
+ const create = { name: form.name.trim() };
59
+ if (form.environment) create.environment = form.environment;
60
+ if (form.description.trim()) create.description = form.description.trim();
61
+ if (rate !== void 0) create.maxTasksPerSecond = rate;
62
+ if (timeout !== void 0) create.workerUnavailableTimeoutSeconds = timeout;
63
+ return {
64
+ ok: true,
65
+ create
66
+ };
67
+ }
68
+ /** Connection names: one per line or comma separated, unique, in order. */
69
+ function parseNameList(text) {
70
+ const seen = /* @__PURE__ */ new Set();
71
+ const names = [];
72
+ for (const raw of text.split(/[\n,]+/)) {
73
+ const name = raw.trim();
74
+ if (name && !seen.has(name)) {
75
+ seen.add(name);
76
+ names.push(name);
77
+ }
78
+ }
79
+ return names;
80
+ }
81
+ /** "3m ago" style ages for heartbeats. */
82
+ function formatAge(iso, now = Date.now()) {
83
+ if (!iso) return "—";
84
+ const seconds = Math.max(0, Math.round((now - new Date(iso).getTime()) / 1e3));
85
+ if (seconds < 60) return `${seconds}s ago`;
86
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m ago`;
87
+ if (seconds < 86400) return `${Math.round(seconds / 3600)}h ago`;
88
+ return new Date(iso).toLocaleDateString();
89
+ }
90
+ /** The one-line worker start command shown with a new token (no token in it). */
91
+ function workerRunCommand(taskQueues, bridgeUrl) {
92
+ return `FLUIDGRIDS_WORKER_TOKEN=<token> fluidgrids worker run --task-queue ${taskQueues[0] ?? "my-queue"}${bridgeUrl ? ` --bridge-url ${bridgeUrl}` : ""}`;
93
+ }
94
+
95
+ // src/modules/task-queues/components/TaskQueueDialogs.tsx
96
+ /**
97
+ * The Task queues page's dialogs (BOFF-9906): create or edit a queue, issue a
98
+ * worker token (shown once, with its connection allow-list) and edit a
99
+ * token's allow-list. Semantic tokens only; layers at 1100.
100
+ */
101
+ const ENVIRONMENTS = [
102
+ "DEV",
103
+ "STAGING",
104
+ "PROD"
105
+ ];
106
+ function TaskQueueDialog({ queue, onClose, onSaved }) {
107
+ const tr = useTr();
108
+ const [form, setForm] = useState(queue ? {
109
+ name: queue.name,
110
+ environment: queue.environment ?? "",
111
+ description: queue.description ?? "",
112
+ maxTasksPerSecond: queue.maxTasksPerSecond === null || queue.maxTasksPerSecond === void 0 ? "" : String(queue.maxTasksPerSecond),
113
+ workerUnavailableTimeoutSeconds: String(queue.workerUnavailableTimeoutSeconds)
114
+ } : EMPTY_TASK_QUEUE_FORM);
115
+ const [busy, setBusy] = useState(false);
116
+ const [error, setError] = useState(null);
117
+ const checked = taskQueueInputOf(form);
118
+ const nameProblem = form.name ? taskQueueNameProblem(form.name) : null;
119
+ const save = async () => {
120
+ if (!checked.ok) return;
121
+ setBusy(true);
122
+ setError(null);
123
+ try {
124
+ if (queue?.id) {
125
+ const rate = boundedNumber(form.maxTasksPerSecond, .01, 1e4);
126
+ await updateTaskQueue(queue.id, {
127
+ description: form.description.trim(),
128
+ ...rate === void 0 ? { clearMaxTasksPerSecond: true } : { maxTasksPerSecond: rate },
129
+ ...checked.create.workerUnavailableTimeoutSeconds !== void 0 ? { workerUnavailableTimeoutSeconds: checked.create.workerUnavailableTimeoutSeconds } : {}
130
+ });
131
+ } else await createTaskQueue(checked.create);
132
+ onSaved();
133
+ } catch (err) {
134
+ setError(describeOperationError(err));
135
+ } finally {
136
+ setBusy(false);
137
+ }
138
+ };
139
+ return /* @__PURE__ */ jsx(Dialog, {
140
+ open: true,
141
+ onOpenChange: (open) => !open && onClose(),
142
+ children: /* @__PURE__ */ jsxs(DialogContent, {
143
+ style: RUN_CONTROL_DIALOG_STYLE,
144
+ "data-testid": "task-queue-dialog",
145
+ children: [
146
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: queue ? tr("fluidgrids.taskQueues.editTitle", "Edit task queue") : tr("fluidgrids.taskQueues.createTitle", "New task queue") }), /* @__PURE__ */ jsx(DialogDescription, { children: tr("fluidgrids.taskQueues.dialogDescription", "Workflow and activity tasks on a self-hosted queue run on the workers you start for it.") })] }),
147
+ /* @__PURE__ */ jsxs("div", {
148
+ className: "space-y-3",
149
+ children: [
150
+ /* @__PURE__ */ jsxs("div", {
151
+ className: "space-y-1.5",
152
+ children: [
153
+ /* @__PURE__ */ jsx(Label, {
154
+ htmlFor: "task-queue-name",
155
+ children: tr("fluidgrids.taskQueues.name", "Name")
156
+ }),
157
+ /* @__PURE__ */ jsx(Input, {
158
+ id: "task-queue-name",
159
+ value: form.name,
160
+ disabled: !!queue,
161
+ placeholder: "payments",
162
+ onChange: (event) => setForm({
163
+ ...form,
164
+ name: event.target.value
165
+ }),
166
+ "data-testid": "task-queue-name"
167
+ }),
168
+ nameProblem && /* @__PURE__ */ jsx(FieldError, { message: nameProblem === "reserved" ? tr("fluidgrids.taskQueues.nameReserved", "managed is the platform's queue.") : tr("fluidgrids.taskQueues.namePattern", "Lower-case letters, digits and dashes (at most 63).") })
169
+ ]
170
+ }),
171
+ /* @__PURE__ */ jsxs("div", {
172
+ className: "space-y-1.5",
173
+ children: [/* @__PURE__ */ jsx(Label, {
174
+ htmlFor: "task-queue-env",
175
+ children: tr("fluidgrids.taskQueues.environment", "Environment")
176
+ }), /* @__PURE__ */ jsxs(NativeSelect, {
177
+ id: "task-queue-env",
178
+ value: form.environment,
179
+ disabled: !!queue,
180
+ onChange: (value) => setForm({
181
+ ...form,
182
+ environment: ENVIRONMENTS.find((item) => item === value) ?? ""
183
+ }),
184
+ testId: "task-queue-env",
185
+ children: [/* @__PURE__ */ jsx("option", {
186
+ value: "",
187
+ children: tr("fluidgrids.taskQueues.everyEnvironment", "Every environment")
188
+ }), ENVIRONMENTS.map((env) => /* @__PURE__ */ jsx("option", {
189
+ value: env,
190
+ children: env
191
+ }, env))]
192
+ })]
193
+ }),
194
+ /* @__PURE__ */ jsxs("div", {
195
+ className: "space-y-1.5",
196
+ children: [/* @__PURE__ */ jsx(Label, {
197
+ htmlFor: "task-queue-description",
198
+ children: tr("fluidgrids.taskQueues.description", "Description (optional)")
199
+ }), /* @__PURE__ */ jsx(Input, {
200
+ id: "task-queue-description",
201
+ value: form.description,
202
+ maxLength: 1e3,
203
+ onChange: (event) => setForm({
204
+ ...form,
205
+ description: event.target.value
206
+ })
207
+ })]
208
+ }),
209
+ /* @__PURE__ */ jsxs("div", {
210
+ className: "grid gap-3 sm:grid-cols-2",
211
+ children: [/* @__PURE__ */ jsxs("div", {
212
+ className: "space-y-1.5",
213
+ children: [
214
+ /* @__PURE__ */ jsx(Label, {
215
+ htmlFor: "task-queue-rate",
216
+ children: tr("fluidgrids.taskQueues.rate", "Activity tasks per second (optional)")
217
+ }),
218
+ /* @__PURE__ */ jsx(Input, {
219
+ id: "task-queue-rate",
220
+ inputMode: "decimal",
221
+ value: form.maxTasksPerSecond,
222
+ onChange: (event) => setForm({
223
+ ...form,
224
+ maxTasksPerSecond: event.target.value
225
+ }),
226
+ "data-testid": "task-queue-rate"
227
+ }),
228
+ !checked.ok && checked.field === "maxTasksPerSecond" && /* @__PURE__ */ jsx(FieldError, { message: tr("fluidgrids.taskQueues.rateRange", "0.01 to 10000.") })
229
+ ]
230
+ }), /* @__PURE__ */ jsxs("div", {
231
+ className: "space-y-1.5",
232
+ children: [
233
+ /* @__PURE__ */ jsx(Label, {
234
+ htmlFor: "task-queue-timeout",
235
+ children: tr("fluidgrids.taskQueues.timeout", "Wait for a worker (seconds)")
236
+ }),
237
+ /* @__PURE__ */ jsx(Input, {
238
+ id: "task-queue-timeout",
239
+ inputMode: "numeric",
240
+ value: form.workerUnavailableTimeoutSeconds,
241
+ placeholder: "600",
242
+ onChange: (event) => setForm({
243
+ ...form,
244
+ workerUnavailableTimeoutSeconds: event.target.value
245
+ }),
246
+ "data-testid": "task-queue-timeout"
247
+ }),
248
+ !checked.ok && checked.field === "workerUnavailableTimeoutSeconds" && /* @__PURE__ */ jsx(FieldError, { message: tr("fluidgrids.taskQueues.timeoutRange", "10 seconds to 7 days.") })
249
+ ]
250
+ })]
251
+ }),
252
+ /* @__PURE__ */ jsx(FieldError, {
253
+ message: error,
254
+ testId: "task-queue-error"
255
+ })
256
+ ]
257
+ }),
258
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button$1, {
259
+ variant: "outline",
260
+ onClick: onClose,
261
+ disabled: busy,
262
+ children: tr("fluidgrids.runControl.common.cancel", "Cancel")
263
+ }), /* @__PURE__ */ jsx(Button$1, {
264
+ onClick: () => void save(),
265
+ disabled: !checked.ok || busy,
266
+ "data-testid": "task-queue-save",
267
+ children: tr("fluidgrids.runControl.common.save", "Save")
268
+ })] })
269
+ ]
270
+ })
271
+ });
272
+ }
273
+ /** The allow-list editor: connection names workers may resolve (H2). */
274
+ function AllowlistEditor({ value, onChange, disabled }) {
275
+ const tr = useTr();
276
+ return /* @__PURE__ */ jsxs("div", {
277
+ className: "space-y-1.5",
278
+ children: [
279
+ /* @__PURE__ */ jsx(Label, {
280
+ htmlFor: "worker-token-allowlist",
281
+ children: tr("fluidgrids.taskQueues.allowlist", "Connections its user activities may use (optional)")
282
+ }),
283
+ /* @__PURE__ */ jsx(Textarea, {
284
+ id: "worker-token-allowlist",
285
+ rows: 3,
286
+ value,
287
+ disabled,
288
+ onChange: (event) => onChange(event.target.value),
289
+ placeholder: "crm-prod\nbilling-api",
290
+ className: "font-mono text-xs",
291
+ "data-testid": "worker-token-allowlist"
292
+ }),
293
+ /* @__PURE__ */ jsx("p", {
294
+ className: "text-xs text-text-secondary",
295
+ children: tr("fluidgrids.taskQueues.allowlistHint", "Empty: no credential is ever released to these workers. Only access tokens are released, never refresh tokens.")
296
+ })
297
+ ]
298
+ });
299
+ }
300
+ function WorkerTokenDialog({ queues, canSetAllowlist, onClose, onCreated }) {
301
+ const tr = useTr();
302
+ const [name, setName] = useState("");
303
+ const [selected, setSelected] = useState(queues[0] ? [queues[0]] : []);
304
+ const selectedQueues = new Set(selected);
305
+ const [allowlist, setAllowlist] = useState("");
306
+ const [busy, setBusy] = useState(false);
307
+ const [error, setError] = useState(null);
308
+ const valid = name.trim().length > 0 && selected.length > 0;
309
+ const submit = async () => {
310
+ if (!valid) return;
311
+ setBusy(true);
312
+ setError(null);
313
+ try {
314
+ onCreated(await createWorkerToken({
315
+ name: name.trim(),
316
+ taskQueues: selected,
317
+ connectionAllowlist: canSetAllowlist ? parseNameList(allowlist) : []
318
+ }));
319
+ } catch (err) {
320
+ setError(describeOperationError(err));
321
+ } finally {
322
+ setBusy(false);
323
+ }
324
+ };
325
+ return /* @__PURE__ */ jsx(Dialog, {
326
+ open: true,
327
+ onOpenChange: (open) => !open && onClose(),
328
+ children: /* @__PURE__ */ jsxs(DialogContent, {
329
+ style: RUN_CONTROL_DIALOG_STYLE,
330
+ "data-testid": "worker-token-dialog",
331
+ children: [
332
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.taskQueues.tokenTitle", "New worker token") }), /* @__PURE__ */ jsx(DialogDescription, { children: tr("fluidgrids.taskQueues.tokenDescription", "Workers authenticate with it and may serve only the queues it names. It is shown once.") })] }),
333
+ /* @__PURE__ */ jsxs("div", {
334
+ className: "space-y-3",
335
+ children: [
336
+ /* @__PURE__ */ jsxs("div", {
337
+ className: "space-y-1.5",
338
+ children: [/* @__PURE__ */ jsx(Label, {
339
+ htmlFor: "worker-token-name",
340
+ children: tr("fluidgrids.taskQueues.tokenName", "Name")
341
+ }), /* @__PURE__ */ jsx(Input, {
342
+ id: "worker-token-name",
343
+ value: name,
344
+ onChange: (event) => setName(event.target.value),
345
+ "data-testid": "worker-token-name"
346
+ })]
347
+ }),
348
+ /* @__PURE__ */ jsxs("fieldset", {
349
+ className: "space-y-1.5",
350
+ children: [
351
+ /* @__PURE__ */ jsx("legend", {
352
+ className: "text-sm font-medium text-text-primary",
353
+ children: tr("fluidgrids.taskQueues.tokenQueues", "Task queues")
354
+ }),
355
+ queues.length === 0 && /* @__PURE__ */ jsx("p", {
356
+ className: "text-sm text-text-secondary",
357
+ children: tr("fluidgrids.taskQueues.noQueues", "Create a self-hosted queue first.")
358
+ }),
359
+ queues.map((queue) => /* @__PURE__ */ jsxs("label", {
360
+ className: "flex items-center gap-2 text-sm text-text-primary",
361
+ children: [/* @__PURE__ */ jsx("input", {
362
+ type: "checkbox",
363
+ className: "h-4 w-4 rounded",
364
+ checked: selectedQueues.has(queue),
365
+ onChange: (event) => setSelected((current) => event.target.checked ? [...current, queue] : current.filter((item) => item !== queue)),
366
+ "data-testid": `worker-token-queue-${queue}`
367
+ }), /* @__PURE__ */ jsx("code", {
368
+ className: "font-mono text-xs",
369
+ children: queue
370
+ })]
371
+ }, queue))
372
+ ]
373
+ }),
374
+ canSetAllowlist && /* @__PURE__ */ jsx(AllowlistEditor, {
375
+ value: allowlist,
376
+ onChange: setAllowlist
377
+ }),
378
+ /* @__PURE__ */ jsx(FieldError, {
379
+ message: error,
380
+ testId: "worker-token-error"
381
+ })
382
+ ]
383
+ }),
384
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button$1, {
385
+ variant: "outline",
386
+ onClick: onClose,
387
+ disabled: busy,
388
+ children: tr("fluidgrids.runControl.common.cancel", "Cancel")
389
+ }), /* @__PURE__ */ jsx(Button$1, {
390
+ onClick: () => void submit(),
391
+ disabled: !valid || busy,
392
+ "data-testid": "worker-token-submit",
393
+ children: tr("fluidgrids.taskQueues.issue", "Issue token")
394
+ })] })
395
+ ]
396
+ })
397
+ });
398
+ }
399
+ /** Shows a new or rotated token once, with the worker start command. */
400
+ function WorkerTokenSecretDialog({ created, onClose }) {
401
+ const tr = useTr();
402
+ const command = workerRunCommand(created.workerToken.taskQueues, created.bridgeUrl);
403
+ const copy = async (text) => {
404
+ try {
405
+ await navigator.clipboard.writeText(text);
406
+ toast.success(tr("fluidgrids.runControl.common.copied", "Copied"));
407
+ } catch {
408
+ toast.error(tr("fluidgrids.runControl.common.copyFailed", "Could not copy; select the text instead."));
409
+ }
410
+ };
411
+ return /* @__PURE__ */ jsx(Dialog, {
412
+ open: true,
413
+ onOpenChange: (open) => !open && onClose(),
414
+ children: /* @__PURE__ */ jsxs(DialogContent, {
415
+ style: RUN_CONTROL_DIALOG_STYLE,
416
+ "data-testid": "worker-token-secret",
417
+ children: [
418
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.taskQueues.secretTitle", "Copy the worker token") }), /* @__PURE__ */ jsx(DialogDescription, { children: tr("fluidgrids.taskQueues.secretDescription", "It cannot be shown again. Give it to the worker in FLUIDGRIDS_WORKER_TOKEN; never put it in source code.") })] }),
419
+ /* @__PURE__ */ jsxs("div", {
420
+ className: "space-y-3",
421
+ children: [/* @__PURE__ */ jsxs("div", {
422
+ className: "flex gap-2",
423
+ children: [/* @__PURE__ */ jsx("code", {
424
+ className: "min-w-0 flex-1 break-all rounded-md bg-background-primary p-2 font-mono text-xs text-text-primary",
425
+ "data-testid": "worker-token-value",
426
+ children: created.token
427
+ }), /* @__PURE__ */ jsx(Button$1, {
428
+ size: "sm",
429
+ variant: "outline",
430
+ onClick: () => void copy(created.token),
431
+ children: /* @__PURE__ */ jsx(Icon, {
432
+ icon: "mdi:content-copy",
433
+ width: 14
434
+ })
435
+ })]
436
+ }), /* @__PURE__ */ jsxs("div", {
437
+ className: "space-y-1.5",
438
+ children: [/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.taskQueues.startWorker", "Start a worker") }), /* @__PURE__ */ jsx("code", {
439
+ className: "block break-all rounded-md bg-background-primary p-2 font-mono text-xs text-text-primary",
440
+ "data-testid": "worker-run-command",
441
+ children: command
442
+ })]
443
+ })]
444
+ }),
445
+ /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button$1, {
446
+ onClick: onClose,
447
+ "data-testid": "worker-token-secret-done",
448
+ children: tr("fluidgrids.runControl.common.done", "Done")
449
+ }) })
450
+ ]
451
+ })
452
+ });
453
+ }
454
+ function AllowlistDialog({ token, onClose, onSaved }) {
455
+ const tr = useTr();
456
+ const [value, setValue] = useState(() => token.connectionAllowlist.join("\n"));
457
+ const [busy, setBusy] = useState(false);
458
+ const [error, setError] = useState(null);
459
+ const save = async () => {
460
+ setBusy(true);
461
+ setError(null);
462
+ try {
463
+ await setWorkerTokenConnectionAllowlist(token.id, parseNameList(value));
464
+ onSaved();
465
+ } catch (err) {
466
+ setError(describeOperationError(err));
467
+ } finally {
468
+ setBusy(false);
469
+ }
470
+ };
471
+ return /* @__PURE__ */ jsx(Dialog, {
472
+ open: true,
473
+ onOpenChange: (open) => !open && onClose(),
474
+ children: /* @__PURE__ */ jsxs(DialogContent, {
475
+ style: RUN_CONTROL_DIALOG_STYLE,
476
+ "data-testid": "worker-allowlist-dialog",
477
+ children: [
478
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.taskQueues.allowlistTitle", "Connections for {{name}}", { name: token.name }) }), /* @__PURE__ */ jsx(DialogDescription, { children: tr("fluidgrids.taskQueues.allowlistDescription", "Workspace admins only. Every release of a credential is audited.") })] }),
479
+ /* @__PURE__ */ jsx(AllowlistEditor, {
480
+ value,
481
+ onChange: setValue
482
+ }),
483
+ /* @__PURE__ */ jsx(FieldError, {
484
+ message: error,
485
+ testId: "worker-allowlist-error"
486
+ }),
487
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button$1, {
488
+ variant: "outline",
489
+ onClick: onClose,
490
+ disabled: busy,
491
+ children: tr("fluidgrids.runControl.common.cancel", "Cancel")
492
+ }), /* @__PURE__ */ jsx(Button$1, {
493
+ onClick: () => void save(),
494
+ disabled: busy,
495
+ "data-testid": "worker-allowlist-save",
496
+ children: tr("fluidgrids.runControl.common.save", "Save")
497
+ })] })
498
+ ]
499
+ })
500
+ });
501
+ }
502
+
503
+ // src/modules/task-queues/pages/TaskQueuesPage.tsx
504
+ /**
505
+ * Task queues (BOFF-9906, spec code-first 04 §8, 08 §3 and 11 §2.31): the
506
+ * workspace's queues (managed first, then the self-hosted ones it created)
507
+ * with each queue's backlog, the self-hosted workers connected now, and the
508
+ * worker tokens they authenticate with. The whole page needs
509
+ * fluidgrids.self-hosted-workers; with it off the page says so and sends
510
+ * nothing.
511
+ */
512
+ var TaskQueuesPage_exports = /* @__PURE__ */ __exportAll({
513
+ TaskQueueStatsCard: () => TaskQueueStatsCard,
514
+ TaskQueuesContent: () => TaskQueuesContent,
515
+ TaskQueuesPage: () => TaskQueuesPage,
516
+ WORKERS_POLL_MS: () => WORKERS_POLL_MS
517
+ });
518
+ /** Workers heartbeat every 30 s; the list is re-read this often. */
519
+ const WORKERS_POLL_MS = 15e3;
520
+ function TaskQueuesPage() {
521
+ const tr = useTr();
522
+ if (!useSelfHostedWorkersEnabled()) return /* @__PURE__ */ jsxs("div", {
523
+ className: "p-4 sm:p-6",
524
+ "data-testid": "task-queues-disabled",
525
+ children: [/* @__PURE__ */ jsx("h1", {
526
+ className: "text-2xl font-bold text-text-primary",
527
+ children: tr("fluidgrids.taskQueues.title", "Task queues")
528
+ }), /* @__PURE__ */ jsx("p", {
529
+ className: "mt-2 text-text-secondary",
530
+ children: tr("fluidgrids.taskQueues.disabled", "Self-hosted workers are not enabled for this workspace.")
531
+ })]
532
+ });
533
+ return /* @__PURE__ */ jsx(TaskQueuesContent, {});
534
+ }
535
+ /** The page body; exported for hosts that gate the flag themselves, and for tests. */
536
+ function TaskQueuesContent() {
537
+ const tr = useTr();
538
+ const { allowed: canCreate } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.TASK_QUEUE_CREATE);
539
+ const { allowed: canUpdate } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.TASK_QUEUE_UPDATE);
540
+ const { allowed: canDelete } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.TASK_QUEUE_DELETE);
541
+ const { allowed: canReadTokens } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.RUNNER_READ);
542
+ const { allowed: canCreateToken } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.RUNNER_CREATE);
543
+ const { allowed: canRotateToken } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.RUNNER_UPDATE);
544
+ const { allowed: canDeleteToken } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.RUNNER_DELETE);
545
+ const canEditAllowlist = canRotateToken && canCreateToken;
546
+ const queues = useResource("task-queues", true, fetchTaskQueues);
547
+ const workers = useResource("workflow-workers", true, () => fetchWorkflowWorkers({ includeEnded: false }), {
548
+ live: true,
549
+ intervalMs: WORKERS_POLL_MS
550
+ });
551
+ const tokens = useResource("worker-tokens", canReadTokens, fetchWorkerTokens);
552
+ const [selected, setSelected] = useState(null);
553
+ const [editing, setEditing] = useState(null);
554
+ const [deleting, setDeleting] = useState(null);
555
+ const [issuing, setIssuing] = useState(false);
556
+ const [secret, setSecret] = useState(null);
557
+ const [allowlist, setAllowlist] = useState(null);
558
+ const selfHosted = (queues.data ?? []).filter((queue) => queue.kind === "SELF_HOSTED").map((queue) => queue.name);
559
+ const removeQueue = async () => {
560
+ if (!deleting?.id) return;
561
+ try {
562
+ await deleteTaskQueue(deleting.id);
563
+ toast.success(tr("fluidgrids.taskQueues.deleted", "Task queue deleted"));
564
+ if (selected?.id === deleting.id) setSelected(null);
565
+ await queues.refetch();
566
+ } catch (err) {
567
+ toast.error(describeOperationError(err));
568
+ } finally {
569
+ setDeleting(null);
570
+ }
571
+ };
572
+ const [tokenBusy, setTokenBusy] = useState(null);
573
+ const rotate = async (token) => {
574
+ if (tokenBusy) return;
575
+ setTokenBusy(token.id);
576
+ try {
577
+ setSecret(await rotateWorkerToken(token.id));
578
+ await tokens.refetch();
579
+ } catch (err) {
580
+ toast.error(describeOperationError(err));
581
+ } finally {
582
+ setTokenBusy(null);
583
+ }
584
+ };
585
+ const removeToken = async (token) => {
586
+ if (tokenBusy) return;
587
+ setTokenBusy(token.id);
588
+ try {
589
+ await deleteWorkerToken(token.id);
590
+ await Promise.all([tokens.refetch(), workers.refetch()]);
591
+ } catch (err) {
592
+ toast.error(describeOperationError(err));
593
+ } finally {
594
+ setTokenBusy(null);
595
+ }
596
+ };
597
+ return /* @__PURE__ */ jsxs("div", {
598
+ className: "min-h-screen space-y-6 bg-background-primary p-4 sm:p-6",
599
+ "data-testid": "task-queues-page",
600
+ children: [
601
+ /* @__PURE__ */ jsxs("div", {
602
+ className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
603
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
604
+ className: "text-2xl font-bold text-text-primary sm:text-3xl",
605
+ children: tr("fluidgrids.taskQueues.title", "Task queues")
606
+ }), /* @__PURE__ */ jsx("p", {
607
+ className: "mt-1 text-text-secondary",
608
+ children: tr("fluidgrids.taskQueues.subtitle", "Run durable functions and activities on workers you host.")
609
+ })] }), canCreate && /* @__PURE__ */ jsxs(Button$1, {
610
+ onClick: () => setEditing({ queue: null }),
611
+ "data-testid": "task-queue-create",
612
+ children: [/* @__PURE__ */ jsx(Icon, {
613
+ icon: "mdi:plus",
614
+ className: "mr-1",
615
+ width: 16
616
+ }), tr("fluidgrids.taskQueues.create", "New queue")]
617
+ })]
618
+ }),
619
+ /* @__PURE__ */ jsxs("section", {
620
+ className: "rounded-lg border border-border-secondary bg-background-secondary",
621
+ "data-testid": "task-queues-table",
622
+ children: [/* @__PURE__ */ jsx(FieldError, { message: queues.error }), /* @__PURE__ */ jsxs("table", {
623
+ className: "w-full text-sm",
624
+ children: [/* @__PURE__ */ jsx("thead", {
625
+ className: "border-b border-border-secondary text-left text-xs uppercase text-text-secondary",
626
+ children: /* @__PURE__ */ jsxs("tr", { children: [
627
+ /* @__PURE__ */ jsx("th", {
628
+ className: "px-4 py-2",
629
+ children: tr("fluidgrids.taskQueues.name", "Name")
630
+ }),
631
+ /* @__PURE__ */ jsx("th", {
632
+ className: "px-4 py-2",
633
+ children: tr("fluidgrids.taskQueues.kind", "Kind")
634
+ }),
635
+ /* @__PURE__ */ jsx("th", {
636
+ className: "hidden px-4 py-2 sm:table-cell",
637
+ children: tr("fluidgrids.taskQueues.environment", "Environment")
638
+ }),
639
+ /* @__PURE__ */ jsx("th", {
640
+ className: "hidden px-4 py-2 md:table-cell",
641
+ children: tr("fluidgrids.taskQueues.limit", "Rate limit")
642
+ }),
643
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2" })
644
+ ] })
645
+ }), /* @__PURE__ */ jsx("tbody", {
646
+ className: "divide-y divide-border-secondary",
647
+ children: (queues.data ?? []).map((queue) => /* @__PURE__ */ jsxs("tr", {
648
+ className: selected?.name === queue.name && selected.environment === queue.environment ? "bg-action-primary-bg/10" : "",
649
+ "data-testid": `task-queue-${queue.name}`,
650
+ children: [
651
+ /* @__PURE__ */ jsxs("td", {
652
+ className: "px-4 py-2",
653
+ children: [/* @__PURE__ */ jsx("button", {
654
+ type: "button",
655
+ className: "font-mono text-text-primary hover:underline",
656
+ onClick: () => setSelected(queue),
657
+ children: queue.name
658
+ }), queue.description && /* @__PURE__ */ jsx("p", {
659
+ className: "text-xs text-text-secondary",
660
+ children: queue.description
661
+ })]
662
+ }),
663
+ /* @__PURE__ */ jsx("td", {
664
+ className: "px-4 py-2",
665
+ children: /* @__PURE__ */ jsx(StatusPill, {
666
+ tone: queue.kind === "MANAGED" ? "neutral" : "info",
667
+ children: queue.kind === "MANAGED" ? tr("fluidgrids.taskQueues.managed", "Managed") : tr("fluidgrids.taskQueues.selfHosted", "Self-hosted")
668
+ })
669
+ }),
670
+ /* @__PURE__ */ jsx("td", {
671
+ className: "hidden px-4 py-2 text-text-secondary sm:table-cell",
672
+ children: queue.environment ?? tr("fluidgrids.taskQueues.every", "Every")
673
+ }),
674
+ /* @__PURE__ */ jsx("td", {
675
+ className: "hidden px-4 py-2 text-text-secondary md:table-cell",
676
+ children: queue.maxTasksPerSecond ? `${queue.maxTasksPerSecond}/s` : "—"
677
+ }),
678
+ /* @__PURE__ */ jsxs("td", {
679
+ className: "px-4 py-2 text-right",
680
+ children: [queue.id && canUpdate && /* @__PURE__ */ jsx(Button$1, {
681
+ size: "sm",
682
+ variant: "ghost",
683
+ onClick: () => setEditing({ queue }),
684
+ "aria-label": tr("fluidgrids.taskQueues.edit", "Edit"),
685
+ children: /* @__PURE__ */ jsx(Icon, {
686
+ icon: "mdi:pencil",
687
+ width: 14
688
+ })
689
+ }), queue.id && canDelete && /* @__PURE__ */ jsx(Button$1, {
690
+ size: "sm",
691
+ variant: "ghost",
692
+ className: "text-[var(--color-status-error-text)]",
693
+ onClick: () => setDeleting(queue),
694
+ "aria-label": tr("fluidgrids.taskQueues.delete", "Delete"),
695
+ "data-testid": `task-queue-delete-${queue.name}`,
696
+ children: /* @__PURE__ */ jsx(Icon, {
697
+ icon: "mdi:delete-outline",
698
+ width: 14
699
+ })
700
+ })]
701
+ })
702
+ ]
703
+ }, `${queue.name}:${queue.environment ?? ""}`))
704
+ })]
705
+ })]
706
+ }),
707
+ selected && /* @__PURE__ */ jsx(TaskQueueStatsCard, { queue: selected }),
708
+ /* @__PURE__ */ jsxs("section", {
709
+ className: "rounded-lg border border-border-secondary bg-background-secondary",
710
+ "data-testid": "workers-table",
711
+ children: [
712
+ /* @__PURE__ */ jsxs("header", {
713
+ className: "flex items-center gap-2 border-b border-border-secondary px-4 py-3",
714
+ children: [/* @__PURE__ */ jsx(Icon, {
715
+ icon: "mdi:server-network",
716
+ width: 18,
717
+ className: "text-text-secondary"
718
+ }), /* @__PURE__ */ jsx("h2", {
719
+ className: "text-sm font-semibold text-text-primary",
720
+ children: tr("fluidgrids.taskQueues.workers", "Workers")
721
+ })]
722
+ }),
723
+ /* @__PURE__ */ jsx(FieldError, { message: workers.error }),
724
+ workers.data && workers.data.length === 0 && /* @__PURE__ */ jsx("p", {
725
+ className: "px-4 py-3 text-sm text-text-secondary",
726
+ children: tr("fluidgrids.taskQueues.noWorkers", "No worker is connected.")
727
+ }),
728
+ /* @__PURE__ */ jsx("ul", {
729
+ className: "divide-y divide-border-secondary",
730
+ children: (workers.data ?? []).map((worker) => /* @__PURE__ */ jsxs("li", {
731
+ className: "flex flex-wrap items-center gap-3 px-4 py-2 text-sm",
732
+ "data-testid": `worker-${worker.id}`,
733
+ children: [
734
+ /* @__PURE__ */ jsx(StatusPill, {
735
+ tone: worker.status === "ONLINE" ? "success" : worker.status === "STALE" ? "warning" : "neutral",
736
+ children: worker.status
737
+ }),
738
+ /* @__PURE__ */ jsx("span", {
739
+ className: "font-mono text-xs text-text-primary",
740
+ children: worker.identity
741
+ }),
742
+ /* @__PURE__ */ jsxs("span", {
743
+ className: "text-xs text-text-secondary",
744
+ children: [
745
+ worker.sdkLanguage,
746
+ " ",
747
+ worker.sdkVersion
748
+ ]
749
+ }),
750
+ /* @__PURE__ */ jsx("span", {
751
+ className: "text-xs text-text-secondary",
752
+ children: worker.taskQueues.join(", ")
753
+ }),
754
+ /* @__PURE__ */ jsx("span", {
755
+ className: "text-xs text-text-secondary",
756
+ children: tr("fluidgrids.taskQueues.slots", "slots {{used}}/{{total}}", {
757
+ used: worker.slots.workflow.used + worker.slots.activity.used,
758
+ total: worker.slots.workflow.total + worker.slots.activity.total
759
+ })
760
+ }),
761
+ /* @__PURE__ */ jsx("span", {
762
+ className: "ml-auto text-xs text-text-secondary",
763
+ children: formatAge(worker.lastHeartbeatAt)
764
+ })
765
+ ]
766
+ }, worker.id))
767
+ })
768
+ ]
769
+ }),
770
+ canReadTokens && /* @__PURE__ */ jsxs("section", {
771
+ className: "rounded-lg border border-border-secondary bg-background-secondary",
772
+ "data-testid": "worker-tokens",
773
+ children: [
774
+ /* @__PURE__ */ jsxs("header", {
775
+ className: "flex items-center gap-2 border-b border-border-secondary px-4 py-3",
776
+ children: [
777
+ /* @__PURE__ */ jsx(Icon, {
778
+ icon: "mdi:key-variant",
779
+ width: 18,
780
+ className: "text-text-secondary"
781
+ }),
782
+ /* @__PURE__ */ jsx("h2", {
783
+ className: "text-sm font-semibold text-text-primary",
784
+ children: tr("fluidgrids.taskQueues.tokens", "Worker tokens")
785
+ }),
786
+ canCreateToken && /* @__PURE__ */ jsxs(Button$1, {
787
+ size: "sm",
788
+ variant: "outline",
789
+ className: "ml-auto",
790
+ onClick: () => setIssuing(true),
791
+ "data-testid": "worker-token-create",
792
+ children: [/* @__PURE__ */ jsx(Icon, {
793
+ icon: "mdi:plus",
794
+ className: "mr-1",
795
+ width: 14
796
+ }), tr("fluidgrids.taskQueues.issueToken", "New token")]
797
+ })
798
+ ]
799
+ }),
800
+ /* @__PURE__ */ jsx(FieldError, { message: tokens.error }),
801
+ /* @__PURE__ */ jsxs("ul", {
802
+ className: "divide-y divide-border-secondary",
803
+ children: [(tokens.data ?? []).map((token) => /* @__PURE__ */ jsxs("li", {
804
+ className: "flex flex-wrap items-center gap-3 px-4 py-2 text-sm",
805
+ "data-testid": `worker-token-${token.id}`,
806
+ children: [
807
+ /* @__PURE__ */ jsx("span", {
808
+ className: "font-medium text-text-primary",
809
+ children: token.name
810
+ }),
811
+ /* @__PURE__ */ jsxs("code", {
812
+ className: "font-mono text-xs text-text-secondary",
813
+ children: [token.tokenPrefix, "…"]
814
+ }),
815
+ /* @__PURE__ */ jsx("span", {
816
+ className: "text-xs text-text-secondary",
817
+ children: token.taskQueues.join(", ")
818
+ }),
819
+ /* @__PURE__ */ jsx("span", {
820
+ className: "text-xs text-text-secondary",
821
+ children: token.connectionAllowlist.length > 0 ? tr("fluidgrids.taskQueues.connections", "{{count}} connections", { count: token.connectionAllowlist.length }) : tr("fluidgrids.taskQueues.noConnections", "no connections")
822
+ }),
823
+ /* @__PURE__ */ jsxs("span", {
824
+ className: "ml-auto flex gap-1",
825
+ children: [
826
+ canEditAllowlist && /* @__PURE__ */ jsx(Button$1, {
827
+ size: "sm",
828
+ variant: "ghost",
829
+ onClick: () => setAllowlist(token),
830
+ "data-testid": `worker-token-allowlist-${token.id}`,
831
+ children: tr("fluidgrids.taskQueues.editConnections", "Connections")
832
+ }),
833
+ canRotateToken && /* @__PURE__ */ jsx(Button$1, {
834
+ size: "sm",
835
+ variant: "ghost",
836
+ onClick: () => void rotate(token),
837
+ disabled: tokenBusy !== null,
838
+ "data-testid": `worker-token-rotate-${token.id}`,
839
+ children: tr("fluidgrids.taskQueues.rotate", "Rotate")
840
+ }),
841
+ canDeleteToken && /* @__PURE__ */ jsx(Button$1, {
842
+ size: "sm",
843
+ variant: "ghost",
844
+ className: "text-[var(--color-status-error-text)]",
845
+ onClick: () => void removeToken(token),
846
+ disabled: tokenBusy !== null,
847
+ "data-testid": `worker-token-delete-${token.id}`,
848
+ children: tr("fluidgrids.taskQueues.delete", "Delete")
849
+ })
850
+ ]
851
+ })
852
+ ]
853
+ }, token.id)), tokens.data && tokens.data.length === 0 && /* @__PURE__ */ jsx("li", {
854
+ className: "px-4 py-3 text-sm text-text-secondary",
855
+ children: tr("fluidgrids.taskQueues.noTokens", "No worker tokens.")
856
+ })]
857
+ })
858
+ ]
859
+ }),
860
+ editing && /* @__PURE__ */ jsx(TaskQueueDialog, {
861
+ queue: editing.queue,
862
+ onClose: () => setEditing(null),
863
+ onSaved: () => {
864
+ setEditing(null);
865
+ queues.refetch();
866
+ }
867
+ }),
868
+ issuing && /* @__PURE__ */ jsx(WorkerTokenDialog, {
869
+ queues: selfHosted,
870
+ canSetAllowlist: canCreateToken,
871
+ onClose: () => setIssuing(false),
872
+ onCreated: (created) => {
873
+ setIssuing(false);
874
+ setSecret(created);
875
+ tokens.refetch();
876
+ }
877
+ }),
878
+ secret && /* @__PURE__ */ jsx(WorkerTokenSecretDialog, {
879
+ created: secret,
880
+ onClose: () => setSecret(null)
881
+ }),
882
+ allowlist && /* @__PURE__ */ jsx(AllowlistDialog, {
883
+ token: allowlist,
884
+ onClose: () => setAllowlist(null),
885
+ onSaved: () => {
886
+ setAllowlist(null);
887
+ tokens.refetch();
888
+ }
889
+ }),
890
+ /* @__PURE__ */ jsx(AlertDialog, {
891
+ open: deleting !== null,
892
+ onOpenChange: (open) => !open && setDeleting(null),
893
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, {
894
+ style: RUN_CONTROL_DIALOG_STYLE,
895
+ children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsx(AlertDialogTitle, { children: tr("fluidgrids.taskQueues.deleteTitle", "Delete {{name}}?", { name: deleting?.name ?? "" }) }), /* @__PURE__ */ jsx(AlertDialogDescription, { children: tr("fluidgrids.taskQueues.deleteDescription", "The service refuses while open runs or activities use the queue.") })] }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, { children: tr("fluidgrids.runControl.common.cancel", "Cancel") }), /* @__PURE__ */ jsx(AlertDialogAction, {
896
+ onClick: () => void removeQueue(),
897
+ "data-testid": "task-queue-delete-confirm",
898
+ children: tr("fluidgrids.taskQueues.delete", "Delete")
899
+ })] })]
900
+ })
901
+ })
902
+ ]
903
+ });
904
+ }
905
+ function TaskQueueStatsCard({ queue }) {
906
+ const tr = useTr();
907
+ const stats = useResource(`queue-stats:${queue.name}:${queue.environment ?? ""}`, true, () => fetchTaskQueueStats(queue.name, queue.environment), {
908
+ live: true,
909
+ intervalMs: 3e4
910
+ });
911
+ const data = stats.data;
912
+ return /* @__PURE__ */ jsxs("section", {
913
+ className: "grid gap-4 rounded-lg border border-border-secondary bg-background-secondary p-4 sm:grid-cols-3",
914
+ "data-testid": "task-queue-stats",
915
+ children: [
916
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
917
+ className: "text-xs uppercase text-text-secondary",
918
+ children: tr("fluidgrids.taskQueues.pollers", "Pollers")
919
+ }), /* @__PURE__ */ jsx("p", {
920
+ className: "text-2xl font-bold text-text-primary",
921
+ "data-testid": "task-queue-pollers",
922
+ children: data?.pollers ?? "—"
923
+ })] }),
924
+ ["workflow", "activity"].map((kind) => /* @__PURE__ */ jsxs("div", { children: [
925
+ /* @__PURE__ */ jsx("p", {
926
+ className: "text-xs uppercase text-text-secondary",
927
+ children: kind === "workflow" ? tr("fluidgrids.taskQueues.workflowBacklog", "Workflow backlog") : tr("fluidgrids.taskQueues.activityBacklog", "Activity backlog")
928
+ }),
929
+ /* @__PURE__ */ jsx("p", {
930
+ className: "text-2xl font-bold text-text-primary",
931
+ "data-testid": `task-queue-backlog-${kind}`,
932
+ children: data ? data[kind].backlogCount : "—"
933
+ }),
934
+ data && /* @__PURE__ */ jsx("p", {
935
+ className: "text-xs text-text-secondary",
936
+ children: tr("fluidgrids.taskQueues.rates", "oldest {{age}}s · {{add}}/s in · {{out}}/s out", {
937
+ age: Math.round(data[kind].approxBacklogAgeSeconds),
938
+ add: data[kind].tasksAddRate.toFixed(1),
939
+ out: data[kind].tasksDispatchRate.toFixed(1)
940
+ })
941
+ })
942
+ ] }, kind)),
943
+ /* @__PURE__ */ jsx("div", {
944
+ className: "sm:col-span-3",
945
+ children: /* @__PURE__ */ jsx(FieldError, { message: stats.error })
946
+ })
947
+ ]
948
+ });
949
+ }
950
+
951
+ export { taskQueueNameProblem as a, taskQueueInputOf as i, TaskQueuesPage as n, TaskQueuesPage_exports as r, TaskQueueStatsCard as t };