@burdenoff/fluidgrids-fe-sdk 2026.1111.17 → 2026.1111.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AiStudioPage-kJHSsIcj.js → AiStudioPage-htYcXz43.js} +2 -2
- package/dist/{MissingNodesInstallModal-CMybsI8h.js → MissingNodesInstallModal-Dly7T2_7.js} +1 -1
- package/dist/{SettingsPage-CgkRlCM5.js → SettingsPage-CJLZvv4J.js} +1485 -1484
- package/dist/{TemplateCard-DsC0KNH1.js → TemplateCard-BdB087SP.js} +47 -11
- package/dist/{TemplateConfigurationModal-BhGWIzpm.js → TemplateConfigurationModal-CPVcTDSi.js} +1 -1
- package/dist/{TemplateDetailPanel-D4cUVr-m.js → TemplateDetailPanel-DruadMkg.js} +1 -1
- package/dist/{TemplatePreview-GthzCOSo.js → TemplatePreview-CNrYJXw2.js} +2 -2
- package/dist/{TemplatesPage-CsDNT54N.js → TemplatesPage-BU4tSqOo.js} +1 -1
- package/dist/{WorkflowBuilder-DEi_9YOb.js → WorkflowBuilder-BSPGcOfA.js} +2 -2
- package/dist/{WorkflowBuilder-iPqXgbYr.js → WorkflowBuilder-DMonxuoY.js} +4 -4
- package/dist/WorkflowShareDialog-mbIwRQUY.js +168 -0
- package/dist/{WorkflowsList-CA20dVIl.js → WorkflowsList-DiiGyZkT.js} +2 -1
- package/dist/{agentTools-CkRJB7r5.js → agentTools-C7pdNJqE.js} +2 -2
- package/dist/{canvasNoteTools-CQIc7Oxg.js → canvasNoteTools-CqiPTBa1.js} +1 -1
- package/dist/code-editor-field-9-GdVE_8.js +111 -0
- package/dist/{flow-builder-y2Y1iG4Y.js → flow-builder-CV5Zu_Hq.js} +246 -8639
- package/dist/{index-W2o__Nm6.d.ts → index-6JR2vD9J.d.ts} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +15 -13
- package/dist/{node-detail-view-B91kXzIy.js → node-detail-view-ttMp6H6K.js} +4 -2
- package/dist/node-editor-tab-body-Bqb--pZy.js +174 -0
- package/dist/observability.d.ts +1 -1
- package/dist/popover-BbVOHsfO.js +472 -0
- package/dist/{quick-config-panel-DggPl1vY.js → quick-config-panel-Da4uOVSs.js} +2 -1
- package/dist/schedule-panel-DnFN933b.js +7542 -0
- package/dist/settings.js +1 -1
- package/dist/sidebar-node-editor-lcnIZxdR.js +177 -0
- package/dist/templates.js +2 -2
- package/dist/{useAgentDefinitions-Dpz0HDTq.js → useAgentDefinitions-B0dam3_o.js} +1 -396
- package/dist/useConnections-Cs7LCWEM.js +401 -0
- package/dist/{WorkflowShareDialog-B_gn968c.js → useWorkflowCodeExport-Bu2QYJzA.js} +4 -161
- package/dist/workflow-builder.js +2 -2
- package/dist/workflow-pages.d.ts +1 -1
- package/dist/workflow-pages.js +6 -6
- package/dist/workflows-list.js +1 -1
- package/package.json +1 -1
|
@@ -13,7 +13,8 @@ import { KF as WorkflowSearchAttributeType } from "./graphql-types-CDKVWH2R.js";
|
|
|
13
13
|
import { H as useFlowStore } from "./node-operation-selection--oy-yEq_.js";
|
|
14
14
|
import { At as useSaveStatusStore, Qt as findSaveConflict } from "./FluidGridsProvider-dvZSNtSW.js";
|
|
15
15
|
import { a as useLogStreamDestinations, i as useLogStreamDestinationActions } from "./useObservability-CfEw3ZFD.js";
|
|
16
|
-
import {
|
|
16
|
+
import { n as useConnections } from "./useConnections-Cs7LCWEM.js";
|
|
17
|
+
import { Nt as useUpdateWorkflowDefinitionMutation, Rt as useWorkflowDefinitionQuery, Vt as useWorkflowDefinitionsQuery, n as useAgentDefinitions } from "./useAgentDefinitions-B0dam3_o.js";
|
|
17
18
|
import { a as formToInput$2, i as emptyAgentForm, n as agentToForm, o as grantDays, r as describeRule, t as AgentFormError } from "./agent-form-Bfvm32j1.js";
|
|
18
19
|
import { i as getGlobalStoreClient } from "./node-registry-refresh-Dy0uRg9f.js";
|
|
19
20
|
import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
|
|
@@ -30,437 +31,6 @@ import { print } from "graphql";
|
|
|
30
31
|
import { clearGraphqlFetchResponseCache, getStoredOrganizationId, getStoredProjectId, getWorkspaceTokenPayload, graphqlFetchPinned, isWorkspaceTokenValidForContext, prepareWorkspaceRequest } from "@burdenoff/fe-libs/shared";
|
|
31
32
|
import { resolveExternalGatewayUrls } from "@burdenoff/fe-libs/shared/config";
|
|
32
33
|
|
|
33
|
-
// src/modules/agent-definitions/components/AgentsTab.tsx
|
|
34
|
-
/**
|
|
35
|
-
* Settings → Agents (BOFF-9962): FluidGrids agents with their own identity.
|
|
36
|
-
*
|
|
37
|
-
* Builders create an agent, choose the tools it may call (node type and operations) and the
|
|
38
|
-
* connections those tools may use, and can require approval for every tool call. Each person
|
|
39
|
-
* lets the agent act for them with a time-boxed grant (30 days by default, 365 at most) and can
|
|
40
|
-
* withdraw it at any time; admins can revoke anyone's grant. An AI Agent node that names the agent
|
|
41
|
-
* runs its tools as "agent for user", and every call is recorded under both identities. Giving an
|
|
42
|
-
* agent more access withdraws other people's grants until they grant again.
|
|
43
|
-
*/
|
|
44
|
-
function errorMessage$2(err) {
|
|
45
|
-
if (err instanceof AgentFormError) return err.message;
|
|
46
|
-
return describeOperationError(err);
|
|
47
|
-
}
|
|
48
|
-
function AgentEditor({ initial, onCancel, onSave }) {
|
|
49
|
-
const tr = useTr();
|
|
50
|
-
const [form, setForm] = useState(initial);
|
|
51
|
-
const [busy, setBusy] = useState(false);
|
|
52
|
-
const { connections, loading: connectionsLoading } = useConnections();
|
|
53
|
-
const setTool = (index, patch) => setForm((current) => ({
|
|
54
|
-
...current,
|
|
55
|
-
tools: current.tools.map((row, i) => i === index ? {
|
|
56
|
-
...row,
|
|
57
|
-
...patch
|
|
58
|
-
} : row)
|
|
59
|
-
}));
|
|
60
|
-
const toggleConnection = (id, on) => setForm((current) => ({
|
|
61
|
-
...current,
|
|
62
|
-
connectionScope: on ? [.../* @__PURE__ */ new Set([...current.connectionScope, id])] : current.connectionScope.filter((value) => value !== id)
|
|
63
|
-
}));
|
|
64
|
-
const submit = async () => {
|
|
65
|
-
setBusy(true);
|
|
66
|
-
try {
|
|
67
|
-
await onSave(form);
|
|
68
|
-
} catch (err) {
|
|
69
|
-
toast.error(errorMessage$2(err));
|
|
70
|
-
} finally {
|
|
71
|
-
setBusy(false);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
75
|
-
className: "rounded-lg border border-border-secondary bg-background-primary p-4 space-y-4",
|
|
76
|
-
"data-testid": "agent-definition-editor",
|
|
77
|
-
children: [
|
|
78
|
-
/* @__PURE__ */ jsxs("div", {
|
|
79
|
-
className: "grid gap-3 sm:grid-cols-2",
|
|
80
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
81
|
-
className: "space-y-1",
|
|
82
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
83
|
-
htmlFor: "agent-name",
|
|
84
|
-
children: tr("fluidgrids.agents.name", "Name")
|
|
85
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
86
|
-
id: "agent-name",
|
|
87
|
-
value: form.name,
|
|
88
|
-
onChange: (e) => setForm({
|
|
89
|
-
...form,
|
|
90
|
-
name: e.target.value
|
|
91
|
-
}),
|
|
92
|
-
"data-testid": "agent-definition-name"
|
|
93
|
-
})]
|
|
94
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
95
|
-
className: "flex items-end gap-2",
|
|
96
|
-
children: [/* @__PURE__ */ jsx(Switch, {
|
|
97
|
-
id: "agent-active",
|
|
98
|
-
checked: form.status === "ACTIVE",
|
|
99
|
-
onCheckedChange: (on) => setForm({
|
|
100
|
-
...form,
|
|
101
|
-
status: on ? "ACTIVE" : "DISABLED"
|
|
102
|
-
})
|
|
103
|
-
}), /* @__PURE__ */ jsx(Label, {
|
|
104
|
-
htmlFor: "agent-active",
|
|
105
|
-
children: tr("fluidgrids.agents.active", "Active")
|
|
106
|
-
})]
|
|
107
|
-
})]
|
|
108
|
-
}),
|
|
109
|
-
/* @__PURE__ */ jsxs("div", {
|
|
110
|
-
className: "space-y-1",
|
|
111
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
112
|
-
htmlFor: "agent-description",
|
|
113
|
-
children: tr("fluidgrids.agents.description", "Description")
|
|
114
|
-
}), /* @__PURE__ */ jsx(Textarea, {
|
|
115
|
-
id: "agent-description",
|
|
116
|
-
value: form.description,
|
|
117
|
-
onChange: (e) => setForm({
|
|
118
|
-
...form,
|
|
119
|
-
description: e.target.value
|
|
120
|
-
}),
|
|
121
|
-
rows: 2
|
|
122
|
-
})]
|
|
123
|
-
}),
|
|
124
|
-
/* @__PURE__ */ jsxs("div", {
|
|
125
|
-
className: "space-y-2",
|
|
126
|
-
children: [
|
|
127
|
-
/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.tools", "Tools the agent may call") }),
|
|
128
|
-
/* @__PURE__ */ jsx("p", {
|
|
129
|
-
className: "text-xs text-text-secondary",
|
|
130
|
-
children: tr("fluidgrids.agents.toolsHelp", "A node type (for example store/slack) and the operations of it the agent may call, separated by commas. Leave the operations empty to allow every operation of that node type. Nothing else can be called.")
|
|
131
|
-
}),
|
|
132
|
-
form.tools.map((row, index) => /* @__PURE__ */ jsxs("div", {
|
|
133
|
-
className: "flex flex-wrap items-center gap-2",
|
|
134
|
-
"data-testid": `agent-definition-tool-${index}`,
|
|
135
|
-
children: [
|
|
136
|
-
/* @__PURE__ */ jsx(Input, {
|
|
137
|
-
className: "w-48",
|
|
138
|
-
placeholder: "store/slack",
|
|
139
|
-
value: row.nodeType,
|
|
140
|
-
onChange: (e) => setTool(index, { nodeType: e.target.value }),
|
|
141
|
-
"aria-label": tr("fluidgrids.agents.nodeType", "Node type")
|
|
142
|
-
}),
|
|
143
|
-
/* @__PURE__ */ jsx(Input, {
|
|
144
|
-
className: "min-w-48 flex-1",
|
|
145
|
-
placeholder: "postMessage, listChannels",
|
|
146
|
-
value: row.operations,
|
|
147
|
-
onChange: (e) => setTool(index, { operations: e.target.value }),
|
|
148
|
-
"aria-label": tr("fluidgrids.agents.operations", "Operations")
|
|
149
|
-
}),
|
|
150
|
-
/* @__PURE__ */ jsx(Button$1, {
|
|
151
|
-
variant: "ghost",
|
|
152
|
-
size: "sm",
|
|
153
|
-
onClick: () => setForm({
|
|
154
|
-
...form,
|
|
155
|
-
tools: form.tools.filter((_, i) => i !== index)
|
|
156
|
-
}),
|
|
157
|
-
"aria-label": tr("fluidgrids.agents.removeTool", "Remove tool"),
|
|
158
|
-
children: /* @__PURE__ */ jsx(Icon, {
|
|
159
|
-
icon: "mdi:close",
|
|
160
|
-
width: 16
|
|
161
|
-
})
|
|
162
|
-
})
|
|
163
|
-
]
|
|
164
|
-
}, index)),
|
|
165
|
-
/* @__PURE__ */ jsxs(Button$1, {
|
|
166
|
-
variant: "outline",
|
|
167
|
-
size: "sm",
|
|
168
|
-
onClick: () => setForm({
|
|
169
|
-
...form,
|
|
170
|
-
tools: [...form.tools, {
|
|
171
|
-
nodeType: "",
|
|
172
|
-
operations: ""
|
|
173
|
-
}]
|
|
174
|
-
}),
|
|
175
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
176
|
-
icon: "mdi:plus",
|
|
177
|
-
width: 16
|
|
178
|
-
}), tr("fluidgrids.agents.addTool", "Add tool")]
|
|
179
|
-
})
|
|
180
|
-
]
|
|
181
|
-
}),
|
|
182
|
-
/* @__PURE__ */ jsxs("div", {
|
|
183
|
-
className: "space-y-2",
|
|
184
|
-
children: [/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.connections", "Connections the agent's tools may use") }), connectionsLoading ? /* @__PURE__ */ jsx("p", {
|
|
185
|
-
className: "text-xs text-text-secondary",
|
|
186
|
-
children: tr("fluidgrids.agents.loadingConnections", "Loading connections…")
|
|
187
|
-
}) : connections.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
188
|
-
className: "text-xs text-text-secondary",
|
|
189
|
-
children: tr("fluidgrids.agents.noConnections", "This workspace has no connections yet.")
|
|
190
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
191
|
-
className: "grid gap-1 sm:grid-cols-2",
|
|
192
|
-
children: connections.map((connection) => /* @__PURE__ */ jsxs("label", {
|
|
193
|
-
className: "flex items-center gap-2 text-sm text-text-primary",
|
|
194
|
-
children: [
|
|
195
|
-
/* @__PURE__ */ jsx(Checkbox, {
|
|
196
|
-
checked: form.connectionScope.includes(connection.id),
|
|
197
|
-
onCheckedChange: (on) => toggleConnection(connection.id, on === true)
|
|
198
|
-
}),
|
|
199
|
-
/* @__PURE__ */ jsx("span", { children: connection.name }),
|
|
200
|
-
/* @__PURE__ */ jsx("span", {
|
|
201
|
-
className: "text-xs text-text-tertiary",
|
|
202
|
-
children: connection.provider?.name ?? connection.providerId
|
|
203
|
-
})
|
|
204
|
-
]
|
|
205
|
-
}, connection.id))
|
|
206
|
-
})]
|
|
207
|
-
}),
|
|
208
|
-
/* @__PURE__ */ jsxs("div", {
|
|
209
|
-
className: "flex items-center gap-2",
|
|
210
|
-
children: [/* @__PURE__ */ jsx(Switch, {
|
|
211
|
-
id: "agent-approval",
|
|
212
|
-
checked: form.requireToolApproval,
|
|
213
|
-
onCheckedChange: (on) => setForm({
|
|
214
|
-
...form,
|
|
215
|
-
requireToolApproval: on
|
|
216
|
-
})
|
|
217
|
-
}), /* @__PURE__ */ jsx(Label, {
|
|
218
|
-
htmlFor: "agent-approval",
|
|
219
|
-
children: tr("fluidgrids.agents.requireApproval", "Every tool call waits for a person's approval")
|
|
220
|
-
})]
|
|
221
|
-
}),
|
|
222
|
-
/* @__PURE__ */ jsxs("div", {
|
|
223
|
-
className: "flex gap-2",
|
|
224
|
-
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
225
|
-
onClick: () => void submit(),
|
|
226
|
-
disabled: busy,
|
|
227
|
-
"data-testid": "agent-definition-save",
|
|
228
|
-
children: tr("fluidgrids.agents.save", "Save agent")
|
|
229
|
-
}), /* @__PURE__ */ jsx(Button$1, {
|
|
230
|
-
variant: "outline",
|
|
231
|
-
onClick: onCancel,
|
|
232
|
-
disabled: busy,
|
|
233
|
-
children: tr("fluidgrids.agents.cancel", "Cancel")
|
|
234
|
-
})]
|
|
235
|
-
})
|
|
236
|
-
]
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
function AgentGrants({ agent, loadGrants, onGrant, onRevoke }) {
|
|
240
|
-
const tr = useTr();
|
|
241
|
-
const [grants, setGrants] = useState(null);
|
|
242
|
-
const [days, setDays] = useState(String(30));
|
|
243
|
-
const [busy, setBusy] = useState(false);
|
|
244
|
-
const load = useCallback(async () => {
|
|
245
|
-
try {
|
|
246
|
-
setGrants(await loadGrants(agent.id));
|
|
247
|
-
} catch (err) {
|
|
248
|
-
setGrants([]);
|
|
249
|
-
toast.error(errorMessage$2(err));
|
|
250
|
-
}
|
|
251
|
-
}, [agent.id, loadGrants]);
|
|
252
|
-
useEffect(() => {
|
|
253
|
-
load();
|
|
254
|
-
}, [load]);
|
|
255
|
-
const run = async (action, done) => {
|
|
256
|
-
setBusy(true);
|
|
257
|
-
try {
|
|
258
|
-
await action();
|
|
259
|
-
toast.success(done);
|
|
260
|
-
await load();
|
|
261
|
-
} catch (err) {
|
|
262
|
-
toast.error(errorMessage$2(err));
|
|
263
|
-
} finally {
|
|
264
|
-
setBusy(false);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
const mine = grants?.find((grant) => grant.isMine && grant.status === "ACTIVE");
|
|
268
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
269
|
-
className: "space-y-2",
|
|
270
|
-
"data-testid": `agent-grants-${agent.id}`,
|
|
271
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
272
|
-
className: "flex flex-wrap items-center gap-2",
|
|
273
|
-
children: mine ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
274
|
-
className: "text-sm text-text-primary",
|
|
275
|
-
children: mine.expiresAt ? tr("fluidgrids.agents.myGrantUntil", "This agent may act for you until {{date}}.", { date: new Date(mine.expiresAt).toLocaleDateString() }) : tr("fluidgrids.agents.myGrant", "This agent may act for you.")
|
|
276
|
-
}), /* @__PURE__ */ jsx(Button$1, {
|
|
277
|
-
variant: "outline",
|
|
278
|
-
size: "sm",
|
|
279
|
-
disabled: busy,
|
|
280
|
-
onClick: () => void run(() => onRevoke(agent.id, null), tr("fluidgrids.agents.withdrawn", "Grant withdrawn")),
|
|
281
|
-
"data-testid": "agent-grant-withdraw",
|
|
282
|
-
children: tr("fluidgrids.agents.withdraw", "Withdraw my grant")
|
|
283
|
-
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
284
|
-
/* @__PURE__ */ jsx(Label, {
|
|
285
|
-
htmlFor: `grant-days-${agent.id}`,
|
|
286
|
-
className: "text-sm",
|
|
287
|
-
children: tr("fluidgrids.agents.grantFor", "Let it act for me for")
|
|
288
|
-
}),
|
|
289
|
-
/* @__PURE__ */ jsx(Input, {
|
|
290
|
-
id: `grant-days-${agent.id}`,
|
|
291
|
-
className: "w-20",
|
|
292
|
-
type: "number",
|
|
293
|
-
min: 1,
|
|
294
|
-
max: 365,
|
|
295
|
-
value: days,
|
|
296
|
-
onChange: (e) => setDays(e.target.value)
|
|
297
|
-
}),
|
|
298
|
-
/* @__PURE__ */ jsx("span", {
|
|
299
|
-
className: "text-sm text-text-secondary",
|
|
300
|
-
children: tr("fluidgrids.agents.days", "days")
|
|
301
|
-
}),
|
|
302
|
-
/* @__PURE__ */ jsx(Button$1, {
|
|
303
|
-
size: "sm",
|
|
304
|
-
disabled: busy || agent.status !== "ACTIVE",
|
|
305
|
-
onClick: () => void run(async () => onGrant(agent.id, grantDays(days)), tr("fluidgrids.agents.granted", "Grant given")),
|
|
306
|
-
"data-testid": "agent-grant-give",
|
|
307
|
-
children: tr("fluidgrids.agents.grant", "Grant")
|
|
308
|
-
})
|
|
309
|
-
] })
|
|
310
|
-
}), grants && grants.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
311
|
-
className: "space-y-1 text-xs text-text-secondary",
|
|
312
|
-
children: grants.map((grant) => /* @__PURE__ */ jsxs("li", {
|
|
313
|
-
className: "flex items-center gap-2",
|
|
314
|
-
children: [
|
|
315
|
-
/* @__PURE__ */ jsx("span", {
|
|
316
|
-
className: "font-mono",
|
|
317
|
-
children: grant.userId
|
|
318
|
-
}),
|
|
319
|
-
/* @__PURE__ */ jsx(Badge, {
|
|
320
|
-
variant: "outline",
|
|
321
|
-
children: grant.status
|
|
322
|
-
}),
|
|
323
|
-
grant.expiresAt ? /* @__PURE__ */ jsx("span", { children: tr("fluidgrids.agents.until", "until {{date}}", { date: new Date(grant.expiresAt).toLocaleDateString() }) }) : null,
|
|
324
|
-
!grant.isMine && grant.status === "ACTIVE" ? /* @__PURE__ */ jsx(Button$1, {
|
|
325
|
-
variant: "ghost",
|
|
326
|
-
size: "sm",
|
|
327
|
-
disabled: busy,
|
|
328
|
-
onClick: () => void run(() => onRevoke(agent.id, grant.userId), tr("fluidgrids.agents.revoked", "Grant revoked")),
|
|
329
|
-
children: tr("fluidgrids.agents.revoke", "Revoke")
|
|
330
|
-
}) : null
|
|
331
|
-
]
|
|
332
|
-
}, grant.userId))
|
|
333
|
-
}) : null]
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
function AgentsTab() {
|
|
337
|
-
const tr = useTr();
|
|
338
|
-
const { agents, loading, error, save, remove, grants, grant, revoke } = useAgentDefinitions();
|
|
339
|
-
const [editing, setEditing] = useState(null);
|
|
340
|
-
const onSave = async (form) => {
|
|
341
|
-
const input = formToInput$2(form);
|
|
342
|
-
await save(editing?.id ?? null, input);
|
|
343
|
-
toast.success(tr("fluidgrids.agents.saved", "Agent saved"));
|
|
344
|
-
setEditing(null);
|
|
345
|
-
};
|
|
346
|
-
const onDelete = async (agent) => {
|
|
347
|
-
try {
|
|
348
|
-
await remove(agent.id);
|
|
349
|
-
toast.success(tr("fluidgrids.agents.deleted", "Agent deleted"));
|
|
350
|
-
} catch (err) {
|
|
351
|
-
toast.error(errorMessage$2(err));
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
355
|
-
className: "space-y-4",
|
|
356
|
-
"data-testid": "agents-tab",
|
|
357
|
-
children: [
|
|
358
|
-
/* @__PURE__ */ jsxs("div", {
|
|
359
|
-
className: "flex flex-wrap items-start justify-between gap-2",
|
|
360
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
|
|
361
|
-
className: "text-base font-semibold text-text-primary",
|
|
362
|
-
children: tr("fluidgrids.agents.title", "Agents")
|
|
363
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
364
|
-
className: "text-sm text-text-secondary",
|
|
365
|
-
children: tr("fluidgrids.agents.subtitle", "Give an AI Agent node an identity of its own. It can only call the tools and connections you allow, only for people who granted it, and every tool call is recorded under the agent and the person.")
|
|
366
|
-
})] }), !editing ? /* @__PURE__ */ jsxs(Button$1, {
|
|
367
|
-
onClick: () => setEditing({
|
|
368
|
-
id: null,
|
|
369
|
-
form: emptyAgentForm()
|
|
370
|
-
}),
|
|
371
|
-
"data-testid": "agent-definition-new",
|
|
372
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
373
|
-
icon: "mdi:plus",
|
|
374
|
-
width: 16
|
|
375
|
-
}), tr("fluidgrids.agents.new", "New agent")]
|
|
376
|
-
}) : null]
|
|
377
|
-
}),
|
|
378
|
-
editing ? /* @__PURE__ */ jsx(AgentEditor, {
|
|
379
|
-
initial: editing.form,
|
|
380
|
-
onCancel: () => setEditing(null),
|
|
381
|
-
onSave
|
|
382
|
-
}) : null,
|
|
383
|
-
error ? /* @__PURE__ */ jsx("p", {
|
|
384
|
-
className: "text-sm text-destructive",
|
|
385
|
-
children: errorMessage$2(error)
|
|
386
|
-
}) : null,
|
|
387
|
-
loading ? /* @__PURE__ */ jsx("p", {
|
|
388
|
-
className: "text-sm text-text-secondary",
|
|
389
|
-
children: tr("fluidgrids.agents.loading", "Loading agents…")
|
|
390
|
-
}) : agents.length === 0 && !error ? /* @__PURE__ */ jsx("p", {
|
|
391
|
-
className: "text-sm text-text-secondary",
|
|
392
|
-
children: tr("fluidgrids.agents.empty", "No agents yet.")
|
|
393
|
-
}) : /* @__PURE__ */ jsx("ul", {
|
|
394
|
-
className: "space-y-3",
|
|
395
|
-
children: agents.map((agent) => /* @__PURE__ */ jsxs("li", {
|
|
396
|
-
className: "rounded-lg border border-border-secondary bg-background-secondary p-3 space-y-2",
|
|
397
|
-
"data-testid": `agent-definition-${agent.id}`,
|
|
398
|
-
children: [
|
|
399
|
-
/* @__PURE__ */ jsxs("div", {
|
|
400
|
-
className: "flex flex-wrap items-center justify-between gap-2",
|
|
401
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
402
|
-
className: "flex items-center gap-2",
|
|
403
|
-
children: [
|
|
404
|
-
/* @__PURE__ */ jsx(Icon, {
|
|
405
|
-
icon: "mdi:robot-outline",
|
|
406
|
-
width: 18
|
|
407
|
-
}),
|
|
408
|
-
/* @__PURE__ */ jsx("span", {
|
|
409
|
-
className: "font-medium text-text-primary",
|
|
410
|
-
children: agent.name
|
|
411
|
-
}),
|
|
412
|
-
/* @__PURE__ */ jsx(Badge, {
|
|
413
|
-
variant: "outline",
|
|
414
|
-
children: agent.status
|
|
415
|
-
}),
|
|
416
|
-
agent.requireToolApproval ? /* @__PURE__ */ jsx(Badge, {
|
|
417
|
-
variant: "outline",
|
|
418
|
-
children: tr("fluidgrids.agents.approvalBadge", "Approval required")
|
|
419
|
-
}) : null
|
|
420
|
-
]
|
|
421
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
422
|
-
className: "flex gap-1",
|
|
423
|
-
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
424
|
-
variant: "ghost",
|
|
425
|
-
size: "sm",
|
|
426
|
-
onClick: () => setEditing({
|
|
427
|
-
id: agent.id,
|
|
428
|
-
form: agentToForm(agent)
|
|
429
|
-
}),
|
|
430
|
-
children: tr("fluidgrids.agents.edit", "Edit")
|
|
431
|
-
}), /* @__PURE__ */ jsx(Button$1, {
|
|
432
|
-
variant: "ghost",
|
|
433
|
-
size: "sm",
|
|
434
|
-
onClick: () => void onDelete(agent),
|
|
435
|
-
children: tr("fluidgrids.agents.delete", "Delete")
|
|
436
|
-
})]
|
|
437
|
-
})]
|
|
438
|
-
}),
|
|
439
|
-
agent.description ? /* @__PURE__ */ jsx("p", {
|
|
440
|
-
className: "text-sm text-text-secondary",
|
|
441
|
-
children: agent.description
|
|
442
|
-
}) : null,
|
|
443
|
-
/* @__PURE__ */ jsx("div", {
|
|
444
|
-
className: "text-xs text-text-secondary",
|
|
445
|
-
children: agent.toolAllowlist.length > 0 ? agent.toolAllowlist.map(describeRule).join(" · ") : tr("fluidgrids.agents.noTools", "No tools allowed")
|
|
446
|
-
}),
|
|
447
|
-
/* @__PURE__ */ jsx("div", {
|
|
448
|
-
className: "text-xs text-text-tertiary",
|
|
449
|
-
children: tr("fluidgrids.agents.connectionCount", "{{count}} connections in scope", { count: agent.connectionScope.length })
|
|
450
|
-
}),
|
|
451
|
-
/* @__PURE__ */ jsx(AgentGrants, {
|
|
452
|
-
agent,
|
|
453
|
-
loadGrants: grants,
|
|
454
|
-
onGrant: grant,
|
|
455
|
-
onRevoke: revoke
|
|
456
|
-
})
|
|
457
|
-
]
|
|
458
|
-
}, agent.id))
|
|
459
|
-
})
|
|
460
|
-
]
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
|
|
464
34
|
// src/modules/action-policies/utils/policy-form.ts
|
|
465
35
|
const POLICY_EFFECTS = [
|
|
466
36
|
"REQUIRE_APPROVAL",
|
|
@@ -744,7 +314,7 @@ function needsAllowFallback(draft, others) {
|
|
|
744
314
|
* rule; changing a policy or deciding an approval needs workflow publish rights.
|
|
745
315
|
*/
|
|
746
316
|
const FIELD$1 = "rounded-md border border-border-secondary bg-background-secondary px-2 py-1.5 text-sm text-text-primary";
|
|
747
|
-
function errorMessage$
|
|
317
|
+
function errorMessage$2(err) {
|
|
748
318
|
if (err instanceof PolicyFormError) return err.message;
|
|
749
319
|
return describeOperationError(err);
|
|
750
320
|
}
|
|
@@ -858,7 +428,7 @@ function PolicyEditor({ initial, conditionEditable, storedCondition, others, onC
|
|
|
858
428
|
try {
|
|
859
429
|
await action();
|
|
860
430
|
} catch (err) {
|
|
861
|
-
toast.error(errorMessage$
|
|
431
|
+
toast.error(errorMessage$2(err));
|
|
862
432
|
} finally {
|
|
863
433
|
setBusy(false);
|
|
864
434
|
}
|
|
@@ -1213,7 +783,7 @@ function ApprovalsList({ approvals, onDecide }) {
|
|
|
1213
783
|
await onDecide(approval.id, true);
|
|
1214
784
|
toast.success(tr("fluidgrids.actionPolicies.approved", "Approved — the run continues"));
|
|
1215
785
|
} catch (err) {
|
|
1216
|
-
toast.error(errorMessage$
|
|
786
|
+
toast.error(errorMessage$2(err));
|
|
1217
787
|
} finally {
|
|
1218
788
|
setBusy(null);
|
|
1219
789
|
}
|
|
@@ -1229,7 +799,7 @@ function ApprovalsList({ approvals, onDecide }) {
|
|
|
1229
799
|
await onDecide(approval.id, false);
|
|
1230
800
|
toast.success(tr("fluidgrids.actionPolicies.declined", "Declined — the node fails"));
|
|
1231
801
|
} catch (err) {
|
|
1232
|
-
toast.error(errorMessage$
|
|
802
|
+
toast.error(errorMessage$2(err));
|
|
1233
803
|
} finally {
|
|
1234
804
|
setBusy(null);
|
|
1235
805
|
}
|
|
@@ -1356,7 +926,7 @@ function ActionPoliciesTab() {
|
|
|
1356
926
|
try {
|
|
1357
927
|
setPreview(await dryRun({ excludePolicyId: policy.id }));
|
|
1358
928
|
} catch (err) {
|
|
1359
|
-
toast.error(errorMessage$
|
|
929
|
+
toast.error(errorMessage$2(err));
|
|
1360
930
|
}
|
|
1361
931
|
},
|
|
1362
932
|
children: tr("fluidgrids.actionPolicies.withoutIt", "Without it")
|
|
@@ -1389,7 +959,7 @@ function ActionPoliciesTab() {
|
|
|
1389
959
|
await remove(policy.id);
|
|
1390
960
|
toast.success(tr("fluidgrids.actionPolicies.deleted", "Policy deleted"));
|
|
1391
961
|
} catch (err) {
|
|
1392
|
-
toast.error(errorMessage$
|
|
962
|
+
toast.error(errorMessage$2(err));
|
|
1393
963
|
}
|
|
1394
964
|
},
|
|
1395
965
|
children: confirmDelete === policy.id ? tr("fluidgrids.actionPolicies.confirmDelete", "Confirm delete") : tr("fluidgrids.actionPolicies.delete", "Delete")
|
|
@@ -2744,1190 +2314,1621 @@ function WorkflowNotificationsTab({ workflowKey }) {
|
|
|
2744
2314
|
})
|
|
2745
2315
|
})
|
|
2746
2316
|
]
|
|
2747
|
-
}),
|
|
2748
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2749
|
-
className: "flex flex-wrap items-center justify-between gap-3",
|
|
2750
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
2751
|
-
className: "text-xs text-text-secondary",
|
|
2752
|
-
children: settings && !settings.customized ? tr("fluidgrids.settings.workflowNotifications.defaultsNote", "These are the defaults; nobody has changed them for this workflow yet.") : null
|
|
2753
|
-
}), /* @__PURE__ */ jsx(Button$1, {
|
|
2754
|
-
"data-agent": "confirm",
|
|
2755
|
-
onClick: () => void handleSave(),
|
|
2756
|
-
disabled: locked,
|
|
2757
|
-
"data-testid": "workflow-notifications-save",
|
|
2758
|
-
children: saving ? tr("fluidgrids.settings.execution.saving", "Saving…") : tr("fluidgrids.settings.execution.save", "Save")
|
|
2759
|
-
})]
|
|
2317
|
+
}),
|
|
2318
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2319
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
2320
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
2321
|
+
className: "text-xs text-text-secondary",
|
|
2322
|
+
children: settings && !settings.customized ? tr("fluidgrids.settings.workflowNotifications.defaultsNote", "These are the defaults; nobody has changed them for this workflow yet.") : null
|
|
2323
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
2324
|
+
"data-agent": "confirm",
|
|
2325
|
+
onClick: () => void handleSave(),
|
|
2326
|
+
disabled: locked,
|
|
2327
|
+
"data-testid": "workflow-notifications-save",
|
|
2328
|
+
children: saving ? tr("fluidgrids.settings.execution.saving", "Saving…") : tr("fluidgrids.settings.execution.save", "Save")
|
|
2329
|
+
})]
|
|
2330
|
+
})
|
|
2331
|
+
]
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// src/modules/settings/components/RunControlTab.tsx
|
|
2336
|
+
/**
|
|
2337
|
+
* Settings → Run control (BOFF-9906, Phase C): the workspace's activity pause
|
|
2338
|
+
* rules (run control), its search attribute definitions (search attributes),
|
|
2339
|
+
* the archive destination of finished runs (run archival) and the codec
|
|
2340
|
+
* server the run page decodes encoded payloads with (self-hosted workers).
|
|
2341
|
+
* Each card shows, and reads, only while its own flag is on; the tab shows
|
|
2342
|
+
* while any of them is.
|
|
2343
|
+
*/
|
|
2344
|
+
/**
|
|
2345
|
+
* Whether the Run control tab has anything to show. Pause rules act on durable
|
|
2346
|
+
* activities, so the service refuses their writes unless durable functions are
|
|
2347
|
+
* on as well (`runControlDurable`, not `runControl` alone).
|
|
2348
|
+
*/
|
|
2349
|
+
function hasRunControlSettings(flags) {
|
|
2350
|
+
return flags.runControlDurable || flags.searchAttributes || flags.runArchival || flags.selfHostedWorkers;
|
|
2351
|
+
}
|
|
2352
|
+
function RunControlTab({ flags }) {
|
|
2353
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2354
|
+
className: "space-y-6",
|
|
2355
|
+
"data-testid": "settings-run-control",
|
|
2356
|
+
children: [
|
|
2357
|
+
flags.runControlDurable && /* @__PURE__ */ jsx(PauseRulesCard, {}),
|
|
2358
|
+
flags.searchAttributes && /* @__PURE__ */ jsx(SearchAttributeDefinitionsCard, {}),
|
|
2359
|
+
flags.runArchival && /* @__PURE__ */ jsx(ArchiveSettingsCard, {}),
|
|
2360
|
+
flags.selfHostedWorkers && /* @__PURE__ */ jsx(CodecServerCard, {})
|
|
2361
|
+
]
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
function Card$1({ icon, title, description, children, testId }) {
|
|
2365
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
2366
|
+
className: "rounded-lg border border-border-secondary bg-background-secondary p-4 sm:p-6",
|
|
2367
|
+
"data-testid": testId,
|
|
2368
|
+
children: [
|
|
2369
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2370
|
+
className: "mb-1 flex items-center gap-2",
|
|
2371
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
2372
|
+
icon,
|
|
2373
|
+
width: 18,
|
|
2374
|
+
className: "text-text-secondary"
|
|
2375
|
+
}), /* @__PURE__ */ jsx("h3", {
|
|
2376
|
+
className: "text-base font-semibold text-text-primary",
|
|
2377
|
+
children: title
|
|
2378
|
+
})]
|
|
2379
|
+
}),
|
|
2380
|
+
/* @__PURE__ */ jsx("p", {
|
|
2381
|
+
className: "mb-4 text-sm text-text-secondary",
|
|
2382
|
+
children: description
|
|
2383
|
+
}),
|
|
2384
|
+
children
|
|
2385
|
+
]
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
function PauseRulesCard() {
|
|
2389
|
+
const tr = useTr();
|
|
2390
|
+
const rules = useResource("pause-rules", true, fetchPauseRules);
|
|
2391
|
+
const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE);
|
|
2392
|
+
const [form, setForm] = useState({
|
|
2393
|
+
activityType: "",
|
|
2394
|
+
workflowKey: "",
|
|
2395
|
+
reason: "",
|
|
2396
|
+
expiresAt: ""
|
|
2397
|
+
});
|
|
2398
|
+
const [busy, setBusy] = useState(false);
|
|
2399
|
+
const [error, setError] = useState(null);
|
|
2400
|
+
const expires = form.expiresAt ? new Date(form.expiresAt) : null;
|
|
2401
|
+
const valid = (form.activityType.trim() || form.workflowKey.trim()) && form.reason.trim() && (!expires || !Number.isNaN(expires.getTime()) && expires.getTime() > Date.now());
|
|
2402
|
+
const create = async () => {
|
|
2403
|
+
if (!valid) return;
|
|
2404
|
+
setBusy(true);
|
|
2405
|
+
setError(null);
|
|
2406
|
+
try {
|
|
2407
|
+
await createPauseRule({
|
|
2408
|
+
activityType: form.activityType.trim() || null,
|
|
2409
|
+
workflowKey: form.workflowKey.trim() || null,
|
|
2410
|
+
reason: form.reason.trim(),
|
|
2411
|
+
expiresAt: expires ? expires.toISOString() : null
|
|
2412
|
+
});
|
|
2413
|
+
setForm({
|
|
2414
|
+
activityType: "",
|
|
2415
|
+
workflowKey: "",
|
|
2416
|
+
reason: "",
|
|
2417
|
+
expiresAt: ""
|
|
2418
|
+
});
|
|
2419
|
+
await rules.refetch();
|
|
2420
|
+
} catch (err) {
|
|
2421
|
+
setError(describeOperationError(err));
|
|
2422
|
+
} finally {
|
|
2423
|
+
setBusy(false);
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
const remove = async (id) => {
|
|
2427
|
+
try {
|
|
2428
|
+
await deletePauseRule(id);
|
|
2429
|
+
toast.success(tr("fluidgrids.settings.pauseRules.deleted", "Rule deleted; what it paused is unpaused"));
|
|
2430
|
+
await rules.refetch();
|
|
2431
|
+
} catch (err) {
|
|
2432
|
+
toast.error(describeOperationError(err));
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
return /* @__PURE__ */ jsxs(Card$1, {
|
|
2436
|
+
icon: "mdi:pause-octagon-outline",
|
|
2437
|
+
title: tr("fluidgrids.settings.pauseRules.title", "Activity pause rules"),
|
|
2438
|
+
description: tr("fluidgrids.settings.pauseRules.description", "A rule pauses matching durable activities when they are scheduled or retried, for example while a downstream system is down. Deleting it unpauses them."),
|
|
2439
|
+
testId: "settings-pause-rules",
|
|
2440
|
+
children: [
|
|
2441
|
+
/* @__PURE__ */ jsx(FieldError, { message: rules.error }),
|
|
2442
|
+
rules.data && rules.data.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
2443
|
+
className: "mb-3 text-sm text-text-secondary",
|
|
2444
|
+
children: tr("fluidgrids.settings.pauseRules.empty", "No pause rules.")
|
|
2445
|
+
}),
|
|
2446
|
+
rules.data && rules.data.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
2447
|
+
className: "mb-4 divide-y divide-border-secondary rounded-md border border-border-secondary",
|
|
2448
|
+
children: rules.data.map((rule) => /* @__PURE__ */ jsxs("li", {
|
|
2449
|
+
className: "flex flex-wrap items-center gap-3 px-3 py-2 text-sm",
|
|
2450
|
+
"data-testid": `pause-rule-${rule.id}`,
|
|
2451
|
+
children: [
|
|
2452
|
+
/* @__PURE__ */ jsx("code", {
|
|
2453
|
+
className: "font-mono text-xs text-text-primary",
|
|
2454
|
+
children: rule.activityType ?? rule.nodeType ?? "*"
|
|
2455
|
+
}),
|
|
2456
|
+
rule.workflowKey && /* @__PURE__ */ jsx("span", {
|
|
2457
|
+
className: "text-xs text-text-secondary",
|
|
2458
|
+
children: rule.workflowKey
|
|
2459
|
+
}),
|
|
2460
|
+
/* @__PURE__ */ jsx("span", {
|
|
2461
|
+
className: "text-text-primary",
|
|
2462
|
+
children: rule.reason
|
|
2463
|
+
}),
|
|
2464
|
+
rule.expiresAt && /* @__PURE__ */ jsx("span", {
|
|
2465
|
+
className: "text-xs text-text-secondary",
|
|
2466
|
+
children: tr("fluidgrids.settings.pauseRules.until", "until {{date}}", { date: new Date(rule.expiresAt).toLocaleString() })
|
|
2467
|
+
}),
|
|
2468
|
+
canEdit && /* @__PURE__ */ jsx(Button$1, {
|
|
2469
|
+
size: "sm",
|
|
2470
|
+
variant: "ghost",
|
|
2471
|
+
className: "ml-auto text-[var(--color-status-error-text)]",
|
|
2472
|
+
onClick: () => void remove(rule.id),
|
|
2473
|
+
"data-testid": `pause-rule-delete-${rule.id}`,
|
|
2474
|
+
children: tr("fluidgrids.settings.common.delete", "Delete")
|
|
2475
|
+
})
|
|
2476
|
+
]
|
|
2477
|
+
}, rule.id))
|
|
2478
|
+
}),
|
|
2479
|
+
canEdit && /* @__PURE__ */ jsxs("div", {
|
|
2480
|
+
className: "grid gap-3 sm:grid-cols-2",
|
|
2481
|
+
children: [
|
|
2482
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2483
|
+
className: "space-y-1.5",
|
|
2484
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
2485
|
+
htmlFor: "pause-rule-type",
|
|
2486
|
+
children: tr("fluidgrids.settings.pauseRules.activityType", "Activity or node type")
|
|
2487
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
2488
|
+
id: "pause-rule-type",
|
|
2489
|
+
value: form.activityType,
|
|
2490
|
+
placeholder: "store/crm",
|
|
2491
|
+
onChange: (event) => setForm({
|
|
2492
|
+
...form,
|
|
2493
|
+
activityType: event.target.value
|
|
2494
|
+
}),
|
|
2495
|
+
"data-testid": "pause-rule-type"
|
|
2496
|
+
})]
|
|
2497
|
+
}),
|
|
2498
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2499
|
+
className: "space-y-1.5",
|
|
2500
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
2501
|
+
htmlFor: "pause-rule-workflow",
|
|
2502
|
+
children: tr("fluidgrids.settings.pauseRules.workflowKey", "Workflow key (optional)")
|
|
2503
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
2504
|
+
id: "pause-rule-workflow",
|
|
2505
|
+
value: form.workflowKey,
|
|
2506
|
+
onChange: (event) => setForm({
|
|
2507
|
+
...form,
|
|
2508
|
+
workflowKey: event.target.value
|
|
2509
|
+
}),
|
|
2510
|
+
"data-testid": "pause-rule-workflow"
|
|
2511
|
+
})]
|
|
2512
|
+
}),
|
|
2513
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2514
|
+
className: "space-y-1.5",
|
|
2515
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
2516
|
+
htmlFor: "pause-rule-reason",
|
|
2517
|
+
children: tr("fluidgrids.settings.pauseRules.reason", "Reason")
|
|
2518
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
2519
|
+
id: "pause-rule-reason",
|
|
2520
|
+
value: form.reason,
|
|
2521
|
+
onChange: (event) => setForm({
|
|
2522
|
+
...form,
|
|
2523
|
+
reason: event.target.value
|
|
2524
|
+
}),
|
|
2525
|
+
"data-testid": "pause-rule-reason"
|
|
2526
|
+
})]
|
|
2527
|
+
}),
|
|
2528
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2529
|
+
className: "space-y-1.5",
|
|
2530
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
2531
|
+
htmlFor: "pause-rule-expires",
|
|
2532
|
+
children: tr("fluidgrids.settings.pauseRules.expiresAt", "Ends at (optional)")
|
|
2533
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
2534
|
+
id: "pause-rule-expires",
|
|
2535
|
+
type: "datetime-local",
|
|
2536
|
+
value: form.expiresAt,
|
|
2537
|
+
onChange: (event) => setForm({
|
|
2538
|
+
...form,
|
|
2539
|
+
expiresAt: event.target.value
|
|
2540
|
+
}),
|
|
2541
|
+
"data-testid": "pause-rule-expires"
|
|
2542
|
+
})]
|
|
2543
|
+
}),
|
|
2544
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2545
|
+
className: "sm:col-span-2",
|
|
2546
|
+
children: [/* @__PURE__ */ jsx(FieldError, {
|
|
2547
|
+
message: error,
|
|
2548
|
+
testId: "pause-rule-error"
|
|
2549
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
2550
|
+
onClick: () => void create(),
|
|
2551
|
+
disabled: !valid || busy,
|
|
2552
|
+
"data-testid": "pause-rule-create",
|
|
2553
|
+
children: tr("fluidgrids.settings.pauseRules.create", "Add rule")
|
|
2554
|
+
})]
|
|
2555
|
+
})
|
|
2556
|
+
]
|
|
2760
2557
|
})
|
|
2761
2558
|
]
|
|
2762
2559
|
});
|
|
2763
2560
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
*/
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2561
|
+
const ATTRIBUTE_TYPES = [
|
|
2562
|
+
"KEYWORD",
|
|
2563
|
+
"TEXT",
|
|
2564
|
+
"INT",
|
|
2565
|
+
"DOUBLE",
|
|
2566
|
+
"BOOL",
|
|
2567
|
+
"DATETIME",
|
|
2568
|
+
"KEYWORD_LIST"
|
|
2569
|
+
];
|
|
2570
|
+
/** A letter, then letters, digits and underscores (at most 64). */
|
|
2571
|
+
const SEARCH_ATTRIBUTE_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
2572
|
+
function SearchAttributeDefinitionsCard() {
|
|
2573
|
+
const tr = useTr();
|
|
2574
|
+
const definitions = useResource("search-attributes", true, fetchSearchAttributes);
|
|
2575
|
+
const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE);
|
|
2576
|
+
const [name, setName] = useState("");
|
|
2577
|
+
const [type, setType] = useState("KEYWORD");
|
|
2578
|
+
const [description, setDescription] = useState("");
|
|
2579
|
+
const [busy, setBusy] = useState(false);
|
|
2580
|
+
const [error, setError] = useState(null);
|
|
2581
|
+
const validName = SEARCH_ATTRIBUTE_NAME.test(name.trim());
|
|
2582
|
+
const create = async () => {
|
|
2583
|
+
if (!validName) return;
|
|
2584
|
+
setBusy(true);
|
|
2585
|
+
setError(null);
|
|
2586
|
+
try {
|
|
2587
|
+
await createSearchAttribute({
|
|
2588
|
+
name,
|
|
2589
|
+
type,
|
|
2590
|
+
description
|
|
2591
|
+
});
|
|
2592
|
+
setName("");
|
|
2593
|
+
setDescription("");
|
|
2594
|
+
await definitions.refetch();
|
|
2595
|
+
} catch (err) {
|
|
2596
|
+
setError(describeOperationError(err));
|
|
2597
|
+
} finally {
|
|
2598
|
+
setBusy(false);
|
|
2599
|
+
}
|
|
2600
|
+
};
|
|
2601
|
+
const remove = async (id) => {
|
|
2602
|
+
try {
|
|
2603
|
+
await deleteSearchAttribute(id);
|
|
2604
|
+
await definitions.refetch();
|
|
2605
|
+
} catch (err) {
|
|
2606
|
+
toast.error(describeOperationError(err));
|
|
2607
|
+
}
|
|
2608
|
+
};
|
|
2609
|
+
return /* @__PURE__ */ jsxs(Card$1, {
|
|
2610
|
+
icon: "mdi:tag-search-outline",
|
|
2611
|
+
title: tr("fluidgrids.settings.searchAttributes.title", "Search attributes"),
|
|
2612
|
+
description: tr("fluidgrids.settings.searchAttributes.description", "Typed values runs carry so the Runs list can filter on them (at most 50). Deleting one leaves the runs' values in place."),
|
|
2613
|
+
testId: "settings-search-attributes",
|
|
2798
2614
|
children: [
|
|
2799
|
-
/* @__PURE__ */
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2615
|
+
/* @__PURE__ */ jsx(FieldError, { message: definitions.error }),
|
|
2616
|
+
definitions.data && definitions.data.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
2617
|
+
className: "mb-4 divide-y divide-border-secondary rounded-md border border-border-secondary",
|
|
2618
|
+
children: definitions.data.map((definition) => /* @__PURE__ */ jsxs("li", {
|
|
2619
|
+
className: "flex flex-wrap items-center gap-3 px-3 py-2 text-sm",
|
|
2620
|
+
"data-testid": `search-attribute-${definition.name}`,
|
|
2621
|
+
children: [
|
|
2622
|
+
/* @__PURE__ */ jsx("code", {
|
|
2623
|
+
className: "font-mono text-xs text-text-primary",
|
|
2624
|
+
children: definition.name
|
|
2625
|
+
}),
|
|
2626
|
+
/* @__PURE__ */ jsx(StatusPill, {
|
|
2627
|
+
tone: "neutral",
|
|
2628
|
+
children: definition.type
|
|
2629
|
+
}),
|
|
2630
|
+
definition.description && /* @__PURE__ */ jsx("span", {
|
|
2631
|
+
className: "text-xs text-text-secondary",
|
|
2632
|
+
children: definition.description
|
|
2633
|
+
}),
|
|
2634
|
+
canEdit && /* @__PURE__ */ jsx(Button$1, {
|
|
2635
|
+
size: "sm",
|
|
2636
|
+
variant: "ghost",
|
|
2637
|
+
className: "ml-auto text-[var(--color-status-error-text)]",
|
|
2638
|
+
onClick: () => void remove(definition.id),
|
|
2639
|
+
children: tr("fluidgrids.settings.common.delete", "Delete")
|
|
2640
|
+
})
|
|
2641
|
+
]
|
|
2642
|
+
}, definition.id))
|
|
2813
2643
|
}),
|
|
2814
|
-
|
|
2644
|
+
canEdit && /* @__PURE__ */ jsxs("div", {
|
|
2645
|
+
className: "grid gap-3 sm:grid-cols-3",
|
|
2646
|
+
children: [
|
|
2647
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2648
|
+
className: "space-y-1.5",
|
|
2649
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
2650
|
+
htmlFor: "search-attribute-name",
|
|
2651
|
+
children: tr("fluidgrids.settings.searchAttributes.name", "Name")
|
|
2652
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
2653
|
+
id: "search-attribute-name",
|
|
2654
|
+
value: name,
|
|
2655
|
+
placeholder: "customerId",
|
|
2656
|
+
onChange: (event) => setName(event.target.value),
|
|
2657
|
+
"data-testid": "search-attribute-name"
|
|
2658
|
+
})]
|
|
2659
|
+
}),
|
|
2660
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2661
|
+
className: "space-y-1.5",
|
|
2662
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
2663
|
+
htmlFor: "search-attribute-type",
|
|
2664
|
+
children: tr("fluidgrids.settings.searchAttributes.type", "Type")
|
|
2665
|
+
}), /* @__PURE__ */ jsx(NativeSelect, {
|
|
2666
|
+
id: "search-attribute-type",
|
|
2667
|
+
value: type,
|
|
2668
|
+
onChange: (value) => setType(ATTRIBUTE_TYPES.find((item) => item === value) ?? "KEYWORD"),
|
|
2669
|
+
testId: "search-attribute-type",
|
|
2670
|
+
children: ATTRIBUTE_TYPES.map((item) => /* @__PURE__ */ jsx("option", {
|
|
2671
|
+
value: item,
|
|
2672
|
+
children: item
|
|
2673
|
+
}, item))
|
|
2674
|
+
})]
|
|
2675
|
+
}),
|
|
2676
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2677
|
+
className: "space-y-1.5",
|
|
2678
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
2679
|
+
htmlFor: "search-attribute-description",
|
|
2680
|
+
children: tr("fluidgrids.settings.searchAttributes.descriptionField", "Description (optional)")
|
|
2681
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
2682
|
+
id: "search-attribute-description",
|
|
2683
|
+
value: description,
|
|
2684
|
+
maxLength: 1e3,
|
|
2685
|
+
onChange: (event) => setDescription(event.target.value)
|
|
2686
|
+
})]
|
|
2687
|
+
}),
|
|
2688
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2689
|
+
className: "sm:col-span-3",
|
|
2690
|
+
children: [
|
|
2691
|
+
name && !validName && /* @__PURE__ */ jsx(FieldError, { message: tr("fluidgrids.settings.searchAttributes.badName", "Start with a letter; use letters, digits and underscores (at most 64).") }),
|
|
2692
|
+
/* @__PURE__ */ jsx(FieldError, {
|
|
2693
|
+
message: error,
|
|
2694
|
+
testId: "search-attribute-error"
|
|
2695
|
+
}),
|
|
2696
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
2697
|
+
onClick: () => void create(),
|
|
2698
|
+
disabled: !validName || busy,
|
|
2699
|
+
"data-testid": "search-attribute-create",
|
|
2700
|
+
children: tr("fluidgrids.settings.searchAttributes.create", "Add attribute")
|
|
2701
|
+
})
|
|
2702
|
+
]
|
|
2703
|
+
})
|
|
2704
|
+
]
|
|
2705
|
+
})
|
|
2815
2706
|
]
|
|
2816
2707
|
});
|
|
2817
2708
|
}
|
|
2818
|
-
|
|
2709
|
+
/** An IAM role ARN in another account (the service refuses its own). */
|
|
2710
|
+
const ROLE_ARN = /^arn:aws[a-z-]*:iam::\d{12}:role\/[\w+=,.@/-]{1,512}$/;
|
|
2711
|
+
/** An S3 bucket name. */
|
|
2712
|
+
const BUCKET_NAME = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
|
|
2713
|
+
function ArchiveSettingsCard() {
|
|
2819
2714
|
const tr = useTr();
|
|
2820
|
-
const
|
|
2821
|
-
const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.
|
|
2715
|
+
const archive = useResource("archive-settings", true, fetchArchiveSettings);
|
|
2716
|
+
const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_DELETE);
|
|
2822
2717
|
const [form, setForm] = useState({
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2718
|
+
enabled: false,
|
|
2719
|
+
bucket: "",
|
|
2720
|
+
prefix: "",
|
|
2721
|
+
region: "",
|
|
2722
|
+
roleArn: "",
|
|
2723
|
+
includePayloads: true
|
|
2827
2724
|
});
|
|
2828
2725
|
const [busy, setBusy] = useState(false);
|
|
2829
2726
|
const [error, setError] = useState(null);
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2727
|
+
useEffect(() => {
|
|
2728
|
+
const current = archive.data;
|
|
2729
|
+
if (current) setForm({
|
|
2730
|
+
enabled: current.enabled,
|
|
2731
|
+
bucket: current.bucket,
|
|
2732
|
+
prefix: current.prefix,
|
|
2733
|
+
region: current.region,
|
|
2734
|
+
roleArn: current.roleArn,
|
|
2735
|
+
includePayloads: current.includePayloads
|
|
2736
|
+
});
|
|
2737
|
+
}, [archive.data]);
|
|
2738
|
+
const valid = BUCKET_NAME.test(form.bucket.trim()) && /^[a-z]{2}-[a-z]+-\d$/.test(form.region.trim()) && ROLE_ARN.test(form.roleArn.trim());
|
|
2739
|
+
const save = async () => {
|
|
2833
2740
|
if (!valid) return;
|
|
2834
2741
|
setBusy(true);
|
|
2835
2742
|
setError(null);
|
|
2836
2743
|
try {
|
|
2837
|
-
await
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
reason: "",
|
|
2847
|
-
expiresAt: ""
|
|
2848
|
-
});
|
|
2849
|
-
await rules.refetch();
|
|
2744
|
+
archive.set(await saveArchiveSettings({
|
|
2745
|
+
enabled: form.enabled,
|
|
2746
|
+
bucket: form.bucket.trim(),
|
|
2747
|
+
prefix: form.prefix.trim().replace(/^\/+|\/+$/g, ""),
|
|
2748
|
+
region: form.region.trim(),
|
|
2749
|
+
roleArn: form.roleArn.trim(),
|
|
2750
|
+
includePayloads: form.includePayloads
|
|
2751
|
+
}));
|
|
2752
|
+
toast.success(tr("fluidgrids.settings.archive.saved", "Archive destination saved"));
|
|
2850
2753
|
} catch (err) {
|
|
2851
2754
|
setError(describeOperationError(err));
|
|
2852
2755
|
} finally {
|
|
2853
2756
|
setBusy(false);
|
|
2854
2757
|
}
|
|
2855
2758
|
};
|
|
2856
|
-
const
|
|
2759
|
+
const clear = async () => {
|
|
2760
|
+
setBusy(true);
|
|
2761
|
+
setError(null);
|
|
2857
2762
|
try {
|
|
2858
|
-
await
|
|
2859
|
-
|
|
2860
|
-
|
|
2763
|
+
await clearArchiveSettings();
|
|
2764
|
+
archive.set(null);
|
|
2765
|
+
setForm({
|
|
2766
|
+
enabled: false,
|
|
2767
|
+
bucket: "",
|
|
2768
|
+
prefix: "",
|
|
2769
|
+
region: "",
|
|
2770
|
+
roleArn: "",
|
|
2771
|
+
includePayloads: true
|
|
2772
|
+
});
|
|
2861
2773
|
} catch (err) {
|
|
2862
|
-
|
|
2774
|
+
setError(describeOperationError(err));
|
|
2775
|
+
} finally {
|
|
2776
|
+
setBusy(false);
|
|
2863
2777
|
}
|
|
2864
2778
|
};
|
|
2865
2779
|
return /* @__PURE__ */ jsxs(Card$1, {
|
|
2866
|
-
icon: "mdi:
|
|
2867
|
-
title: tr("fluidgrids.settings.
|
|
2868
|
-
description: tr("fluidgrids.settings.
|
|
2869
|
-
testId: "settings-
|
|
2780
|
+
icon: "mdi:archive-arrow-down-outline",
|
|
2781
|
+
title: tr("fluidgrids.settings.archive.title", "Run archive"),
|
|
2782
|
+
description: tr("fluidgrids.settings.archive.description", "Before retention deletes a finished run, it is written to an S3 bucket in your AWS account. The service assumes your role with this workspace's id as the external id, and checks the destination with a probe object when you enable it."),
|
|
2783
|
+
testId: "settings-archive",
|
|
2870
2784
|
children: [
|
|
2871
|
-
/* @__PURE__ */ jsx(
|
|
2872
|
-
|
|
2873
|
-
className: "mb-3 text-sm text-text
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
className: "flex flex-wrap items-center gap-3 px-3 py-2 text-sm",
|
|
2880
|
-
"data-testid": `pause-rule-${rule.id}`,
|
|
2881
|
-
children: [
|
|
2882
|
-
/* @__PURE__ */ jsx("code", {
|
|
2883
|
-
className: "font-mono text-xs text-text-primary",
|
|
2884
|
-
children: rule.activityType ?? rule.nodeType ?? "*"
|
|
2885
|
-
}),
|
|
2886
|
-
rule.workflowKey && /* @__PURE__ */ jsx("span", {
|
|
2887
|
-
className: "text-xs text-text-secondary",
|
|
2888
|
-
children: rule.workflowKey
|
|
2889
|
-
}),
|
|
2890
|
-
/* @__PURE__ */ jsx("span", {
|
|
2891
|
-
className: "text-text-primary",
|
|
2892
|
-
children: rule.reason
|
|
2893
|
-
}),
|
|
2894
|
-
rule.expiresAt && /* @__PURE__ */ jsx("span", {
|
|
2895
|
-
className: "text-xs text-text-secondary",
|
|
2896
|
-
children: tr("fluidgrids.settings.pauseRules.until", "until {{date}}", { date: new Date(rule.expiresAt).toLocaleString() })
|
|
2897
|
-
}),
|
|
2898
|
-
canEdit && /* @__PURE__ */ jsx(Button$1, {
|
|
2899
|
-
size: "sm",
|
|
2900
|
-
variant: "ghost",
|
|
2901
|
-
className: "ml-auto text-[var(--color-status-error-text)]",
|
|
2902
|
-
onClick: () => void remove(rule.id),
|
|
2903
|
-
"data-testid": `pause-rule-delete-${rule.id}`,
|
|
2904
|
-
children: tr("fluidgrids.settings.common.delete", "Delete")
|
|
2905
|
-
})
|
|
2906
|
-
]
|
|
2907
|
-
}, rule.id))
|
|
2785
|
+
archive.data && archive.data.failingRuns > 0 && /* @__PURE__ */ jsx("div", {
|
|
2786
|
+
role: "status",
|
|
2787
|
+
className: "mb-4 rounded-md border border-[var(--color-status-warning-border)] bg-[var(--color-status-warning-bg-subtle)] p-3 text-sm text-[var(--color-status-warning-text)]",
|
|
2788
|
+
"data-testid": "archive-failing",
|
|
2789
|
+
children: tr("fluidgrids.settings.archive.failing", "{{count}} runs could not be archived, so their purge waits. Last error: {{error}}", {
|
|
2790
|
+
count: archive.data.failingRuns,
|
|
2791
|
+
error: archive.data.lastError ?? "—"
|
|
2792
|
+
})
|
|
2908
2793
|
}),
|
|
2909
|
-
|
|
2794
|
+
/* @__PURE__ */ jsx(FieldError, { message: archive.error }),
|
|
2795
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2910
2796
|
className: "grid gap-3 sm:grid-cols-2",
|
|
2911
2797
|
children: [
|
|
2912
2798
|
/* @__PURE__ */ jsxs("div", {
|
|
2913
2799
|
className: "space-y-1.5",
|
|
2914
2800
|
children: [/* @__PURE__ */ jsx(Label, {
|
|
2915
|
-
htmlFor: "
|
|
2916
|
-
children: tr("fluidgrids.settings.
|
|
2801
|
+
htmlFor: "archive-bucket",
|
|
2802
|
+
children: tr("fluidgrids.settings.archive.bucket", "Bucket")
|
|
2917
2803
|
}), /* @__PURE__ */ jsx(Input, {
|
|
2918
|
-
id: "
|
|
2919
|
-
value: form.
|
|
2920
|
-
|
|
2804
|
+
id: "archive-bucket",
|
|
2805
|
+
value: form.bucket,
|
|
2806
|
+
disabled: !canEdit,
|
|
2921
2807
|
onChange: (event) => setForm({
|
|
2922
2808
|
...form,
|
|
2923
|
-
|
|
2809
|
+
bucket: event.target.value
|
|
2924
2810
|
}),
|
|
2925
|
-
"data-testid": "
|
|
2811
|
+
"data-testid": "archive-bucket"
|
|
2926
2812
|
})]
|
|
2927
2813
|
}),
|
|
2928
2814
|
/* @__PURE__ */ jsxs("div", {
|
|
2929
2815
|
className: "space-y-1.5",
|
|
2930
2816
|
children: [/* @__PURE__ */ jsx(Label, {
|
|
2931
|
-
htmlFor: "
|
|
2932
|
-
children: tr("fluidgrids.settings.
|
|
2817
|
+
htmlFor: "archive-prefix",
|
|
2818
|
+
children: tr("fluidgrids.settings.archive.prefix", "Key prefix (optional)")
|
|
2933
2819
|
}), /* @__PURE__ */ jsx(Input, {
|
|
2934
|
-
id: "
|
|
2935
|
-
value: form.
|
|
2820
|
+
id: "archive-prefix",
|
|
2821
|
+
value: form.prefix,
|
|
2822
|
+
disabled: !canEdit,
|
|
2936
2823
|
onChange: (event) => setForm({
|
|
2937
2824
|
...form,
|
|
2938
|
-
|
|
2825
|
+
prefix: event.target.value
|
|
2939
2826
|
}),
|
|
2940
|
-
"data-testid": "
|
|
2827
|
+
"data-testid": "archive-prefix"
|
|
2941
2828
|
})]
|
|
2942
2829
|
}),
|
|
2943
2830
|
/* @__PURE__ */ jsxs("div", {
|
|
2944
2831
|
className: "space-y-1.5",
|
|
2945
2832
|
children: [/* @__PURE__ */ jsx(Label, {
|
|
2946
|
-
htmlFor: "
|
|
2947
|
-
children: tr("fluidgrids.settings.
|
|
2833
|
+
htmlFor: "archive-region",
|
|
2834
|
+
children: tr("fluidgrids.settings.archive.region", "Region")
|
|
2948
2835
|
}), /* @__PURE__ */ jsx(Input, {
|
|
2949
|
-
id: "
|
|
2950
|
-
value: form.
|
|
2836
|
+
id: "archive-region",
|
|
2837
|
+
value: form.region,
|
|
2838
|
+
placeholder: "ap-south-1",
|
|
2839
|
+
disabled: !canEdit,
|
|
2951
2840
|
onChange: (event) => setForm({
|
|
2952
2841
|
...form,
|
|
2953
|
-
|
|
2842
|
+
region: event.target.value
|
|
2954
2843
|
}),
|
|
2955
|
-
"data-testid": "
|
|
2844
|
+
"data-testid": "archive-region"
|
|
2956
2845
|
})]
|
|
2957
2846
|
}),
|
|
2958
2847
|
/* @__PURE__ */ jsxs("div", {
|
|
2959
2848
|
className: "space-y-1.5",
|
|
2960
2849
|
children: [/* @__PURE__ */ jsx(Label, {
|
|
2961
|
-
htmlFor: "
|
|
2962
|
-
children: tr("fluidgrids.settings.
|
|
2850
|
+
htmlFor: "archive-role",
|
|
2851
|
+
children: tr("fluidgrids.settings.archive.roleArn", "Role ARN")
|
|
2963
2852
|
}), /* @__PURE__ */ jsx(Input, {
|
|
2964
|
-
id: "
|
|
2965
|
-
|
|
2966
|
-
|
|
2853
|
+
id: "archive-role",
|
|
2854
|
+
value: form.roleArn,
|
|
2855
|
+
placeholder: "arn:aws:iam::123456789012:role/fluidgrids-archive",
|
|
2856
|
+
disabled: !canEdit,
|
|
2967
2857
|
onChange: (event) => setForm({
|
|
2968
2858
|
...form,
|
|
2969
|
-
|
|
2859
|
+
roleArn: event.target.value
|
|
2970
2860
|
}),
|
|
2971
|
-
"data-testid": "
|
|
2861
|
+
"data-testid": "archive-role"
|
|
2972
2862
|
})]
|
|
2973
2863
|
}),
|
|
2974
|
-
/* @__PURE__ */ jsxs("
|
|
2975
|
-
className: "sm:col-span-2",
|
|
2976
|
-
children: [/* @__PURE__ */ jsx(
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2864
|
+
/* @__PURE__ */ jsxs("label", {
|
|
2865
|
+
className: "flex items-center justify-between gap-3 text-sm sm:col-span-2",
|
|
2866
|
+
children: [tr("fluidgrids.settings.archive.includePayloads", "Include payloads, inputs and outputs"), /* @__PURE__ */ jsx(Switch, {
|
|
2867
|
+
checked: form.includePayloads,
|
|
2868
|
+
disabled: !canEdit,
|
|
2869
|
+
onCheckedChange: (includePayloads) => setForm({
|
|
2870
|
+
...form,
|
|
2871
|
+
includePayloads
|
|
2872
|
+
})
|
|
2873
|
+
})]
|
|
2874
|
+
}),
|
|
2875
|
+
/* @__PURE__ */ jsxs("label", {
|
|
2876
|
+
className: "flex items-center justify-between gap-3 text-sm sm:col-span-2",
|
|
2877
|
+
children: [tr("fluidgrids.settings.archive.enabled", "Archive runs before they are purged"), /* @__PURE__ */ jsx(Switch, {
|
|
2878
|
+
checked: form.enabled,
|
|
2879
|
+
disabled: !canEdit,
|
|
2880
|
+
onCheckedChange: (enabled) => setForm({
|
|
2881
|
+
...form,
|
|
2882
|
+
enabled
|
|
2883
|
+
}),
|
|
2884
|
+
"data-testid": "archive-enabled"
|
|
2984
2885
|
})]
|
|
2985
2886
|
})
|
|
2986
2887
|
]
|
|
2888
|
+
}),
|
|
2889
|
+
/* @__PURE__ */ jsx(FieldError, {
|
|
2890
|
+
message: error,
|
|
2891
|
+
testId: "archive-error"
|
|
2892
|
+
}),
|
|
2893
|
+
canEdit && /* @__PURE__ */ jsxs("div", {
|
|
2894
|
+
className: "mt-4 flex gap-2",
|
|
2895
|
+
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
2896
|
+
onClick: () => void save(),
|
|
2897
|
+
disabled: !valid || busy,
|
|
2898
|
+
"data-testid": "archive-save",
|
|
2899
|
+
children: tr("fluidgrids.settings.common.save", "Save")
|
|
2900
|
+
}), archive.data && /* @__PURE__ */ jsx(Button$1, {
|
|
2901
|
+
variant: "outline",
|
|
2902
|
+
onClick: () => void clear(),
|
|
2903
|
+
disabled: busy,
|
|
2904
|
+
"data-testid": "archive-clear",
|
|
2905
|
+
children: tr("fluidgrids.settings.archive.remove", "Remove destination")
|
|
2906
|
+
})]
|
|
2987
2907
|
})
|
|
2988
2908
|
]
|
|
2989
2909
|
});
|
|
2990
2910
|
}
|
|
2991
|
-
|
|
2992
|
-
"KEYWORD",
|
|
2993
|
-
"TEXT",
|
|
2994
|
-
"INT",
|
|
2995
|
-
"DOUBLE",
|
|
2996
|
-
"BOOL",
|
|
2997
|
-
"DATETIME",
|
|
2998
|
-
"KEYWORD_LIST"
|
|
2999
|
-
];
|
|
3000
|
-
/** A letter, then letters, digits and underscores (at most 64). */
|
|
3001
|
-
const SEARCH_ATTRIBUTE_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
3002
|
-
function SearchAttributeDefinitionsCard() {
|
|
2911
|
+
function CodecServerCard() {
|
|
3003
2912
|
const tr = useTr();
|
|
3004
|
-
const
|
|
3005
|
-
const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.
|
|
3006
|
-
const [
|
|
3007
|
-
const [
|
|
3008
|
-
const [description, setDescription] = useState("");
|
|
2913
|
+
const settings = useResource("codec-server", true, fetchCodecServerSettings);
|
|
2914
|
+
const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_DELETE);
|
|
2915
|
+
const [url, setUrl] = useState("");
|
|
2916
|
+
const [includeCredentials, setIncludeCredentials] = useState(false);
|
|
3009
2917
|
const [busy, setBusy] = useState(false);
|
|
3010
2918
|
const [error, setError] = useState(null);
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
2919
|
+
useEffect(() => {
|
|
2920
|
+
if (!settings.data) return;
|
|
2921
|
+
setUrl(settings.data.codecServerUrl ?? "");
|
|
2922
|
+
setIncludeCredentials(settings.data.codecServerIncludeCredentials);
|
|
2923
|
+
}, [settings.data]);
|
|
2924
|
+
const check = url.trim() ? checkCodecServerUrl(url) : null;
|
|
2925
|
+
const save = async () => {
|
|
2926
|
+
if (check && !check.ok) return;
|
|
2927
|
+
setBusy(true);
|
|
2928
|
+
setError(null);
|
|
2929
|
+
try {
|
|
2930
|
+
settings.set(await saveCodecServerSettings({
|
|
2931
|
+
codecServerUrl: url.trim(),
|
|
2932
|
+
codecServerIncludeCredentials: includeCredentials
|
|
2933
|
+
}));
|
|
2934
|
+
toast.success(tr("fluidgrids.settings.codec.saved", "Codec server saved"));
|
|
3025
2935
|
} catch (err) {
|
|
3026
2936
|
setError(describeOperationError(err));
|
|
3027
2937
|
} finally {
|
|
3028
2938
|
setBusy(false);
|
|
3029
2939
|
}
|
|
3030
2940
|
};
|
|
3031
|
-
const remove = async (id) => {
|
|
3032
|
-
try {
|
|
3033
|
-
await deleteSearchAttribute(id);
|
|
3034
|
-
await definitions.refetch();
|
|
3035
|
-
} catch (err) {
|
|
3036
|
-
toast.error(describeOperationError(err));
|
|
3037
|
-
}
|
|
3038
|
-
};
|
|
3039
2941
|
return /* @__PURE__ */ jsxs(Card$1, {
|
|
3040
|
-
icon: "mdi:
|
|
3041
|
-
title: tr("fluidgrids.settings.
|
|
3042
|
-
description: tr("fluidgrids.settings.
|
|
3043
|
-
testId: "settings-
|
|
3044
|
-
children: [
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
className: "flex flex-wrap items-center gap-3 px-3 py-2 text-sm",
|
|
3050
|
-
"data-testid": `search-attribute-${definition.name}`,
|
|
2942
|
+
icon: "mdi:lock-open-outline",
|
|
2943
|
+
title: tr("fluidgrids.settings.codec.title", "Codec server"),
|
|
2944
|
+
description: tr("fluidgrids.settings.codec.description", "Payloads your self-hosted workers encode stay encoded in FluidGrids. The run page can send them to your codec server (Temporal's codec-server protocol) to show them decoded. No FluidGrids token is ever sent there."),
|
|
2945
|
+
testId: "settings-codec",
|
|
2946
|
+
children: [/* @__PURE__ */ jsx(FieldError, { message: settings.error }), /* @__PURE__ */ jsxs("div", {
|
|
2947
|
+
className: "space-y-3",
|
|
2948
|
+
children: [
|
|
2949
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2950
|
+
className: "space-y-1.5",
|
|
3051
2951
|
children: [
|
|
3052
|
-
/* @__PURE__ */ jsx(
|
|
3053
|
-
|
|
3054
|
-
children:
|
|
2952
|
+
/* @__PURE__ */ jsx(Label, {
|
|
2953
|
+
htmlFor: "codec-url",
|
|
2954
|
+
children: tr("fluidgrids.settings.codec.url", "Codec server URL")
|
|
3055
2955
|
}),
|
|
3056
|
-
/* @__PURE__ */ jsx(
|
|
3057
|
-
|
|
3058
|
-
|
|
2956
|
+
/* @__PURE__ */ jsx(Input, {
|
|
2957
|
+
id: "codec-url",
|
|
2958
|
+
value: url,
|
|
2959
|
+
disabled: !canEdit,
|
|
2960
|
+
placeholder: "https://codec.example.com",
|
|
2961
|
+
onChange: (event) => setUrl(event.target.value),
|
|
2962
|
+
"data-testid": "codec-url"
|
|
3059
2963
|
}),
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
2964
|
+
check && !check.ok && /* @__PURE__ */ jsx(FieldError, {
|
|
2965
|
+
message: tr("fluidgrids.settings.codec.badUrl", "Use an https URL (http only for localhost)."),
|
|
2966
|
+
testId: "codec-url-error"
|
|
3063
2967
|
}),
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
className: "ml-auto text-[var(--color-status-error-text)]",
|
|
3068
|
-
onClick: () => void remove(definition.id),
|
|
3069
|
-
children: tr("fluidgrids.settings.common.delete", "Delete")
|
|
2968
|
+
/* @__PURE__ */ jsx("p", {
|
|
2969
|
+
className: "text-xs text-text-secondary",
|
|
2970
|
+
children: tr("fluidgrids.settings.codec.empty", "Leave it empty to show encoded payloads as encoded.")
|
|
3070
2971
|
})
|
|
3071
2972
|
]
|
|
3072
|
-
},
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
children: tr("fluidgrids.settings.searchAttributes.type", "Type")
|
|
3095
|
-
}), /* @__PURE__ */ jsx(NativeSelect, {
|
|
3096
|
-
id: "search-attribute-type",
|
|
3097
|
-
value: type,
|
|
3098
|
-
onChange: (value) => setType(ATTRIBUTE_TYPES.find((item) => item === value) ?? "KEYWORD"),
|
|
3099
|
-
testId: "search-attribute-type",
|
|
3100
|
-
children: ATTRIBUTE_TYPES.map((item) => /* @__PURE__ */ jsx("option", {
|
|
3101
|
-
value: item,
|
|
3102
|
-
children: item
|
|
3103
|
-
}, item))
|
|
3104
|
-
})]
|
|
3105
|
-
}),
|
|
3106
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3107
|
-
className: "space-y-1.5",
|
|
3108
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
3109
|
-
htmlFor: "search-attribute-description",
|
|
3110
|
-
children: tr("fluidgrids.settings.searchAttributes.descriptionField", "Description (optional)")
|
|
3111
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
3112
|
-
id: "search-attribute-description",
|
|
3113
|
-
value: description,
|
|
3114
|
-
maxLength: 1e3,
|
|
3115
|
-
onChange: (event) => setDescription(event.target.value)
|
|
3116
|
-
})]
|
|
3117
|
-
}),
|
|
3118
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3119
|
-
className: "sm:col-span-3",
|
|
3120
|
-
children: [
|
|
3121
|
-
name && !validName && /* @__PURE__ */ jsx(FieldError, { message: tr("fluidgrids.settings.searchAttributes.badName", "Start with a letter; use letters, digits and underscores (at most 64).") }),
|
|
3122
|
-
/* @__PURE__ */ jsx(FieldError, {
|
|
3123
|
-
message: error,
|
|
3124
|
-
testId: "search-attribute-error"
|
|
3125
|
-
}),
|
|
3126
|
-
/* @__PURE__ */ jsx(Button$1, {
|
|
3127
|
-
onClick: () => void create(),
|
|
3128
|
-
disabled: !validName || busy,
|
|
3129
|
-
"data-testid": "search-attribute-create",
|
|
3130
|
-
children: tr("fluidgrids.settings.searchAttributes.create", "Add attribute")
|
|
3131
|
-
})
|
|
3132
|
-
]
|
|
3133
|
-
})
|
|
3134
|
-
]
|
|
3135
|
-
})
|
|
3136
|
-
]
|
|
2973
|
+
}),
|
|
2974
|
+
/* @__PURE__ */ jsxs("label", {
|
|
2975
|
+
className: "flex items-center justify-between gap-3 text-sm",
|
|
2976
|
+
children: [tr("fluidgrids.settings.codec.credentials", "Send my browser's own credentials (cookies) to it"), /* @__PURE__ */ jsx(Switch, {
|
|
2977
|
+
checked: includeCredentials,
|
|
2978
|
+
disabled: !canEdit,
|
|
2979
|
+
onCheckedChange: setIncludeCredentials,
|
|
2980
|
+
"data-testid": "codec-credentials"
|
|
2981
|
+
})]
|
|
2982
|
+
}),
|
|
2983
|
+
/* @__PURE__ */ jsx(FieldError, {
|
|
2984
|
+
message: error,
|
|
2985
|
+
testId: "codec-error"
|
|
2986
|
+
}),
|
|
2987
|
+
canEdit && /* @__PURE__ */ jsx(Button$1, {
|
|
2988
|
+
onClick: () => void save(),
|
|
2989
|
+
disabled: busy || check !== null && !check.ok,
|
|
2990
|
+
"data-testid": "codec-save",
|
|
2991
|
+
children: tr("fluidgrids.settings.common.save", "Save")
|
|
2992
|
+
})
|
|
2993
|
+
]
|
|
2994
|
+
})]
|
|
3137
2995
|
});
|
|
3138
2996
|
}
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
/**
|
|
3142
|
-
|
|
3143
|
-
|
|
2997
|
+
|
|
2998
|
+
// src/modules/settings/utils/error-trigger.ts
|
|
2999
|
+
/**
|
|
3000
|
+
* Error Workflow setting (BOFF-7514): the chosen workflow starts at its Error
|
|
3001
|
+
* Trigger node, so one without that node never starts. Mirrors the backend's
|
|
3002
|
+
* `findErrorTriggerNodeId` in wspace-workflow-svc.
|
|
3003
|
+
*/
|
|
3004
|
+
const ERROR_TRIGGER_NODE_TYPE = "internal:error-trigger";
|
|
3005
|
+
function asRecord(value) {
|
|
3006
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3007
|
+
}
|
|
3008
|
+
/** Reads `data.nodes`, falling back to the legacy top-level `nodes`. */
|
|
3009
|
+
function hasErrorTriggerNode(definitionJson) {
|
|
3010
|
+
const definition = asRecord(definitionJson);
|
|
3011
|
+
const dataNodes = asRecord(definition?.data)?.nodes;
|
|
3012
|
+
const nodes = Array.isArray(dataNodes) ? dataNodes : definition?.nodes;
|
|
3013
|
+
return Array.isArray(nodes) && nodes.some((node) => asRecord(node)?.type === "internal:error-trigger");
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// src/modules/settings/utils/optional-positive-int.ts
|
|
3017
|
+
/**
|
|
3018
|
+
* Number inputs whose empty value means "no limit" — Max parallel executions
|
|
3019
|
+
* (BOFF-7647) and Timeout in seconds (BOFF-7514). Empty is also what an unset
|
|
3020
|
+
* value means to the engine; anything else is clamped to a whole number of at
|
|
3021
|
+
* least 1.
|
|
3022
|
+
*/
|
|
3023
|
+
function parseOptionalPositiveInt(value) {
|
|
3024
|
+
if (value.trim() === "") return void 0;
|
|
3025
|
+
const parsed = Number(value);
|
|
3026
|
+
return Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : void 0;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
// src/modules/observability/types.ts
|
|
3030
|
+
const LOG_STREAM_EVENT_TYPES = [
|
|
3031
|
+
"LOG_ERROR",
|
|
3032
|
+
"LOG_WARN",
|
|
3033
|
+
"NODE_FAILED",
|
|
3034
|
+
"NODE_RETRIED",
|
|
3035
|
+
"RUN_CANCELLED",
|
|
3036
|
+
"RUN_COMPLETED",
|
|
3037
|
+
"RUN_FAILED",
|
|
3038
|
+
"RUN_STARTED"
|
|
3039
|
+
];
|
|
3040
|
+
|
|
3041
|
+
// src/modules/observability/components/LogStreamingTab.tsx
|
|
3042
|
+
const DEFAULT_EVENT_FILTERS = [
|
|
3043
|
+
"RUN_FAILED",
|
|
3044
|
+
"NODE_FAILED",
|
|
3045
|
+
"LOG_ERROR"
|
|
3046
|
+
];
|
|
3047
|
+
const PROVIDERS = [
|
|
3048
|
+
"WEBHOOK",
|
|
3049
|
+
"SYSLOG",
|
|
3050
|
+
"SENTRY"
|
|
3051
|
+
];
|
|
3052
|
+
function emptyForm() {
|
|
3053
|
+
return {
|
|
3054
|
+
name: "",
|
|
3055
|
+
provider: "WEBHOOK",
|
|
3056
|
+
endpoint: "",
|
|
3057
|
+
eventFilters: DEFAULT_EVENT_FILTERS,
|
|
3058
|
+
isEnabled: true,
|
|
3059
|
+
secret: "",
|
|
3060
|
+
configText: "{}"
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
function formFromDestination(destination) {
|
|
3064
|
+
return {
|
|
3065
|
+
name: destination.name,
|
|
3066
|
+
provider: destination.provider,
|
|
3067
|
+
endpoint: destination.endpoint,
|
|
3068
|
+
eventFilters: destination.eventFilters,
|
|
3069
|
+
isEnabled: destination.isEnabled,
|
|
3070
|
+
secret: "",
|
|
3071
|
+
configText: JSON.stringify(destination.config ?? {}, null, 2)
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
function eventLabel(event) {
|
|
3075
|
+
return event.toLowerCase().split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
3076
|
+
}
|
|
3077
|
+
function endpointPlaceholder(provider) {
|
|
3078
|
+
switch (provider) {
|
|
3079
|
+
case "SYSLOG": return "logs.example.com:514";
|
|
3080
|
+
case "SENTRY": return "https://public@sentry.example/1";
|
|
3081
|
+
case "WEBHOOK": return "https://hooks.example.com/workflows";
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
function parseConfig(configText) {
|
|
3085
|
+
try {
|
|
3086
|
+
const parsed = JSON.parse(configText || "{}");
|
|
3087
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3088
|
+
return parsed;
|
|
3089
|
+
} catch {
|
|
3090
|
+
return null;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
function formToInput(form) {
|
|
3094
|
+
const config = parseConfig(form.configText);
|
|
3095
|
+
if (!config || !form.name.trim() || !form.endpoint.trim()) return null;
|
|
3096
|
+
return {
|
|
3097
|
+
name: form.name.trim(),
|
|
3098
|
+
provider: form.provider,
|
|
3099
|
+
endpoint: form.endpoint.trim(),
|
|
3100
|
+
eventFilters: form.eventFilters,
|
|
3101
|
+
isEnabled: form.isEnabled,
|
|
3102
|
+
config,
|
|
3103
|
+
...form.secret ? { secret: form.secret } : {}
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
/**
|
|
3107
|
+
* Log stream destinations settings. Gated by the `fluidgrids.log-streams`
|
|
3108
|
+
* kill switch: when an admin turns it off for the workspace, the tab explains
|
|
3109
|
+
* that instead of loading destinations the backend would refuse to change.
|
|
3110
|
+
*/
|
|
3111
|
+
function LogStreamingTab() {
|
|
3112
|
+
if (!useFluidGridsFlag(FLUIDGRIDS_FLAGS.logStreams)) return /* @__PURE__ */ jsx(LogStreamingDisabledNotice, {});
|
|
3113
|
+
return /* @__PURE__ */ jsx(LogStreamingSettings, {});
|
|
3114
|
+
}
|
|
3115
|
+
function LogStreamingDisabledNotice() {
|
|
3144
3116
|
const tr = useTr();
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3117
|
+
return /* @__PURE__ */ jsx(Card, {
|
|
3118
|
+
className: "bg-background-secondary border-border-secondary",
|
|
3119
|
+
"data-testid": "log-streaming-disabled",
|
|
3120
|
+
children: /* @__PURE__ */ jsxs(CardContent, {
|
|
3121
|
+
className: "flex items-start gap-3 p-6",
|
|
3122
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3123
|
+
icon: "mdi:transit-connection-variant",
|
|
3124
|
+
width: 20,
|
|
3125
|
+
className: "mt-0.5 shrink-0 text-text-tertiary",
|
|
3126
|
+
"aria-hidden": "true"
|
|
3127
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
|
|
3128
|
+
className: "text-base font-semibold text-text-primary",
|
|
3129
|
+
children: tr("fluidgrids.settings.logStreaming.disabledTitle", "Log streaming is turned off")
|
|
3130
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
3131
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
3132
|
+
children: tr("fluidgrids.settings.logStreaming.disabledBody", "An administrator has turned off log streaming for this workspace. Run events are not forwarded to external destinations.")
|
|
3133
|
+
})] })]
|
|
3134
|
+
})
|
|
3154
3135
|
});
|
|
3155
|
-
|
|
3156
|
-
|
|
3136
|
+
}
|
|
3137
|
+
function LogStreamingSettings() {
|
|
3138
|
+
const tr = useTr();
|
|
3139
|
+
const { destinations, loading, error, refetch } = useLogStreamDestinations();
|
|
3140
|
+
const actions = useLogStreamDestinationActions();
|
|
3141
|
+
const [form, setForm] = useState(emptyForm);
|
|
3142
|
+
const [editingId, setEditingId] = useState(null);
|
|
3143
|
+
const [showForm, setShowForm] = useState(false);
|
|
3144
|
+
const editingDestination = useMemo(() => destinations.find((destination) => destination.id === editingId), [destinations, editingId]);
|
|
3157
3145
|
useEffect(() => {
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
if (!valid) return;
|
|
3171
|
-
setBusy(true);
|
|
3172
|
-
setError(null);
|
|
3173
|
-
try {
|
|
3174
|
-
archive.set(await saveArchiveSettings({
|
|
3175
|
-
enabled: form.enabled,
|
|
3176
|
-
bucket: form.bucket.trim(),
|
|
3177
|
-
prefix: form.prefix.trim().replace(/^\/+|\/+$/g, ""),
|
|
3178
|
-
region: form.region.trim(),
|
|
3179
|
-
roleArn: form.roleArn.trim(),
|
|
3180
|
-
includePayloads: form.includePayloads
|
|
3181
|
-
}));
|
|
3182
|
-
toast.success(tr("fluidgrids.settings.archive.saved", "Archive destination saved"));
|
|
3183
|
-
} catch (err) {
|
|
3184
|
-
setError(describeOperationError(err));
|
|
3185
|
-
} finally {
|
|
3186
|
-
setBusy(false);
|
|
3146
|
+
if (editingDestination) setForm(formFromDestination(editingDestination));
|
|
3147
|
+
}, [editingDestination]);
|
|
3148
|
+
const resetForm = useCallback(() => {
|
|
3149
|
+
setForm(emptyForm());
|
|
3150
|
+
setEditingId(null);
|
|
3151
|
+
setShowForm(false);
|
|
3152
|
+
}, []);
|
|
3153
|
+
const save = useCallback(async () => {
|
|
3154
|
+
const input = formToInput(form);
|
|
3155
|
+
if (!input) {
|
|
3156
|
+
toast.error(tr("fluidgrids.settings.logStreaming.invalidForm", "Enter a name, endpoint, and valid JSON configuration."));
|
|
3157
|
+
return;
|
|
3187
3158
|
}
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
try {
|
|
3193
|
-
await clearArchiveSettings();
|
|
3194
|
-
archive.set(null);
|
|
3195
|
-
setForm({
|
|
3196
|
-
enabled: false,
|
|
3197
|
-
bucket: "",
|
|
3198
|
-
prefix: "",
|
|
3199
|
-
region: "",
|
|
3200
|
-
roleArn: "",
|
|
3201
|
-
includePayloads: true
|
|
3202
|
-
});
|
|
3203
|
-
} catch (err) {
|
|
3204
|
-
setError(describeOperationError(err));
|
|
3205
|
-
} finally {
|
|
3206
|
-
setBusy(false);
|
|
3159
|
+
const result = editingId ? await actions.update(editingId, input) : await actions.create(input);
|
|
3160
|
+
if (!result?.success) {
|
|
3161
|
+
toast.error(result?.message || tr("fluidgrids.settings.logStreaming.saveFailed", "Could not save log streaming destination."));
|
|
3162
|
+
return;
|
|
3207
3163
|
}
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3278
|
-
className: "space-y-1.5",
|
|
3279
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
3280
|
-
htmlFor: "archive-role",
|
|
3281
|
-
children: tr("fluidgrids.settings.archive.roleArn", "Role ARN")
|
|
3282
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
3283
|
-
id: "archive-role",
|
|
3284
|
-
value: form.roleArn,
|
|
3285
|
-
placeholder: "arn:aws:iam::123456789012:role/fluidgrids-archive",
|
|
3286
|
-
disabled: !canEdit,
|
|
3287
|
-
onChange: (event) => setForm({
|
|
3288
|
-
...form,
|
|
3289
|
-
roleArn: event.target.value
|
|
3290
|
-
}),
|
|
3291
|
-
"data-testid": "archive-role"
|
|
3292
|
-
})]
|
|
3293
|
-
}),
|
|
3294
|
-
/* @__PURE__ */ jsxs("label", {
|
|
3295
|
-
className: "flex items-center justify-between gap-3 text-sm sm:col-span-2",
|
|
3296
|
-
children: [tr("fluidgrids.settings.archive.includePayloads", "Include payloads, inputs and outputs"), /* @__PURE__ */ jsx(Switch, {
|
|
3297
|
-
checked: form.includePayloads,
|
|
3298
|
-
disabled: !canEdit,
|
|
3299
|
-
onCheckedChange: (includePayloads) => setForm({
|
|
3300
|
-
...form,
|
|
3301
|
-
includePayloads
|
|
3302
|
-
})
|
|
3303
|
-
})]
|
|
3304
|
-
}),
|
|
3305
|
-
/* @__PURE__ */ jsxs("label", {
|
|
3306
|
-
className: "flex items-center justify-between gap-3 text-sm sm:col-span-2",
|
|
3307
|
-
children: [tr("fluidgrids.settings.archive.enabled", "Archive runs before they are purged"), /* @__PURE__ */ jsx(Switch, {
|
|
3308
|
-
checked: form.enabled,
|
|
3309
|
-
disabled: !canEdit,
|
|
3310
|
-
onCheckedChange: (enabled) => setForm({
|
|
3311
|
-
...form,
|
|
3312
|
-
enabled
|
|
3313
|
-
}),
|
|
3314
|
-
"data-testid": "archive-enabled"
|
|
3315
|
-
})]
|
|
3316
|
-
})
|
|
3317
|
-
]
|
|
3318
|
-
}),
|
|
3319
|
-
/* @__PURE__ */ jsx(FieldError, {
|
|
3320
|
-
message: error,
|
|
3321
|
-
testId: "archive-error"
|
|
3164
|
+
toast.success(tr("fluidgrids.settings.logStreaming.saved", "Log streaming destination saved."));
|
|
3165
|
+
resetForm();
|
|
3166
|
+
await refetch();
|
|
3167
|
+
}, [
|
|
3168
|
+
actions,
|
|
3169
|
+
editingId,
|
|
3170
|
+
form,
|
|
3171
|
+
refetch,
|
|
3172
|
+
resetForm,
|
|
3173
|
+
tr
|
|
3174
|
+
]);
|
|
3175
|
+
const remove = useCallback(async (destination) => {
|
|
3176
|
+
if (!window.confirm(`Delete “${destination.name}”?`)) return;
|
|
3177
|
+
const result = await actions.remove(destination.id);
|
|
3178
|
+
if (!result?.success) {
|
|
3179
|
+
toast.error(result?.message || tr("fluidgrids.settings.logStreaming.deleteFailed", "Could not delete destination."));
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
toast.success(tr("fluidgrids.settings.logStreaming.deleted", "Log streaming destination deleted."));
|
|
3183
|
+
if (editingId === destination.id) resetForm();
|
|
3184
|
+
await refetch();
|
|
3185
|
+
}, [
|
|
3186
|
+
actions,
|
|
3187
|
+
editingId,
|
|
3188
|
+
refetch,
|
|
3189
|
+
resetForm,
|
|
3190
|
+
tr
|
|
3191
|
+
]);
|
|
3192
|
+
const sendTest = useCallback(async (destination) => {
|
|
3193
|
+
const result = await actions.test(destination.id);
|
|
3194
|
+
if (!result) return;
|
|
3195
|
+
if (result.success) toast.success(tr("fluidgrids.settings.logStreaming.testSucceeded", "Test event delivered."));
|
|
3196
|
+
else toast.error(result.message || tr("fluidgrids.settings.logStreaming.testFailed", "Test event could not be delivered."));
|
|
3197
|
+
await refetch();
|
|
3198
|
+
}, [
|
|
3199
|
+
actions,
|
|
3200
|
+
refetch,
|
|
3201
|
+
tr
|
|
3202
|
+
]);
|
|
3203
|
+
const toggleEvent = (event) => {
|
|
3204
|
+
setForm((current) => ({
|
|
3205
|
+
...current,
|
|
3206
|
+
eventFilters: current.eventFilters.includes(event) ? current.eventFilters.filter((item) => item !== event) : [...current.eventFilters, event]
|
|
3207
|
+
}));
|
|
3208
|
+
};
|
|
3209
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3210
|
+
className: "space-y-6",
|
|
3211
|
+
children: [
|
|
3212
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3213
|
+
className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",
|
|
3214
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
|
|
3215
|
+
className: "text-lg font-semibold text-text-primary",
|
|
3216
|
+
children: tr("fluidgrids.settings.logStreaming.title", "Log streaming")
|
|
3217
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
3218
|
+
className: "mt-1 max-w-2xl text-sm text-text-secondary",
|
|
3219
|
+
children: tr("fluidgrids.settings.logStreaming.description", "Send workflow run and node events to workspace-managed observability destinations. Secrets are write-only and never returned.")
|
|
3220
|
+
})] }), /* @__PURE__ */ jsxs(Button$1, {
|
|
3221
|
+
onClick: () => {
|
|
3222
|
+
setForm(emptyForm());
|
|
3223
|
+
setEditingId(null);
|
|
3224
|
+
setShowForm(true);
|
|
3225
|
+
},
|
|
3226
|
+
disabled: actions.loading,
|
|
3227
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3228
|
+
icon: "mdi:plus",
|
|
3229
|
+
className: "mr-1",
|
|
3230
|
+
width: 16
|
|
3231
|
+
}), tr("fluidgrids.settings.logStreaming.add", "Add destination")]
|
|
3232
|
+
})]
|
|
3322
3233
|
}),
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
disabled: !valid || busy,
|
|
3328
|
-
"data-testid": "archive-save",
|
|
3329
|
-
children: tr("fluidgrids.settings.common.save", "Save")
|
|
3330
|
-
}), archive.data && /* @__PURE__ */ jsx(Button$1, {
|
|
3234
|
+
error && /* @__PURE__ */ jsxs("div", {
|
|
3235
|
+
role: "alert",
|
|
3236
|
+
className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-sm text-status-error-text",
|
|
3237
|
+
children: [error.message, /* @__PURE__ */ jsx(Button$1, {
|
|
3331
3238
|
variant: "outline",
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
children: tr("fluidgrids.settings.
|
|
3239
|
+
size: "sm",
|
|
3240
|
+
className: "ml-3",
|
|
3241
|
+
onClick: () => void refetch(),
|
|
3242
|
+
children: tr("fluidgrids.settings.logStreaming.retry", "Retry")
|
|
3336
3243
|
})]
|
|
3337
|
-
})
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
if (check && !check.ok) return;
|
|
3357
|
-
setBusy(true);
|
|
3358
|
-
setError(null);
|
|
3359
|
-
try {
|
|
3360
|
-
settings.set(await saveCodecServerSettings({
|
|
3361
|
-
codecServerUrl: url.trim(),
|
|
3362
|
-
codecServerIncludeCredentials: includeCredentials
|
|
3363
|
-
}));
|
|
3364
|
-
toast.success(tr("fluidgrids.settings.codec.saved", "Codec server saved"));
|
|
3365
|
-
} catch (err) {
|
|
3366
|
-
setError(describeOperationError(err));
|
|
3367
|
-
} finally {
|
|
3368
|
-
setBusy(false);
|
|
3369
|
-
}
|
|
3370
|
-
};
|
|
3371
|
-
return /* @__PURE__ */ jsxs(Card$1, {
|
|
3372
|
-
icon: "mdi:lock-open-outline",
|
|
3373
|
-
title: tr("fluidgrids.settings.codec.title", "Codec server"),
|
|
3374
|
-
description: tr("fluidgrids.settings.codec.description", "Payloads your self-hosted workers encode stay encoded in FluidGrids. The run page can send them to your codec server (Temporal's codec-server protocol) to show them decoded. No FluidGrids token is ever sent there."),
|
|
3375
|
-
testId: "settings-codec",
|
|
3376
|
-
children: [/* @__PURE__ */ jsx(FieldError, { message: settings.error }), /* @__PURE__ */ jsxs("div", {
|
|
3377
|
-
className: "space-y-3",
|
|
3378
|
-
children: [
|
|
3379
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3380
|
-
className: "space-y-1.5",
|
|
3244
|
+
}),
|
|
3245
|
+
showForm && /* @__PURE__ */ jsxs(Card, {
|
|
3246
|
+
className: "border-border-secondary bg-background-secondary",
|
|
3247
|
+
children: [/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", {
|
|
3248
|
+
className: "flex items-center justify-between gap-3",
|
|
3249
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
3250
|
+
className: "font-medium text-text-primary",
|
|
3251
|
+
children: editingId ? tr("fluidgrids.settings.logStreaming.edit", "Edit destination") : tr("fluidgrids.settings.logStreaming.new", "New destination")
|
|
3252
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
3253
|
+
variant: "ghost",
|
|
3254
|
+
size: "sm",
|
|
3255
|
+
onClick: resetForm,
|
|
3256
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
3257
|
+
icon: "mdi:close",
|
|
3258
|
+
width: 18
|
|
3259
|
+
})
|
|
3260
|
+
})]
|
|
3261
|
+
}) }), /* @__PURE__ */ jsxs(CardContent, {
|
|
3262
|
+
className: "space-y-5",
|
|
3381
3263
|
children: [
|
|
3382
|
-
/* @__PURE__ */
|
|
3383
|
-
|
|
3384
|
-
children:
|
|
3264
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3265
|
+
className: "grid gap-4 md:grid-cols-2",
|
|
3266
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3267
|
+
className: "space-y-2",
|
|
3268
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
3269
|
+
htmlFor: "log-stream-name",
|
|
3270
|
+
children: tr("fluidgrids.settings.logStreaming.name", "Name")
|
|
3271
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
3272
|
+
id: "log-stream-name",
|
|
3273
|
+
value: form.name,
|
|
3274
|
+
onChange: (event) => setForm((current) => ({
|
|
3275
|
+
...current,
|
|
3276
|
+
name: event.target.value
|
|
3277
|
+
})),
|
|
3278
|
+
placeholder: "Production error stream"
|
|
3279
|
+
})]
|
|
3280
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
3281
|
+
className: "space-y-2",
|
|
3282
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
3283
|
+
htmlFor: "log-stream-provider",
|
|
3284
|
+
children: tr("fluidgrids.settings.logStreaming.provider", "Provider")
|
|
3285
|
+
}), /* @__PURE__ */ jsx("select", {
|
|
3286
|
+
id: "log-stream-provider",
|
|
3287
|
+
value: form.provider,
|
|
3288
|
+
onChange: (event) => setForm((current) => ({
|
|
3289
|
+
...current,
|
|
3290
|
+
provider: event.target.value,
|
|
3291
|
+
endpoint: ""
|
|
3292
|
+
})),
|
|
3293
|
+
className: "w-full rounded-md border border-border-secondary bg-background-primary px-3 py-2 text-sm text-text-primary",
|
|
3294
|
+
children: PROVIDERS.map((provider) => /* @__PURE__ */ jsx("option", {
|
|
3295
|
+
value: provider,
|
|
3296
|
+
children: provider
|
|
3297
|
+
}, provider))
|
|
3298
|
+
})]
|
|
3299
|
+
})]
|
|
3385
3300
|
}),
|
|
3386
|
-
/* @__PURE__ */
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3301
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3302
|
+
className: "space-y-2",
|
|
3303
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
3304
|
+
htmlFor: "log-stream-endpoint",
|
|
3305
|
+
children: tr("fluidgrids.settings.logStreaming.endpoint", "Endpoint")
|
|
3306
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
3307
|
+
id: "log-stream-endpoint",
|
|
3308
|
+
value: form.endpoint,
|
|
3309
|
+
onChange: (event) => setForm((current) => ({
|
|
3310
|
+
...current,
|
|
3311
|
+
endpoint: event.target.value
|
|
3312
|
+
})),
|
|
3313
|
+
placeholder: endpointPlaceholder(form.provider),
|
|
3314
|
+
type: form.provider === "WEBHOOK" || form.provider === "SENTRY" ? "url" : "text"
|
|
3315
|
+
})]
|
|
3393
3316
|
}),
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3317
|
+
form.provider === "WEBHOOK" && /* @__PURE__ */ jsxs("div", {
|
|
3318
|
+
className: "space-y-2",
|
|
3319
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
3320
|
+
htmlFor: "log-stream-secret",
|
|
3321
|
+
children: tr("fluidgrids.settings.logStreaming.secret", "Signing secret")
|
|
3322
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
3323
|
+
id: "log-stream-secret",
|
|
3324
|
+
value: form.secret,
|
|
3325
|
+
onChange: (event) => setForm((current) => ({
|
|
3326
|
+
...current,
|
|
3327
|
+
secret: event.target.value
|
|
3328
|
+
})),
|
|
3329
|
+
placeholder: editingId ? "Leave blank to keep the current secret" : "Enter a signing secret",
|
|
3330
|
+
type: "password",
|
|
3331
|
+
autoComplete: "new-password"
|
|
3332
|
+
})]
|
|
3333
|
+
}),
|
|
3334
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3335
|
+
className: "space-y-2",
|
|
3336
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
3337
|
+
htmlFor: "log-stream-config",
|
|
3338
|
+
children: tr("fluidgrids.settings.logStreaming.config", "Provider configuration (JSON)")
|
|
3339
|
+
}), /* @__PURE__ */ jsx("textarea", {
|
|
3340
|
+
id: "log-stream-config",
|
|
3341
|
+
value: form.configText,
|
|
3342
|
+
onChange: (event) => setForm((current) => ({
|
|
3343
|
+
...current,
|
|
3344
|
+
configText: event.target.value
|
|
3345
|
+
})),
|
|
3346
|
+
className: "min-h-24 w-full rounded-md border border-border-secondary bg-background-primary px-3 py-2 font-mono text-xs text-text-primary",
|
|
3347
|
+
spellCheck: false
|
|
3348
|
+
})]
|
|
3349
|
+
}),
|
|
3350
|
+
/* @__PURE__ */ jsxs("fieldset", {
|
|
3351
|
+
className: "space-y-2",
|
|
3352
|
+
children: [/* @__PURE__ */ jsx("legend", {
|
|
3353
|
+
className: "text-sm font-medium text-text-primary",
|
|
3354
|
+
children: tr("fluidgrids.settings.logStreaming.events", "Events to stream")
|
|
3355
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3356
|
+
className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-4",
|
|
3357
|
+
children: LOG_STREAM_EVENT_TYPES.map((event) => /* @__PURE__ */ jsxs("label", {
|
|
3358
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
3359
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
3360
|
+
type: "checkbox",
|
|
3361
|
+
checked: form.eventFilters.includes(event),
|
|
3362
|
+
onChange: () => toggleEvent(event),
|
|
3363
|
+
className: "size-4 rounded border-border-secondary accent-action-primary-bg"
|
|
3364
|
+
}), eventLabel(event)]
|
|
3365
|
+
}, event))
|
|
3366
|
+
})]
|
|
3367
|
+
}),
|
|
3368
|
+
/* @__PURE__ */ jsxs("label", {
|
|
3369
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
3370
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
3371
|
+
type: "checkbox",
|
|
3372
|
+
checked: form.isEnabled,
|
|
3373
|
+
onChange: (event) => setForm((current) => ({
|
|
3374
|
+
...current,
|
|
3375
|
+
isEnabled: event.target.checked
|
|
3376
|
+
})),
|
|
3377
|
+
className: "size-4 rounded border-border-secondary accent-action-primary-bg"
|
|
3378
|
+
}), tr("fluidgrids.settings.logStreaming.enabled", "Destination enabled")]
|
|
3397
3379
|
}),
|
|
3398
|
-
/* @__PURE__ */
|
|
3399
|
-
className: "
|
|
3400
|
-
children:
|
|
3380
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3381
|
+
className: "flex justify-end gap-2",
|
|
3382
|
+
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
3383
|
+
variant: "outline",
|
|
3384
|
+
onClick: resetForm,
|
|
3385
|
+
children: tr("fluidgrids.settings.logStreaming.cancel", "Cancel")
|
|
3386
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
3387
|
+
"data-agent": "confirm",
|
|
3388
|
+
onClick: () => void save(),
|
|
3389
|
+
disabled: actions.loading,
|
|
3390
|
+
children: actions.loading ? tr("fluidgrids.settings.logStreaming.saving", "Saving…") : tr("fluidgrids.settings.logStreaming.save", "Save destination")
|
|
3391
|
+
})]
|
|
3401
3392
|
})
|
|
3402
3393
|
]
|
|
3403
|
-
})
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
"
|
|
3411
|
-
})
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
}
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
}
|
|
3513
|
-
}
|
|
3514
|
-
function parseConfig(configText) {
|
|
3515
|
-
try {
|
|
3516
|
-
const parsed = JSON.parse(configText || "{}");
|
|
3517
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3518
|
-
return parsed;
|
|
3519
|
-
} catch {
|
|
3520
|
-
return null;
|
|
3521
|
-
}
|
|
3522
|
-
}
|
|
3523
|
-
function formToInput(form) {
|
|
3524
|
-
const config = parseConfig(form.configText);
|
|
3525
|
-
if (!config || !form.name.trim() || !form.endpoint.trim()) return null;
|
|
3526
|
-
return {
|
|
3527
|
-
name: form.name.trim(),
|
|
3528
|
-
provider: form.provider,
|
|
3529
|
-
endpoint: form.endpoint.trim(),
|
|
3530
|
-
eventFilters: form.eventFilters,
|
|
3531
|
-
isEnabled: form.isEnabled,
|
|
3532
|
-
config,
|
|
3533
|
-
...form.secret ? { secret: form.secret } : {}
|
|
3534
|
-
};
|
|
3394
|
+
})]
|
|
3395
|
+
}),
|
|
3396
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3397
|
+
className: "space-y-3",
|
|
3398
|
+
children: [
|
|
3399
|
+
loading && destinations.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
3400
|
+
className: "rounded-lg border border-border-secondary bg-background-secondary p-6 text-sm text-text-secondary",
|
|
3401
|
+
children: tr("fluidgrids.settings.logStreaming.loading", "Loading destinations…")
|
|
3402
|
+
}),
|
|
3403
|
+
!loading && destinations.length === 0 && /* @__PURE__ */ jsxs("div", {
|
|
3404
|
+
className: "rounded-lg border border-dashed border-border-secondary bg-background-secondary p-8 text-center",
|
|
3405
|
+
children: [
|
|
3406
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
3407
|
+
icon: "mdi:transit-connection-variant",
|
|
3408
|
+
className: "mx-auto mb-3 text-text-secondary",
|
|
3409
|
+
width: 32
|
|
3410
|
+
}),
|
|
3411
|
+
/* @__PURE__ */ jsx("p", {
|
|
3412
|
+
className: "font-medium text-text-primary",
|
|
3413
|
+
children: tr("fluidgrids.settings.logStreaming.emptyTitle", "No log destinations yet")
|
|
3414
|
+
}),
|
|
3415
|
+
/* @__PURE__ */ jsx("p", {
|
|
3416
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
3417
|
+
children: tr("fluidgrids.settings.logStreaming.emptyDescription", "Add a webhook, syslog target, or Sentry DSN to start streaming events.")
|
|
3418
|
+
})
|
|
3419
|
+
]
|
|
3420
|
+
}),
|
|
3421
|
+
destinations.map((destination) => /* @__PURE__ */ jsx(Card, {
|
|
3422
|
+
className: "border-border-secondary bg-background-secondary",
|
|
3423
|
+
children: /* @__PURE__ */ jsxs(CardContent, {
|
|
3424
|
+
className: "flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between",
|
|
3425
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3426
|
+
className: "min-w-0",
|
|
3427
|
+
children: [
|
|
3428
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3429
|
+
className: "flex flex-wrap items-center gap-2",
|
|
3430
|
+
children: [
|
|
3431
|
+
/* @__PURE__ */ jsx("h3", {
|
|
3432
|
+
className: "font-medium text-text-primary",
|
|
3433
|
+
children: destination.name
|
|
3434
|
+
}),
|
|
3435
|
+
/* @__PURE__ */ jsx("span", {
|
|
3436
|
+
className: "rounded-full bg-background-tertiary px-2 py-0.5 text-xs text-text-secondary",
|
|
3437
|
+
children: destination.provider
|
|
3438
|
+
}),
|
|
3439
|
+
/* @__PURE__ */ jsx("span", {
|
|
3440
|
+
className: destination.isEnabled ? "text-xs text-status-success-text" : "text-xs text-text-secondary",
|
|
3441
|
+
children: destination.isEnabled ? tr("fluidgrids.settings.logStreaming.enabledBadge", "Enabled") : tr("fluidgrids.settings.logStreaming.disabledBadge", "Disabled")
|
|
3442
|
+
})
|
|
3443
|
+
]
|
|
3444
|
+
}),
|
|
3445
|
+
/* @__PURE__ */ jsx("p", {
|
|
3446
|
+
className: "mt-1 truncate text-sm text-text-secondary",
|
|
3447
|
+
children: destination.endpoint
|
|
3448
|
+
}),
|
|
3449
|
+
/* @__PURE__ */ jsxs("p", {
|
|
3450
|
+
className: "mt-1 text-xs text-text-secondary",
|
|
3451
|
+
children: [destination.eventFilters.length > 0 ? destination.eventFilters.map(eventLabel).join(", ") : tr("fluidgrids.settings.logStreaming.allEvents", "All events"), destination.hasSecret && " · Secret configured"]
|
|
3452
|
+
}),
|
|
3453
|
+
destination.lastDispatchError && /* @__PURE__ */ jsx("p", {
|
|
3454
|
+
className: "mt-2 text-xs text-status-error-text",
|
|
3455
|
+
children: destination.lastDispatchError
|
|
3456
|
+
})
|
|
3457
|
+
]
|
|
3458
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
3459
|
+
className: "flex shrink-0 flex-wrap gap-2",
|
|
3460
|
+
children: [
|
|
3461
|
+
/* @__PURE__ */ jsxs(Button$1, {
|
|
3462
|
+
variant: "outline",
|
|
3463
|
+
size: "sm",
|
|
3464
|
+
onClick: () => void sendTest(destination),
|
|
3465
|
+
disabled: actions.loading,
|
|
3466
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3467
|
+
icon: "mdi:send-check-outline",
|
|
3468
|
+
className: "mr-1",
|
|
3469
|
+
width: 15
|
|
3470
|
+
}), tr("fluidgrids.settings.logStreaming.test", "Send test")]
|
|
3471
|
+
}),
|
|
3472
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
3473
|
+
variant: "outline",
|
|
3474
|
+
size: "sm",
|
|
3475
|
+
onClick: () => {
|
|
3476
|
+
setEditingId(destination.id);
|
|
3477
|
+
setShowForm(true);
|
|
3478
|
+
},
|
|
3479
|
+
disabled: actions.loading,
|
|
3480
|
+
children: tr("fluidgrids.settings.logStreaming.editButton", "Edit")
|
|
3481
|
+
}),
|
|
3482
|
+
/* @__PURE__ */ jsxs(Button$1, {
|
|
3483
|
+
variant: "ghost",
|
|
3484
|
+
size: "sm",
|
|
3485
|
+
onClick: () => void remove(destination),
|
|
3486
|
+
disabled: actions.loading,
|
|
3487
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3488
|
+
icon: "mdi:trash-can-outline",
|
|
3489
|
+
className: "text-status-error-text",
|
|
3490
|
+
width: 17
|
|
3491
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
3492
|
+
className: "sr-only",
|
|
3493
|
+
children: tr("fluidgrids.settings.logStreaming.delete", "Delete")
|
|
3494
|
+
})]
|
|
3495
|
+
})
|
|
3496
|
+
]
|
|
3497
|
+
})]
|
|
3498
|
+
})
|
|
3499
|
+
}, destination.id))
|
|
3500
|
+
]
|
|
3501
|
+
})
|
|
3502
|
+
]
|
|
3503
|
+
});
|
|
3535
3504
|
}
|
|
3505
|
+
|
|
3506
|
+
// src/modules/agent-definitions/components/AgentsTab.tsx
|
|
3536
3507
|
/**
|
|
3537
|
-
*
|
|
3538
|
-
*
|
|
3539
|
-
*
|
|
3508
|
+
* Settings → Agents (BOFF-9962): FluidGrids agents with their own identity.
|
|
3509
|
+
*
|
|
3510
|
+
* Builders create an agent, choose the tools it may call (node type and operations) and the
|
|
3511
|
+
* connections those tools may use, and can require approval for every tool call. Each person
|
|
3512
|
+
* lets the agent act for them with a time-boxed grant (30 days by default, 365 at most) and can
|
|
3513
|
+
* withdraw it at any time; admins can revoke anyone's grant. An AI Agent node that names the agent
|
|
3514
|
+
* runs its tools as "agent for user", and every call is recorded under both identities. Giving an
|
|
3515
|
+
* agent more access withdraws other people's grants until they grant again.
|
|
3540
3516
|
*/
|
|
3541
|
-
function
|
|
3542
|
-
if (
|
|
3543
|
-
return
|
|
3544
|
-
}
|
|
3545
|
-
function LogStreamingDisabledNotice() {
|
|
3546
|
-
const tr = useTr();
|
|
3547
|
-
return /* @__PURE__ */ jsx(Card, {
|
|
3548
|
-
className: "bg-background-secondary border-border-secondary",
|
|
3549
|
-
"data-testid": "log-streaming-disabled",
|
|
3550
|
-
children: /* @__PURE__ */ jsxs(CardContent, {
|
|
3551
|
-
className: "flex items-start gap-3 p-6",
|
|
3552
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3553
|
-
icon: "mdi:transit-connection-variant",
|
|
3554
|
-
width: 20,
|
|
3555
|
-
className: "mt-0.5 shrink-0 text-text-tertiary",
|
|
3556
|
-
"aria-hidden": "true"
|
|
3557
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
|
|
3558
|
-
className: "text-base font-semibold text-text-primary",
|
|
3559
|
-
children: tr("fluidgrids.settings.logStreaming.disabledTitle", "Log streaming is turned off")
|
|
3560
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
3561
|
-
className: "mt-1 text-sm text-text-secondary",
|
|
3562
|
-
children: tr("fluidgrids.settings.logStreaming.disabledBody", "An administrator has turned off log streaming for this workspace. Run events are not forwarded to external destinations.")
|
|
3563
|
-
})] })]
|
|
3564
|
-
})
|
|
3565
|
-
});
|
|
3517
|
+
function errorMessage$1(err) {
|
|
3518
|
+
if (err instanceof AgentFormError) return err.message;
|
|
3519
|
+
return describeOperationError(err);
|
|
3566
3520
|
}
|
|
3567
|
-
function
|
|
3521
|
+
function AgentEditor({ initial, onCancel, onSave }) {
|
|
3568
3522
|
const tr = useTr();
|
|
3569
|
-
const
|
|
3570
|
-
const
|
|
3571
|
-
const
|
|
3572
|
-
const
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
}
|
|
3583
|
-
const
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
toast.error(result?.message || tr("fluidgrids.settings.logStreaming.saveFailed", "Could not save log streaming destination."));
|
|
3592
|
-
return;
|
|
3593
|
-
}
|
|
3594
|
-
toast.success(tr("fluidgrids.settings.logStreaming.saved", "Log streaming destination saved."));
|
|
3595
|
-
resetForm();
|
|
3596
|
-
await refetch();
|
|
3597
|
-
}, [
|
|
3598
|
-
actions,
|
|
3599
|
-
editingId,
|
|
3600
|
-
form,
|
|
3601
|
-
refetch,
|
|
3602
|
-
resetForm,
|
|
3603
|
-
tr
|
|
3604
|
-
]);
|
|
3605
|
-
const remove = useCallback(async (destination) => {
|
|
3606
|
-
if (!window.confirm(`Delete “${destination.name}”?`)) return;
|
|
3607
|
-
const result = await actions.remove(destination.id);
|
|
3608
|
-
if (!result?.success) {
|
|
3609
|
-
toast.error(result?.message || tr("fluidgrids.settings.logStreaming.deleteFailed", "Could not delete destination."));
|
|
3610
|
-
return;
|
|
3523
|
+
const [form, setForm] = useState(initial);
|
|
3524
|
+
const [busy, setBusy] = useState(false);
|
|
3525
|
+
const { connections, loading: connectionsLoading } = useConnections();
|
|
3526
|
+
const setTool = (index, patch) => setForm((current) => ({
|
|
3527
|
+
...current,
|
|
3528
|
+
tools: current.tools.map((row, i) => i === index ? {
|
|
3529
|
+
...row,
|
|
3530
|
+
...patch
|
|
3531
|
+
} : row)
|
|
3532
|
+
}));
|
|
3533
|
+
const toggleConnection = (id, on) => setForm((current) => ({
|
|
3534
|
+
...current,
|
|
3535
|
+
connectionScope: on ? [.../* @__PURE__ */ new Set([...current.connectionScope, id])] : current.connectionScope.filter((value) => value !== id)
|
|
3536
|
+
}));
|
|
3537
|
+
const submit = async () => {
|
|
3538
|
+
setBusy(true);
|
|
3539
|
+
try {
|
|
3540
|
+
await onSave(form);
|
|
3541
|
+
} catch (err) {
|
|
3542
|
+
toast.error(errorMessage$1(err));
|
|
3543
|
+
} finally {
|
|
3544
|
+
setBusy(false);
|
|
3611
3545
|
}
|
|
3612
|
-
toast.success(tr("fluidgrids.settings.logStreaming.deleted", "Log streaming destination deleted."));
|
|
3613
|
-
if (editingId === destination.id) resetForm();
|
|
3614
|
-
await refetch();
|
|
3615
|
-
}, [
|
|
3616
|
-
actions,
|
|
3617
|
-
editingId,
|
|
3618
|
-
refetch,
|
|
3619
|
-
resetForm,
|
|
3620
|
-
tr
|
|
3621
|
-
]);
|
|
3622
|
-
const sendTest = useCallback(async (destination) => {
|
|
3623
|
-
const result = await actions.test(destination.id);
|
|
3624
|
-
if (!result) return;
|
|
3625
|
-
if (result.success) toast.success(tr("fluidgrids.settings.logStreaming.testSucceeded", "Test event delivered."));
|
|
3626
|
-
else toast.error(result.message || tr("fluidgrids.settings.logStreaming.testFailed", "Test event could not be delivered."));
|
|
3627
|
-
await refetch();
|
|
3628
|
-
}, [
|
|
3629
|
-
actions,
|
|
3630
|
-
refetch,
|
|
3631
|
-
tr
|
|
3632
|
-
]);
|
|
3633
|
-
const toggleEvent = (event) => {
|
|
3634
|
-
setForm((current) => ({
|
|
3635
|
-
...current,
|
|
3636
|
-
eventFilters: current.eventFilters.includes(event) ? current.eventFilters.filter((item) => item !== event) : [...current.eventFilters, event]
|
|
3637
|
-
}));
|
|
3638
3546
|
};
|
|
3639
3547
|
return /* @__PURE__ */ jsxs("div", {
|
|
3640
|
-
className: "space-y-
|
|
3548
|
+
className: "rounded-lg border border-border-secondary bg-background-primary p-4 space-y-4",
|
|
3549
|
+
"data-testid": "agent-definition-editor",
|
|
3641
3550
|
children: [
|
|
3642
3551
|
/* @__PURE__ */ jsxs("div", {
|
|
3643
|
-
className: "
|
|
3644
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
3645
|
-
className: "
|
|
3646
|
-
children:
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
setForm(
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3552
|
+
className: "grid gap-3 sm:grid-cols-2",
|
|
3553
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3554
|
+
className: "space-y-1",
|
|
3555
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
3556
|
+
htmlFor: "agent-name",
|
|
3557
|
+
children: tr("fluidgrids.agents.name", "Name")
|
|
3558
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
3559
|
+
id: "agent-name",
|
|
3560
|
+
value: form.name,
|
|
3561
|
+
onChange: (e) => setForm({
|
|
3562
|
+
...form,
|
|
3563
|
+
name: e.target.value
|
|
3564
|
+
}),
|
|
3565
|
+
"data-testid": "agent-definition-name"
|
|
3566
|
+
})]
|
|
3567
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
3568
|
+
className: "flex items-end gap-2",
|
|
3569
|
+
children: [/* @__PURE__ */ jsx(Switch, {
|
|
3570
|
+
id: "agent-active",
|
|
3571
|
+
checked: form.status === "ACTIVE",
|
|
3572
|
+
onCheckedChange: (on) => setForm({
|
|
3573
|
+
...form,
|
|
3574
|
+
status: on ? "ACTIVE" : "DISABLED"
|
|
3575
|
+
})
|
|
3576
|
+
}), /* @__PURE__ */ jsx(Label, {
|
|
3577
|
+
htmlFor: "agent-active",
|
|
3578
|
+
children: tr("fluidgrids.agents.active", "Active")
|
|
3579
|
+
})]
|
|
3580
|
+
})]
|
|
3581
|
+
}),
|
|
3582
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3583
|
+
className: "space-y-1",
|
|
3584
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
3585
|
+
htmlFor: "agent-description",
|
|
3586
|
+
children: tr("fluidgrids.agents.description", "Description")
|
|
3587
|
+
}), /* @__PURE__ */ jsx(Textarea, {
|
|
3588
|
+
id: "agent-description",
|
|
3589
|
+
value: form.description,
|
|
3590
|
+
onChange: (e) => setForm({
|
|
3591
|
+
...form,
|
|
3592
|
+
description: e.target.value
|
|
3593
|
+
}),
|
|
3594
|
+
rows: 2
|
|
3662
3595
|
})]
|
|
3663
3596
|
}),
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3597
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3598
|
+
className: "space-y-2",
|
|
3599
|
+
children: [
|
|
3600
|
+
/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.tools", "Tools the agent may call") }),
|
|
3601
|
+
/* @__PURE__ */ jsx("p", {
|
|
3602
|
+
className: "text-xs text-text-secondary",
|
|
3603
|
+
children: tr("fluidgrids.agents.toolsHelp", "A node type (for example store/slack) and the operations of it the agent may call, separated by commas. Leave the operations empty to allow every operation of that node type. Nothing else can be called.")
|
|
3604
|
+
}),
|
|
3605
|
+
form.tools.map((row, index) => /* @__PURE__ */ jsxs("div", {
|
|
3606
|
+
className: "flex flex-wrap items-center gap-2",
|
|
3607
|
+
"data-testid": `agent-definition-tool-${index}`,
|
|
3608
|
+
children: [
|
|
3609
|
+
/* @__PURE__ */ jsx(Input, {
|
|
3610
|
+
className: "w-48",
|
|
3611
|
+
placeholder: "store/slack",
|
|
3612
|
+
value: row.nodeType,
|
|
3613
|
+
onChange: (e) => setTool(index, { nodeType: e.target.value }),
|
|
3614
|
+
"aria-label": tr("fluidgrids.agents.nodeType", "Node type")
|
|
3615
|
+
}),
|
|
3616
|
+
/* @__PURE__ */ jsx(Input, {
|
|
3617
|
+
className: "min-w-48 flex-1",
|
|
3618
|
+
placeholder: "postMessage, listChannels",
|
|
3619
|
+
value: row.operations,
|
|
3620
|
+
onChange: (e) => setTool(index, { operations: e.target.value }),
|
|
3621
|
+
"aria-label": tr("fluidgrids.agents.operations", "Operations")
|
|
3622
|
+
}),
|
|
3623
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
3624
|
+
variant: "ghost",
|
|
3625
|
+
size: "sm",
|
|
3626
|
+
onClick: () => setForm({
|
|
3627
|
+
...form,
|
|
3628
|
+
tools: form.tools.filter((_, i) => i !== index)
|
|
3629
|
+
}),
|
|
3630
|
+
"aria-label": tr("fluidgrids.agents.removeTool", "Remove tool"),
|
|
3631
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
3632
|
+
icon: "mdi:close",
|
|
3633
|
+
width: 16
|
|
3634
|
+
})
|
|
3635
|
+
})
|
|
3636
|
+
]
|
|
3637
|
+
}, index)),
|
|
3638
|
+
/* @__PURE__ */ jsxs(Button$1, {
|
|
3639
|
+
variant: "outline",
|
|
3640
|
+
size: "sm",
|
|
3641
|
+
onClick: () => setForm({
|
|
3642
|
+
...form,
|
|
3643
|
+
tools: [...form.tools, {
|
|
3644
|
+
nodeType: "",
|
|
3645
|
+
operations: ""
|
|
3646
|
+
}]
|
|
3647
|
+
}),
|
|
3648
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3649
|
+
icon: "mdi:plus",
|
|
3650
|
+
width: 16
|
|
3651
|
+
}), tr("fluidgrids.agents.addTool", "Add tool")]
|
|
3652
|
+
})
|
|
3653
|
+
]
|
|
3654
|
+
}),
|
|
3655
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3656
|
+
className: "space-y-2",
|
|
3657
|
+
children: [/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.agents.connections", "Connections the agent's tools may use") }), connectionsLoading ? /* @__PURE__ */ jsx("p", {
|
|
3658
|
+
className: "text-xs text-text-secondary",
|
|
3659
|
+
children: tr("fluidgrids.agents.loadingConnections", "Loading connections…")
|
|
3660
|
+
}) : connections.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
3661
|
+
className: "text-xs text-text-secondary",
|
|
3662
|
+
children: tr("fluidgrids.agents.noConnections", "This workspace has no connections yet.")
|
|
3663
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
3664
|
+
className: "grid gap-1 sm:grid-cols-2",
|
|
3665
|
+
children: connections.map((connection) => /* @__PURE__ */ jsxs("label", {
|
|
3666
|
+
className: "flex items-center gap-2 text-sm text-text-primary",
|
|
3667
|
+
children: [
|
|
3668
|
+
/* @__PURE__ */ jsx(Checkbox, {
|
|
3669
|
+
checked: form.connectionScope.includes(connection.id),
|
|
3670
|
+
onCheckedChange: (on) => toggleConnection(connection.id, on === true)
|
|
3671
|
+
}),
|
|
3672
|
+
/* @__PURE__ */ jsx("span", { children: connection.name }),
|
|
3673
|
+
/* @__PURE__ */ jsx("span", {
|
|
3674
|
+
className: "text-xs text-text-tertiary",
|
|
3675
|
+
children: connection.provider?.name ?? connection.providerId
|
|
3676
|
+
})
|
|
3677
|
+
]
|
|
3678
|
+
}, connection.id))
|
|
3673
3679
|
})]
|
|
3674
3680
|
}),
|
|
3675
|
-
|
|
3676
|
-
className: "
|
|
3677
|
-
children: [/* @__PURE__ */ jsx(
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
icon: "mdi:close",
|
|
3688
|
-
width: 18
|
|
3689
|
-
})
|
|
3690
|
-
})]
|
|
3691
|
-
}) }), /* @__PURE__ */ jsxs(CardContent, {
|
|
3692
|
-
className: "space-y-5",
|
|
3693
|
-
children: [
|
|
3694
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3695
|
-
className: "grid gap-4 md:grid-cols-2",
|
|
3696
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
3697
|
-
className: "space-y-2",
|
|
3698
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
3699
|
-
htmlFor: "log-stream-name",
|
|
3700
|
-
children: tr("fluidgrids.settings.logStreaming.name", "Name")
|
|
3701
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
3702
|
-
id: "log-stream-name",
|
|
3703
|
-
value: form.name,
|
|
3704
|
-
onChange: (event) => setForm((current) => ({
|
|
3705
|
-
...current,
|
|
3706
|
-
name: event.target.value
|
|
3707
|
-
})),
|
|
3708
|
-
placeholder: "Production error stream"
|
|
3709
|
-
})]
|
|
3710
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
3711
|
-
className: "space-y-2",
|
|
3712
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
3713
|
-
htmlFor: "log-stream-provider",
|
|
3714
|
-
children: tr("fluidgrids.settings.logStreaming.provider", "Provider")
|
|
3715
|
-
}), /* @__PURE__ */ jsx("select", {
|
|
3716
|
-
id: "log-stream-provider",
|
|
3717
|
-
value: form.provider,
|
|
3718
|
-
onChange: (event) => setForm((current) => ({
|
|
3719
|
-
...current,
|
|
3720
|
-
provider: event.target.value,
|
|
3721
|
-
endpoint: ""
|
|
3722
|
-
})),
|
|
3723
|
-
className: "w-full rounded-md border border-border-secondary bg-background-primary px-3 py-2 text-sm text-text-primary",
|
|
3724
|
-
children: PROVIDERS.map((provider) => /* @__PURE__ */ jsx("option", {
|
|
3725
|
-
value: provider,
|
|
3726
|
-
children: provider
|
|
3727
|
-
}, provider))
|
|
3728
|
-
})]
|
|
3729
|
-
})]
|
|
3730
|
-
}),
|
|
3731
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3732
|
-
className: "space-y-2",
|
|
3733
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
3734
|
-
htmlFor: "log-stream-endpoint",
|
|
3735
|
-
children: tr("fluidgrids.settings.logStreaming.endpoint", "Endpoint")
|
|
3736
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
3737
|
-
id: "log-stream-endpoint",
|
|
3738
|
-
value: form.endpoint,
|
|
3739
|
-
onChange: (event) => setForm((current) => ({
|
|
3740
|
-
...current,
|
|
3741
|
-
endpoint: event.target.value
|
|
3742
|
-
})),
|
|
3743
|
-
placeholder: endpointPlaceholder(form.provider),
|
|
3744
|
-
type: form.provider === "WEBHOOK" || form.provider === "SENTRY" ? "url" : "text"
|
|
3745
|
-
})]
|
|
3746
|
-
}),
|
|
3747
|
-
form.provider === "WEBHOOK" && /* @__PURE__ */ jsxs("div", {
|
|
3748
|
-
className: "space-y-2",
|
|
3749
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
3750
|
-
htmlFor: "log-stream-secret",
|
|
3751
|
-
children: tr("fluidgrids.settings.logStreaming.secret", "Signing secret")
|
|
3752
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
3753
|
-
id: "log-stream-secret",
|
|
3754
|
-
value: form.secret,
|
|
3755
|
-
onChange: (event) => setForm((current) => ({
|
|
3756
|
-
...current,
|
|
3757
|
-
secret: event.target.value
|
|
3758
|
-
})),
|
|
3759
|
-
placeholder: editingId ? "Leave blank to keep the current secret" : "Enter a signing secret",
|
|
3760
|
-
type: "password",
|
|
3761
|
-
autoComplete: "new-password"
|
|
3762
|
-
})]
|
|
3763
|
-
}),
|
|
3764
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3765
|
-
className: "space-y-2",
|
|
3766
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
3767
|
-
htmlFor: "log-stream-config",
|
|
3768
|
-
children: tr("fluidgrids.settings.logStreaming.config", "Provider configuration (JSON)")
|
|
3769
|
-
}), /* @__PURE__ */ jsx("textarea", {
|
|
3770
|
-
id: "log-stream-config",
|
|
3771
|
-
value: form.configText,
|
|
3772
|
-
onChange: (event) => setForm((current) => ({
|
|
3773
|
-
...current,
|
|
3774
|
-
configText: event.target.value
|
|
3775
|
-
})),
|
|
3776
|
-
className: "min-h-24 w-full rounded-md border border-border-secondary bg-background-primary px-3 py-2 font-mono text-xs text-text-primary",
|
|
3777
|
-
spellCheck: false
|
|
3778
|
-
})]
|
|
3779
|
-
}),
|
|
3780
|
-
/* @__PURE__ */ jsxs("fieldset", {
|
|
3781
|
-
className: "space-y-2",
|
|
3782
|
-
children: [/* @__PURE__ */ jsx("legend", {
|
|
3783
|
-
className: "text-sm font-medium text-text-primary",
|
|
3784
|
-
children: tr("fluidgrids.settings.logStreaming.events", "Events to stream")
|
|
3785
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
3786
|
-
className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-4",
|
|
3787
|
-
children: LOG_STREAM_EVENT_TYPES.map((event) => /* @__PURE__ */ jsxs("label", {
|
|
3788
|
-
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
3789
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
3790
|
-
type: "checkbox",
|
|
3791
|
-
checked: form.eventFilters.includes(event),
|
|
3792
|
-
onChange: () => toggleEvent(event),
|
|
3793
|
-
className: "size-4 rounded border-border-secondary accent-action-primary-bg"
|
|
3794
|
-
}), eventLabel(event)]
|
|
3795
|
-
}, event))
|
|
3796
|
-
})]
|
|
3797
|
-
}),
|
|
3798
|
-
/* @__PURE__ */ jsxs("label", {
|
|
3799
|
-
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
3800
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
3801
|
-
type: "checkbox",
|
|
3802
|
-
checked: form.isEnabled,
|
|
3803
|
-
onChange: (event) => setForm((current) => ({
|
|
3804
|
-
...current,
|
|
3805
|
-
isEnabled: event.target.checked
|
|
3806
|
-
})),
|
|
3807
|
-
className: "size-4 rounded border-border-secondary accent-action-primary-bg"
|
|
3808
|
-
}), tr("fluidgrids.settings.logStreaming.enabled", "Destination enabled")]
|
|
3809
|
-
}),
|
|
3810
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3811
|
-
className: "flex justify-end gap-2",
|
|
3812
|
-
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
3813
|
-
variant: "outline",
|
|
3814
|
-
onClick: resetForm,
|
|
3815
|
-
children: tr("fluidgrids.settings.logStreaming.cancel", "Cancel")
|
|
3816
|
-
}), /* @__PURE__ */ jsx(Button$1, {
|
|
3817
|
-
"data-agent": "confirm",
|
|
3818
|
-
onClick: () => void save(),
|
|
3819
|
-
disabled: actions.loading,
|
|
3820
|
-
children: actions.loading ? tr("fluidgrids.settings.logStreaming.saving", "Saving…") : tr("fluidgrids.settings.logStreaming.save", "Save destination")
|
|
3821
|
-
})]
|
|
3822
|
-
})
|
|
3823
|
-
]
|
|
3681
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3682
|
+
className: "flex items-center gap-2",
|
|
3683
|
+
children: [/* @__PURE__ */ jsx(Switch, {
|
|
3684
|
+
id: "agent-approval",
|
|
3685
|
+
checked: form.requireToolApproval,
|
|
3686
|
+
onCheckedChange: (on) => setForm({
|
|
3687
|
+
...form,
|
|
3688
|
+
requireToolApproval: on
|
|
3689
|
+
})
|
|
3690
|
+
}), /* @__PURE__ */ jsx(Label, {
|
|
3691
|
+
htmlFor: "agent-approval",
|
|
3692
|
+
children: tr("fluidgrids.agents.requireApproval", "Every tool call waits for a person's approval")
|
|
3824
3693
|
})]
|
|
3825
3694
|
}),
|
|
3826
3695
|
/* @__PURE__ */ jsxs("div", {
|
|
3827
|
-
className: "
|
|
3696
|
+
className: "flex gap-2",
|
|
3697
|
+
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
3698
|
+
onClick: () => void submit(),
|
|
3699
|
+
disabled: busy,
|
|
3700
|
+
"data-testid": "agent-definition-save",
|
|
3701
|
+
children: tr("fluidgrids.agents.save", "Save agent")
|
|
3702
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
3703
|
+
variant: "outline",
|
|
3704
|
+
onClick: onCancel,
|
|
3705
|
+
disabled: busy,
|
|
3706
|
+
children: tr("fluidgrids.agents.cancel", "Cancel")
|
|
3707
|
+
})]
|
|
3708
|
+
})
|
|
3709
|
+
]
|
|
3710
|
+
});
|
|
3711
|
+
}
|
|
3712
|
+
function AgentGrants({ agent, loadGrants, onGrant, onRevoke }) {
|
|
3713
|
+
const tr = useTr();
|
|
3714
|
+
const [grants, setGrants] = useState(null);
|
|
3715
|
+
const [days, setDays] = useState(String(30));
|
|
3716
|
+
const [busy, setBusy] = useState(false);
|
|
3717
|
+
const load = useCallback(async () => {
|
|
3718
|
+
try {
|
|
3719
|
+
setGrants(await loadGrants(agent.id));
|
|
3720
|
+
} catch (err) {
|
|
3721
|
+
setGrants([]);
|
|
3722
|
+
toast.error(errorMessage$1(err));
|
|
3723
|
+
}
|
|
3724
|
+
}, [agent.id, loadGrants]);
|
|
3725
|
+
useEffect(() => {
|
|
3726
|
+
load();
|
|
3727
|
+
}, [load]);
|
|
3728
|
+
const run = async (action, done) => {
|
|
3729
|
+
setBusy(true);
|
|
3730
|
+
try {
|
|
3731
|
+
await action();
|
|
3732
|
+
toast.success(done);
|
|
3733
|
+
await load();
|
|
3734
|
+
} catch (err) {
|
|
3735
|
+
toast.error(errorMessage$1(err));
|
|
3736
|
+
} finally {
|
|
3737
|
+
setBusy(false);
|
|
3738
|
+
}
|
|
3739
|
+
};
|
|
3740
|
+
const mine = grants?.find((grant) => grant.isMine && grant.status === "ACTIVE");
|
|
3741
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3742
|
+
className: "space-y-2",
|
|
3743
|
+
"data-testid": `agent-grants-${agent.id}`,
|
|
3744
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3745
|
+
className: "flex flex-wrap items-center gap-2",
|
|
3746
|
+
children: mine ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
3747
|
+
className: "text-sm text-text-primary",
|
|
3748
|
+
children: mine.expiresAt ? tr("fluidgrids.agents.myGrantUntil", "This agent may act for you until {{date}}.", { date: new Date(mine.expiresAt).toLocaleDateString() }) : tr("fluidgrids.agents.myGrant", "This agent may act for you.")
|
|
3749
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
3750
|
+
variant: "outline",
|
|
3751
|
+
size: "sm",
|
|
3752
|
+
disabled: busy,
|
|
3753
|
+
onClick: () => void run(() => onRevoke(agent.id, null), tr("fluidgrids.agents.withdrawn", "Grant withdrawn")),
|
|
3754
|
+
"data-testid": "agent-grant-withdraw",
|
|
3755
|
+
children: tr("fluidgrids.agents.withdraw", "Withdraw my grant")
|
|
3756
|
+
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3757
|
+
/* @__PURE__ */ jsx(Label, {
|
|
3758
|
+
htmlFor: `grant-days-${agent.id}`,
|
|
3759
|
+
className: "text-sm",
|
|
3760
|
+
children: tr("fluidgrids.agents.grantFor", "Let it act for me for")
|
|
3761
|
+
}),
|
|
3762
|
+
/* @__PURE__ */ jsx(Input, {
|
|
3763
|
+
id: `grant-days-${agent.id}`,
|
|
3764
|
+
className: "w-20",
|
|
3765
|
+
type: "number",
|
|
3766
|
+
min: 1,
|
|
3767
|
+
max: 365,
|
|
3768
|
+
value: days,
|
|
3769
|
+
onChange: (e) => setDays(e.target.value)
|
|
3770
|
+
}),
|
|
3771
|
+
/* @__PURE__ */ jsx("span", {
|
|
3772
|
+
className: "text-sm text-text-secondary",
|
|
3773
|
+
children: tr("fluidgrids.agents.days", "days")
|
|
3774
|
+
}),
|
|
3775
|
+
/* @__PURE__ */ jsx(Button$1, {
|
|
3776
|
+
size: "sm",
|
|
3777
|
+
disabled: busy || agent.status !== "ACTIVE",
|
|
3778
|
+
onClick: () => void run(async () => onGrant(agent.id, grantDays(days)), tr("fluidgrids.agents.granted", "Grant given")),
|
|
3779
|
+
"data-testid": "agent-grant-give",
|
|
3780
|
+
children: tr("fluidgrids.agents.grant", "Grant")
|
|
3781
|
+
})
|
|
3782
|
+
] })
|
|
3783
|
+
}), grants && grants.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
3784
|
+
className: "space-y-1 text-xs text-text-secondary",
|
|
3785
|
+
children: grants.map((grant) => /* @__PURE__ */ jsxs("li", {
|
|
3786
|
+
className: "flex items-center gap-2",
|
|
3828
3787
|
children: [
|
|
3829
|
-
|
|
3830
|
-
className: "
|
|
3831
|
-
children:
|
|
3788
|
+
/* @__PURE__ */ jsx("span", {
|
|
3789
|
+
className: "font-mono",
|
|
3790
|
+
children: grant.userId
|
|
3832
3791
|
}),
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
children:
|
|
3836
|
-
/* @__PURE__ */ jsx(Icon, {
|
|
3837
|
-
icon: "mdi:transit-connection-variant",
|
|
3838
|
-
className: "mx-auto mb-3 text-text-secondary",
|
|
3839
|
-
width: 32
|
|
3840
|
-
}),
|
|
3841
|
-
/* @__PURE__ */ jsx("p", {
|
|
3842
|
-
className: "font-medium text-text-primary",
|
|
3843
|
-
children: tr("fluidgrids.settings.logStreaming.emptyTitle", "No log destinations yet")
|
|
3844
|
-
}),
|
|
3845
|
-
/* @__PURE__ */ jsx("p", {
|
|
3846
|
-
className: "mt-1 text-sm text-text-secondary",
|
|
3847
|
-
children: tr("fluidgrids.settings.logStreaming.emptyDescription", "Add a webhook, syslog target, or Sentry DSN to start streaming events.")
|
|
3848
|
-
})
|
|
3849
|
-
]
|
|
3792
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
3793
|
+
variant: "outline",
|
|
3794
|
+
children: grant.status
|
|
3850
3795
|
}),
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3796
|
+
grant.expiresAt ? /* @__PURE__ */ jsx("span", { children: tr("fluidgrids.agents.until", "until {{date}}", { date: new Date(grant.expiresAt).toLocaleDateString() }) }) : null,
|
|
3797
|
+
!grant.isMine && grant.status === "ACTIVE" ? /* @__PURE__ */ jsx(Button$1, {
|
|
3798
|
+
variant: "ghost",
|
|
3799
|
+
size: "sm",
|
|
3800
|
+
disabled: busy,
|
|
3801
|
+
onClick: () => void run(() => onRevoke(agent.id, grant.userId), tr("fluidgrids.agents.revoked", "Grant revoked")),
|
|
3802
|
+
children: tr("fluidgrids.agents.revoke", "Revoke")
|
|
3803
|
+
}) : null
|
|
3804
|
+
]
|
|
3805
|
+
}, grant.userId))
|
|
3806
|
+
}) : null]
|
|
3807
|
+
});
|
|
3808
|
+
}
|
|
3809
|
+
function AgentsTab() {
|
|
3810
|
+
const tr = useTr();
|
|
3811
|
+
const { agents, loading, error, save, remove, grants, grant, revoke } = useAgentDefinitions();
|
|
3812
|
+
const [editing, setEditing] = useState(null);
|
|
3813
|
+
const onSave = async (form) => {
|
|
3814
|
+
const input = formToInput$2(form);
|
|
3815
|
+
await save(editing?.id ?? null, input);
|
|
3816
|
+
toast.success(tr("fluidgrids.agents.saved", "Agent saved"));
|
|
3817
|
+
setEditing(null);
|
|
3818
|
+
};
|
|
3819
|
+
const onDelete = async (agent) => {
|
|
3820
|
+
try {
|
|
3821
|
+
await remove(agent.id);
|
|
3822
|
+
toast.success(tr("fluidgrids.agents.deleted", "Agent deleted"));
|
|
3823
|
+
} catch (err) {
|
|
3824
|
+
toast.error(errorMessage$1(err));
|
|
3825
|
+
}
|
|
3826
|
+
};
|
|
3827
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3828
|
+
className: "space-y-4",
|
|
3829
|
+
"data-testid": "agents-tab",
|
|
3830
|
+
children: [
|
|
3831
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3832
|
+
className: "flex flex-wrap items-start justify-between gap-2",
|
|
3833
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
|
|
3834
|
+
className: "text-base font-semibold text-text-primary",
|
|
3835
|
+
children: tr("fluidgrids.agents.title", "Agents")
|
|
3836
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
3837
|
+
className: "text-sm text-text-secondary",
|
|
3838
|
+
children: tr("fluidgrids.agents.subtitle", "Give an AI Agent node an identity of its own. It can only call the tools and connections you allow, only for people who granted it, and every tool call is recorded under the agent and the person.")
|
|
3839
|
+
})] }), !editing ? /* @__PURE__ */ jsxs(Button$1, {
|
|
3840
|
+
onClick: () => setEditing({
|
|
3841
|
+
id: null,
|
|
3842
|
+
form: emptyAgentForm()
|
|
3843
|
+
}),
|
|
3844
|
+
"data-testid": "agent-definition-new",
|
|
3845
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3846
|
+
icon: "mdi:plus",
|
|
3847
|
+
width: 16
|
|
3848
|
+
}), tr("fluidgrids.agents.new", "New agent")]
|
|
3849
|
+
}) : null]
|
|
3850
|
+
}),
|
|
3851
|
+
editing ? /* @__PURE__ */ jsx(AgentEditor, {
|
|
3852
|
+
initial: editing.form,
|
|
3853
|
+
onCancel: () => setEditing(null),
|
|
3854
|
+
onSave
|
|
3855
|
+
}) : null,
|
|
3856
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
3857
|
+
className: "text-sm text-destructive",
|
|
3858
|
+
children: errorMessage$1(error)
|
|
3859
|
+
}) : null,
|
|
3860
|
+
loading ? /* @__PURE__ */ jsx("p", {
|
|
3861
|
+
className: "text-sm text-text-secondary",
|
|
3862
|
+
children: tr("fluidgrids.agents.loading", "Loading agents…")
|
|
3863
|
+
}) : agents.length === 0 && !error ? /* @__PURE__ */ jsx("p", {
|
|
3864
|
+
className: "text-sm text-text-secondary",
|
|
3865
|
+
children: tr("fluidgrids.agents.empty", "No agents yet.")
|
|
3866
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
3867
|
+
className: "space-y-3",
|
|
3868
|
+
children: agents.map((agent) => /* @__PURE__ */ jsxs("li", {
|
|
3869
|
+
className: "rounded-lg border border-border-secondary bg-background-secondary p-3 space-y-2",
|
|
3870
|
+
"data-testid": `agent-definition-${agent.id}`,
|
|
3871
|
+
children: [
|
|
3872
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3873
|
+
className: "flex flex-wrap items-center justify-between gap-2",
|
|
3855
3874
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3856
|
-
className: "
|
|
3875
|
+
className: "flex items-center gap-2",
|
|
3857
3876
|
children: [
|
|
3858
|
-
/* @__PURE__ */
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
/* @__PURE__ */ jsx("h3", {
|
|
3862
|
-
className: "font-medium text-text-primary",
|
|
3863
|
-
children: destination.name
|
|
3864
|
-
}),
|
|
3865
|
-
/* @__PURE__ */ jsx("span", {
|
|
3866
|
-
className: "rounded-full bg-background-tertiary px-2 py-0.5 text-xs text-text-secondary",
|
|
3867
|
-
children: destination.provider
|
|
3868
|
-
}),
|
|
3869
|
-
/* @__PURE__ */ jsx("span", {
|
|
3870
|
-
className: destination.isEnabled ? "text-xs text-status-success-text" : "text-xs text-text-secondary",
|
|
3871
|
-
children: destination.isEnabled ? tr("fluidgrids.settings.logStreaming.enabledBadge", "Enabled") : tr("fluidgrids.settings.logStreaming.disabledBadge", "Disabled")
|
|
3872
|
-
})
|
|
3873
|
-
]
|
|
3874
|
-
}),
|
|
3875
|
-
/* @__PURE__ */ jsx("p", {
|
|
3876
|
-
className: "mt-1 truncate text-sm text-text-secondary",
|
|
3877
|
-
children: destination.endpoint
|
|
3877
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
3878
|
+
icon: "mdi:robot-outline",
|
|
3879
|
+
width: 18
|
|
3878
3880
|
}),
|
|
3879
|
-
/* @__PURE__ */
|
|
3880
|
-
className: "
|
|
3881
|
-
children:
|
|
3881
|
+
/* @__PURE__ */ jsx("span", {
|
|
3882
|
+
className: "font-medium text-text-primary",
|
|
3883
|
+
children: agent.name
|
|
3882
3884
|
}),
|
|
3883
|
-
|
|
3884
|
-
className: "mt-2 text-xs text-status-error-text",
|
|
3885
|
-
children: destination.lastDispatchError
|
|
3886
|
-
})
|
|
3887
|
-
]
|
|
3888
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
3889
|
-
className: "flex shrink-0 flex-wrap gap-2",
|
|
3890
|
-
children: [
|
|
3891
|
-
/* @__PURE__ */ jsxs(Button$1, {
|
|
3885
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
3892
3886
|
variant: "outline",
|
|
3893
|
-
|
|
3894
|
-
onClick: () => void sendTest(destination),
|
|
3895
|
-
disabled: actions.loading,
|
|
3896
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3897
|
-
icon: "mdi:send-check-outline",
|
|
3898
|
-
className: "mr-1",
|
|
3899
|
-
width: 15
|
|
3900
|
-
}), tr("fluidgrids.settings.logStreaming.test", "Send test")]
|
|
3887
|
+
children: agent.status
|
|
3901
3888
|
}),
|
|
3902
|
-
/* @__PURE__ */ jsx(
|
|
3889
|
+
agent.requireToolApproval ? /* @__PURE__ */ jsx(Badge, {
|
|
3903
3890
|
variant: "outline",
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
setEditingId(destination.id);
|
|
3907
|
-
setShowForm(true);
|
|
3908
|
-
},
|
|
3909
|
-
disabled: actions.loading,
|
|
3910
|
-
children: tr("fluidgrids.settings.logStreaming.editButton", "Edit")
|
|
3911
|
-
}),
|
|
3912
|
-
/* @__PURE__ */ jsxs(Button$1, {
|
|
3913
|
-
variant: "ghost",
|
|
3914
|
-
size: "sm",
|
|
3915
|
-
onClick: () => void remove(destination),
|
|
3916
|
-
disabled: actions.loading,
|
|
3917
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3918
|
-
icon: "mdi:trash-can-outline",
|
|
3919
|
-
className: "text-status-error-text",
|
|
3920
|
-
width: 17
|
|
3921
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
3922
|
-
className: "sr-only",
|
|
3923
|
-
children: tr("fluidgrids.settings.logStreaming.delete", "Delete")
|
|
3924
|
-
})]
|
|
3925
|
-
})
|
|
3891
|
+
children: tr("fluidgrids.agents.approvalBadge", "Approval required")
|
|
3892
|
+
}) : null
|
|
3926
3893
|
]
|
|
3894
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
3895
|
+
className: "flex gap-1",
|
|
3896
|
+
children: [/* @__PURE__ */ jsx(Button$1, {
|
|
3897
|
+
variant: "ghost",
|
|
3898
|
+
size: "sm",
|
|
3899
|
+
onClick: () => setEditing({
|
|
3900
|
+
id: agent.id,
|
|
3901
|
+
form: agentToForm(agent)
|
|
3902
|
+
}),
|
|
3903
|
+
children: tr("fluidgrids.agents.edit", "Edit")
|
|
3904
|
+
}), /* @__PURE__ */ jsx(Button$1, {
|
|
3905
|
+
variant: "ghost",
|
|
3906
|
+
size: "sm",
|
|
3907
|
+
onClick: () => void onDelete(agent),
|
|
3908
|
+
children: tr("fluidgrids.agents.delete", "Delete")
|
|
3909
|
+
})]
|
|
3927
3910
|
})]
|
|
3911
|
+
}),
|
|
3912
|
+
agent.description ? /* @__PURE__ */ jsx("p", {
|
|
3913
|
+
className: "text-sm text-text-secondary",
|
|
3914
|
+
children: agent.description
|
|
3915
|
+
}) : null,
|
|
3916
|
+
/* @__PURE__ */ jsx("div", {
|
|
3917
|
+
className: "text-xs text-text-secondary",
|
|
3918
|
+
children: agent.toolAllowlist.length > 0 ? agent.toolAllowlist.map(describeRule).join(" · ") : tr("fluidgrids.agents.noTools", "No tools allowed")
|
|
3919
|
+
}),
|
|
3920
|
+
/* @__PURE__ */ jsx("div", {
|
|
3921
|
+
className: "text-xs text-text-tertiary",
|
|
3922
|
+
children: tr("fluidgrids.agents.connectionCount", "{{count}} connections in scope", { count: agent.connectionScope.length })
|
|
3923
|
+
}),
|
|
3924
|
+
/* @__PURE__ */ jsx(AgentGrants, {
|
|
3925
|
+
agent,
|
|
3926
|
+
loadGrants: grants,
|
|
3927
|
+
onGrant: grant,
|
|
3928
|
+
onRevoke: revoke
|
|
3928
3929
|
})
|
|
3929
|
-
|
|
3930
|
-
|
|
3930
|
+
]
|
|
3931
|
+
}, agent.id))
|
|
3931
3932
|
})
|
|
3932
3933
|
]
|
|
3933
3934
|
});
|