@burdenoff/fluidgrids-fe-sdk 2026.1028.4 → 2026.1028.5
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/{ExecutionDetailPage-MYBCDBV7.js → ExecutionDetailPage-UFB7VMGE.js} +2 -1
- package/dist/{chunk-CPEIOFYR.js → chunk-S4VYW4BR.js} +15 -0
- package/dist/chunk-VUVTOUQ5.js +1201 -0
- package/dist/{chunk-VXLUYQDO.js → chunk-WRMIYMXE.js} +14 -0
- package/dist/executions.js +2 -1
- package/dist/index.js +5 -4
- package/dist/settings.js +2 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { ExecutionDetailPage } from './chunk-
|
|
1
|
+
export { ExecutionDetailPage } from './chunk-S4VYW4BR.js';
|
|
2
2
|
import './chunk-SSJIK6WS.js';
|
|
3
3
|
import './chunk-UUUDSVC7.js';
|
|
4
|
+
import './chunk-VUVTOUQ5.js';
|
|
4
5
|
import './chunk-5ERAJC2I.js';
|
|
5
6
|
import './chunk-JAKWVVVQ.js';
|
|
6
7
|
import './chunk-QGXA2USP.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildNodeDisplayMap, ExecutionStatusBadge, formatRunVersion, NodeTimeline, RunLogViewer } from './chunk-SSJIK6WS.js';
|
|
2
2
|
import { useExecution, useExecutionActions, useAutoRefresh } from './chunk-UUUDSVC7.js';
|
|
3
|
+
import { useRunPolicyDecisions, RunPolicyDecisions } from './chunk-VUVTOUQ5.js';
|
|
3
4
|
import { EntityTagPicker, useWorkflowProposal, WorkflowProposalCard } from './chunk-JAKWVVVQ.js';
|
|
4
5
|
import { throwIfResultError, useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS } from './chunk-5LKZ4PNK.js';
|
|
5
6
|
import { getFluidGridsConfig } from './chunk-CY6KX4RG.js';
|
|
@@ -952,6 +953,9 @@ function ExecutionDetailPage() {
|
|
|
952
953
|
} = useRunHumanRequests(runId, {
|
|
953
954
|
pollIntervalMs: runCanChange ? 1e4 : 0
|
|
954
955
|
});
|
|
956
|
+
const { decisions: policyDecisions, decide: decidePolicyApproval } = useRunPolicyDecisions(runId, {
|
|
957
|
+
pollIntervalMs: runCanChange ? 1e4 : 0
|
|
958
|
+
});
|
|
955
959
|
const awaitingPerson = humanRequests.some(
|
|
956
960
|
(request) => request.status === "PENDING"
|
|
957
961
|
);
|
|
@@ -1537,6 +1541,17 @@ function ExecutionDetailPage() {
|
|
|
1537
1541
|
/* @__PURE__ */ jsx(RunQueuedBanner, { status: execution.status, context: execution.context }),
|
|
1538
1542
|
/* @__PURE__ */ jsx(RunDataPrunedNotice, { dataPrunedAt: execution.dataPrunedAt }),
|
|
1539
1543
|
/* @__PURE__ */ jsx(RunHumanRequests, { requests: humanRequests }),
|
|
1544
|
+
/* @__PURE__ */ jsx(
|
|
1545
|
+
RunPolicyDecisions,
|
|
1546
|
+
{
|
|
1547
|
+
decisions: policyDecisions,
|
|
1548
|
+
waiting: execution.status === "WAITING",
|
|
1549
|
+
onDecide: async (id, approve) => {
|
|
1550
|
+
await decidePolicyApproval(id, approve);
|
|
1551
|
+
await Promise.all([refetch(), refetchHumanRequests()]);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
),
|
|
1540
1555
|
/* @__PURE__ */ jsx("div", { className: "border-b border-border-secondary mb-6", children: /* @__PURE__ */ jsx("nav", { className: "flex gap-4 -mb-px", children: tabs.map((tab) => /* @__PURE__ */ jsxs(
|
|
1541
1556
|
"button",
|
|
1542
1557
|
{
|
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
import { throwIfResultError, describeOperationError } from './chunk-5LKZ4PNK.js';
|
|
2
|
+
import { useTr } from './chunk-UULVIT25.js';
|
|
3
|
+
import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
|
|
4
|
+
import { useState, useCallback, useEffect, useMemo } from 'react';
|
|
5
|
+
import { gql } from '@apollo/client';
|
|
6
|
+
import { Button } from '@burdenoff/fe-libs/button';
|
|
7
|
+
import { toast } from 'sonner';
|
|
8
|
+
import { Badge, Label, Input, Switch } from '@burdenoff/fe-libs/ui';
|
|
9
|
+
import { cn } from '@burdenoff/fe-libs/utils';
|
|
10
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
11
|
+
import { Icon } from '@iconify/react';
|
|
12
|
+
|
|
13
|
+
var POLICY_FIELDS = `
|
|
14
|
+
id
|
|
15
|
+
name
|
|
16
|
+
description
|
|
17
|
+
enabled
|
|
18
|
+
priority
|
|
19
|
+
effect
|
|
20
|
+
matcher { nodeTypes operations connectionProviders workflowTags }
|
|
21
|
+
condition
|
|
22
|
+
environments
|
|
23
|
+
updatedAt
|
|
24
|
+
`;
|
|
25
|
+
var APPROVAL_FIELDS = `
|
|
26
|
+
id
|
|
27
|
+
runId
|
|
28
|
+
nodeId
|
|
29
|
+
workflowKey
|
|
30
|
+
policyName
|
|
31
|
+
nodeType
|
|
32
|
+
operation
|
|
33
|
+
environment
|
|
34
|
+
reason
|
|
35
|
+
payloadHash
|
|
36
|
+
status
|
|
37
|
+
decision
|
|
38
|
+
expiresAt
|
|
39
|
+
createdAt
|
|
40
|
+
`;
|
|
41
|
+
var ACTION_POLICIES = gql`
|
|
42
|
+
query ActionPolicies {
|
|
43
|
+
actionPolicies {
|
|
44
|
+
${POLICY_FIELDS}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
var UPSERT_ACTION_POLICY = gql`
|
|
49
|
+
mutation UpsertActionPolicy($input: ActionPolicyInput!) {
|
|
50
|
+
upsertActionPolicy(input: $input) {
|
|
51
|
+
${POLICY_FIELDS}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
var DELETE_ACTION_POLICY = gql`
|
|
56
|
+
mutation DeleteActionPolicy($id: ID!) {
|
|
57
|
+
deleteActionPolicy(id: $id)
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
var DRY_RUN_ACTION_POLICY = gql`
|
|
61
|
+
query DryRunActionPolicy($input: ActionPolicyDryRunInput!) {
|
|
62
|
+
dryRunActionPolicy(input: $input) {
|
|
63
|
+
runsScanned
|
|
64
|
+
actionsEvaluated
|
|
65
|
+
wouldAllow
|
|
66
|
+
wouldLog
|
|
67
|
+
wouldRequireApproval
|
|
68
|
+
wouldDeny
|
|
69
|
+
changed
|
|
70
|
+
runsAffected
|
|
71
|
+
rows {
|
|
72
|
+
runId
|
|
73
|
+
workflowKey
|
|
74
|
+
runCreatedAt
|
|
75
|
+
nodeId
|
|
76
|
+
nodeType
|
|
77
|
+
operation
|
|
78
|
+
environment
|
|
79
|
+
currentEffect
|
|
80
|
+
effect
|
|
81
|
+
reason
|
|
82
|
+
policyName
|
|
83
|
+
changed
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
88
|
+
var PENDING_APPROVALS = gql`
|
|
89
|
+
query PendingActionPolicyApprovals($limit: Int) {
|
|
90
|
+
pendingActionPolicyApprovals(limit: $limit) {
|
|
91
|
+
${APPROVAL_FIELDS}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
var DECIDE_APPROVAL = gql`
|
|
96
|
+
mutation DecideActionPolicyApproval($id: ID!, $approve: Boolean!) {
|
|
97
|
+
decideActionPolicyApproval(id: $id, approve: $approve) {
|
|
98
|
+
${APPROVAL_FIELDS}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
var RUN_DECISIONS = gql`
|
|
103
|
+
query ActionPolicyDecisions($runId: ID!) {
|
|
104
|
+
actionPolicyDecisions(runId: $runId) {
|
|
105
|
+
id
|
|
106
|
+
nodeId
|
|
107
|
+
nodeType
|
|
108
|
+
operation
|
|
109
|
+
environment
|
|
110
|
+
effect
|
|
111
|
+
policyName
|
|
112
|
+
reason
|
|
113
|
+
payloadHash
|
|
114
|
+
approvalRequestId
|
|
115
|
+
consumedAt
|
|
116
|
+
createdAt
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
function asError(err) {
|
|
121
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
122
|
+
}
|
|
123
|
+
function useActionPolicies() {
|
|
124
|
+
const [policies, setPolicies] = useState([]);
|
|
125
|
+
const [approvals, setApprovals] = useState([]);
|
|
126
|
+
const [loading, setLoading] = useState(true);
|
|
127
|
+
const [error, setError] = useState(null);
|
|
128
|
+
const reload = useCallback(async () => {
|
|
129
|
+
try {
|
|
130
|
+
const client = getFluidGridsClient();
|
|
131
|
+
const [policyResult, approvalResult] = await Promise.all([
|
|
132
|
+
client.query({
|
|
133
|
+
query: ACTION_POLICIES,
|
|
134
|
+
fetchPolicy: "network-only"
|
|
135
|
+
}),
|
|
136
|
+
client.query({
|
|
137
|
+
query: PENDING_APPROVALS,
|
|
138
|
+
variables: { limit: 50 },
|
|
139
|
+
fetchPolicy: "network-only"
|
|
140
|
+
})
|
|
141
|
+
]);
|
|
142
|
+
throwIfResultError(policyResult);
|
|
143
|
+
throwIfResultError(approvalResult);
|
|
144
|
+
setPolicies(policyResult.data?.actionPolicies ?? []);
|
|
145
|
+
setApprovals(approvalResult.data?.pendingActionPolicyApprovals ?? []);
|
|
146
|
+
setError(null);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
setError(asError(err));
|
|
149
|
+
} finally {
|
|
150
|
+
setLoading(false);
|
|
151
|
+
}
|
|
152
|
+
}, []);
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
void reload();
|
|
155
|
+
}, [reload]);
|
|
156
|
+
const save = useCallback(
|
|
157
|
+
async (input) => {
|
|
158
|
+
const result = await getFluidGridsClient().mutate({ mutation: UPSERT_ACTION_POLICY, variables: { input } });
|
|
159
|
+
throwIfResultError(result);
|
|
160
|
+
await reload();
|
|
161
|
+
return result.data?.upsertActionPolicy ?? null;
|
|
162
|
+
},
|
|
163
|
+
[reload]
|
|
164
|
+
);
|
|
165
|
+
const remove = useCallback(
|
|
166
|
+
async (id) => {
|
|
167
|
+
const result = await getFluidGridsClient().mutate({ mutation: DELETE_ACTION_POLICY, variables: { id } });
|
|
168
|
+
throwIfResultError(result);
|
|
169
|
+
await reload();
|
|
170
|
+
},
|
|
171
|
+
[reload]
|
|
172
|
+
);
|
|
173
|
+
const dryRun = useCallback(
|
|
174
|
+
async (input) => {
|
|
175
|
+
const result = await getFluidGridsClient().query({
|
|
176
|
+
query: DRY_RUN_ACTION_POLICY,
|
|
177
|
+
variables: { input: { ...input, runLimit: 100 } },
|
|
178
|
+
fetchPolicy: "network-only"
|
|
179
|
+
});
|
|
180
|
+
throwIfResultError(result);
|
|
181
|
+
const data = result.data?.dryRunActionPolicy;
|
|
182
|
+
if (!data) throw new Error("No dry-run result returned");
|
|
183
|
+
return data;
|
|
184
|
+
},
|
|
185
|
+
[]
|
|
186
|
+
);
|
|
187
|
+
const decide = useCallback(
|
|
188
|
+
async (id, approve) => {
|
|
189
|
+
const result = await getFluidGridsClient().mutate({ mutation: DECIDE_APPROVAL, variables: { id, approve } });
|
|
190
|
+
throwIfResultError(result);
|
|
191
|
+
await reload();
|
|
192
|
+
},
|
|
193
|
+
[reload]
|
|
194
|
+
);
|
|
195
|
+
return {
|
|
196
|
+
policies,
|
|
197
|
+
approvals,
|
|
198
|
+
loading,
|
|
199
|
+
error,
|
|
200
|
+
reload,
|
|
201
|
+
save,
|
|
202
|
+
remove,
|
|
203
|
+
dryRun,
|
|
204
|
+
decide
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function useRunPolicyDecisions(runId, options) {
|
|
208
|
+
const [decisions, setDecisions] = useState([]);
|
|
209
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 0;
|
|
210
|
+
const load = useCallback(async () => {
|
|
211
|
+
if (!runId) return;
|
|
212
|
+
try {
|
|
213
|
+
const result = await getFluidGridsClient().query({
|
|
214
|
+
query: RUN_DECISIONS,
|
|
215
|
+
variables: { runId },
|
|
216
|
+
fetchPolicy: "network-only"
|
|
217
|
+
});
|
|
218
|
+
throwIfResultError(result);
|
|
219
|
+
setDecisions(result.data?.actionPolicyDecisions ?? []);
|
|
220
|
+
} catch {
|
|
221
|
+
setDecisions([]);
|
|
222
|
+
}
|
|
223
|
+
}, [runId]);
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
void load();
|
|
226
|
+
if (!pollIntervalMs) return;
|
|
227
|
+
const timer = setInterval(() => void load(), pollIntervalMs);
|
|
228
|
+
return () => clearInterval(timer);
|
|
229
|
+
}, [load, pollIntervalMs]);
|
|
230
|
+
const decide = useCallback(
|
|
231
|
+
async (id, approve) => {
|
|
232
|
+
const result = await getFluidGridsClient().mutate({
|
|
233
|
+
mutation: DECIDE_APPROVAL,
|
|
234
|
+
variables: { id, approve }
|
|
235
|
+
});
|
|
236
|
+
throwIfResultError(result);
|
|
237
|
+
await load();
|
|
238
|
+
},
|
|
239
|
+
[load]
|
|
240
|
+
);
|
|
241
|
+
return { decisions, reload: load, decide };
|
|
242
|
+
}
|
|
243
|
+
var EFFECT_CLASSES = {
|
|
244
|
+
ALLOW: "bg-status-success-bg-subtle text-status-success-text border-status-success-border",
|
|
245
|
+
LOG: "bg-status-info-bg-subtle text-status-info-text border-status-info-border",
|
|
246
|
+
REQUIRE_APPROVAL: "bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border",
|
|
247
|
+
DENY: "bg-status-error-bg-subtle text-status-error-text border-status-error-border"
|
|
248
|
+
};
|
|
249
|
+
function effectLabel(effect, tr) {
|
|
250
|
+
switch (effect) {
|
|
251
|
+
case "ALLOW":
|
|
252
|
+
return tr("fluidgrids.actionPolicies.effect.allow", "Allow");
|
|
253
|
+
case "LOG":
|
|
254
|
+
return tr("fluidgrids.actionPolicies.effect.log", "Log");
|
|
255
|
+
case "REQUIRE_APPROVAL":
|
|
256
|
+
return tr(
|
|
257
|
+
"fluidgrids.actionPolicies.effect.requireApproval",
|
|
258
|
+
"Require approval"
|
|
259
|
+
);
|
|
260
|
+
default:
|
|
261
|
+
return tr("fluidgrids.actionPolicies.effect.deny", "Deny");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function EffectBadge({
|
|
265
|
+
effect,
|
|
266
|
+
className
|
|
267
|
+
}) {
|
|
268
|
+
const tr = useTr();
|
|
269
|
+
return /* @__PURE__ */ jsx(
|
|
270
|
+
Badge,
|
|
271
|
+
{
|
|
272
|
+
variant: "outline",
|
|
273
|
+
className: cn("text-xs", EFFECT_CLASSES[effect], className),
|
|
274
|
+
children: effectLabel(effect, tr)
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
function RunPolicyDecisions({
|
|
279
|
+
decisions,
|
|
280
|
+
waiting,
|
|
281
|
+
onDecide
|
|
282
|
+
}) {
|
|
283
|
+
const tr = useTr();
|
|
284
|
+
const [busy, setBusy] = useState(null);
|
|
285
|
+
if (decisions.length === 0) return null;
|
|
286
|
+
const latestHeld = [...decisions].reverse().find((decision) => decision.effect === "REQUIRE_APPROVAL");
|
|
287
|
+
const held = waiting && latestHeld?.approvalRequestId && !latestHeld.consumedAt ? latestHeld : null;
|
|
288
|
+
const decide = async (approve) => {
|
|
289
|
+
if (!held?.approvalRequestId) return;
|
|
290
|
+
setBusy(held.approvalRequestId);
|
|
291
|
+
try {
|
|
292
|
+
await onDecide(held.approvalRequestId, approve);
|
|
293
|
+
toast.success(
|
|
294
|
+
approve ? tr(
|
|
295
|
+
"fluidgrids.actionPolicies.approved",
|
|
296
|
+
"Approved \u2014 the run continues"
|
|
297
|
+
) : tr(
|
|
298
|
+
"fluidgrids.actionPolicies.declined",
|
|
299
|
+
"Declined \u2014 the node fails"
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
toast.error(describeOperationError(err));
|
|
304
|
+
} finally {
|
|
305
|
+
setBusy(null);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
return /* @__PURE__ */ jsxs(
|
|
309
|
+
"div",
|
|
310
|
+
{
|
|
311
|
+
className: "mb-6 rounded-lg border border-border-secondary bg-background-secondary p-4",
|
|
312
|
+
"data-testid": "run-policy-decisions",
|
|
313
|
+
children: [
|
|
314
|
+
/* @__PURE__ */ jsx("h3", { className: "mb-2 text-sm font-semibold text-text-primary", children: tr("fluidgrids.actionPolicies.runTitle", "Action policies") }),
|
|
315
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-1.5", children: decisions.map((decision) => /* @__PURE__ */ jsxs(
|
|
316
|
+
"li",
|
|
317
|
+
{
|
|
318
|
+
className: "flex flex-wrap items-center gap-2 text-sm text-text-primary",
|
|
319
|
+
children: [
|
|
320
|
+
/* @__PURE__ */ jsx(EffectBadge, { effect: decision.effect }),
|
|
321
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: decision.nodeId }),
|
|
322
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-text-secondary", children: [
|
|
323
|
+
decision.policyName ?? tr(
|
|
324
|
+
"fluidgrids.actionPolicies.defaultDeny",
|
|
325
|
+
"default deny"
|
|
326
|
+
),
|
|
327
|
+
" ",
|
|
328
|
+
"\xB7 ",
|
|
329
|
+
decision.reason,
|
|
330
|
+
" \xB7 ",
|
|
331
|
+
decision.environment
|
|
332
|
+
] })
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
decision.id
|
|
336
|
+
)) }),
|
|
337
|
+
held ? /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [
|
|
338
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-status-warning-text", children: tr(
|
|
339
|
+
"fluidgrids.actionPolicies.heldHint",
|
|
340
|
+
"{{node}} is waiting for a workspace admin. Approving runs exactly the held payload.",
|
|
341
|
+
{ node: held.nodeId }
|
|
342
|
+
) }),
|
|
343
|
+
/* @__PURE__ */ jsx(
|
|
344
|
+
Button,
|
|
345
|
+
{
|
|
346
|
+
size: "sm",
|
|
347
|
+
disabled: busy !== null,
|
|
348
|
+
onClick: () => decide(true),
|
|
349
|
+
children: tr("fluidgrids.actionPolicies.approve", "Approve")
|
|
350
|
+
}
|
|
351
|
+
),
|
|
352
|
+
/* @__PURE__ */ jsx(
|
|
353
|
+
Button,
|
|
354
|
+
{
|
|
355
|
+
size: "sm",
|
|
356
|
+
variant: "outline",
|
|
357
|
+
disabled: busy !== null,
|
|
358
|
+
onClick: () => decide(false),
|
|
359
|
+
children: tr("fluidgrids.actionPolicies.decline", "Decline")
|
|
360
|
+
}
|
|
361
|
+
)
|
|
362
|
+
] }) : null
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/modules/action-policies/utils/policy-form.ts
|
|
369
|
+
var POLICY_EFFECTS = [
|
|
370
|
+
"REQUIRE_APPROVAL",
|
|
371
|
+
"DENY",
|
|
372
|
+
"ALLOW",
|
|
373
|
+
"LOG"
|
|
374
|
+
];
|
|
375
|
+
var POLICY_ENVIRONMENTS = [
|
|
376
|
+
"DEV",
|
|
377
|
+
"STAGING",
|
|
378
|
+
"PROD"
|
|
379
|
+
];
|
|
380
|
+
var CONDITION_OPERATORS = [
|
|
381
|
+
{ value: "gt", label: "is greater than", kind: "number" },
|
|
382
|
+
{ value: "gte", label: "is at least", kind: "number" },
|
|
383
|
+
{ value: "lt", label: "is less than", kind: "number" },
|
|
384
|
+
{ value: "lte", label: "is at most", kind: "number" },
|
|
385
|
+
{ value: "eq", label: "equals", kind: "single" },
|
|
386
|
+
{ value: "neq", label: "does not equal", kind: "single" },
|
|
387
|
+
{ value: "in", label: "is one of", kind: "list" },
|
|
388
|
+
{ value: "not_in", label: "is not one of", kind: "list" },
|
|
389
|
+
{ value: "contains", label: "contains", kind: "single" },
|
|
390
|
+
{ value: "count_gt", label: "has more items than", kind: "number" },
|
|
391
|
+
{ value: "count_gte", label: "has at least \u2026 items", kind: "number" },
|
|
392
|
+
{ value: "count_lt", label: "has fewer items than", kind: "number" },
|
|
393
|
+
{ value: "count_lte", label: "has at most \u2026 items", kind: "number" },
|
|
394
|
+
{ value: "exists", label: "is set", kind: "none" },
|
|
395
|
+
{ value: "not_exists", label: "is not set", kind: "none" }
|
|
396
|
+
];
|
|
397
|
+
var PolicyFormError = class extends Error {
|
|
398
|
+
};
|
|
399
|
+
var OPERATOR_KIND = new Map(
|
|
400
|
+
CONDITION_OPERATORS.map((operator) => [operator.value, operator.kind])
|
|
401
|
+
);
|
|
402
|
+
function operatorKind(op) {
|
|
403
|
+
return OPERATOR_KIND.get(op) ?? "single";
|
|
404
|
+
}
|
|
405
|
+
function emptyPolicyForm() {
|
|
406
|
+
return {
|
|
407
|
+
name: "",
|
|
408
|
+
description: "",
|
|
409
|
+
enabled: true,
|
|
410
|
+
priority: "100",
|
|
411
|
+
effect: "REQUIRE_APPROVAL",
|
|
412
|
+
nodeTypes: "",
|
|
413
|
+
operations: "",
|
|
414
|
+
connectionProviders: "",
|
|
415
|
+
workflowTags: "",
|
|
416
|
+
environments: ["PROD"],
|
|
417
|
+
join: "all",
|
|
418
|
+
rows: []
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function splitList(text) {
|
|
422
|
+
return [
|
|
423
|
+
...new Set(
|
|
424
|
+
text.split(",").map((item) => item.trim()).filter(Boolean)
|
|
425
|
+
)
|
|
426
|
+
];
|
|
427
|
+
}
|
|
428
|
+
function parseScalar(text) {
|
|
429
|
+
const trimmed = text.trim();
|
|
430
|
+
if (trimmed === "true") return true;
|
|
431
|
+
if (trimmed === "false") return false;
|
|
432
|
+
if (trimmed === "null") return null;
|
|
433
|
+
if (trimmed !== "" && Number.isFinite(Number(trimmed)))
|
|
434
|
+
return Number(trimmed);
|
|
435
|
+
return trimmed;
|
|
436
|
+
}
|
|
437
|
+
function formatScalar(value) {
|
|
438
|
+
if (value === null) return "null";
|
|
439
|
+
return String(value);
|
|
440
|
+
}
|
|
441
|
+
function rowToLeaf(row, index) {
|
|
442
|
+
const path = row.path.trim();
|
|
443
|
+
if (!path)
|
|
444
|
+
throw new PolicyFormError(`Condition ${index + 1} needs a parameter path`);
|
|
445
|
+
switch (operatorKind(row.op)) {
|
|
446
|
+
case "none":
|
|
447
|
+
return { path, op: row.op };
|
|
448
|
+
case "number": {
|
|
449
|
+
const value = Number(row.value.trim());
|
|
450
|
+
if (row.value.trim() === "" || !Number.isFinite(value))
|
|
451
|
+
throw new PolicyFormError(`Condition ${index + 1} needs a number`);
|
|
452
|
+
return { path, op: row.op, value };
|
|
453
|
+
}
|
|
454
|
+
case "list": {
|
|
455
|
+
const values = splitList(row.value).map(parseScalar);
|
|
456
|
+
if (values.length === 0)
|
|
457
|
+
throw new PolicyFormError(
|
|
458
|
+
`Condition ${index + 1} needs at least one value`
|
|
459
|
+
);
|
|
460
|
+
return { path, op: row.op, value: values };
|
|
461
|
+
}
|
|
462
|
+
default:
|
|
463
|
+
return { path, op: row.op, value: parseScalar(row.value) };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function formToInput(form) {
|
|
467
|
+
const name = form.name.trim();
|
|
468
|
+
if (!name) throw new PolicyFormError("Give the policy a name");
|
|
469
|
+
const priority = Number(form.priority);
|
|
470
|
+
if (!Number.isInteger(priority) || priority < 0 || priority > 1e5)
|
|
471
|
+
throw new PolicyFormError("Priority is a whole number from 0 to 100000");
|
|
472
|
+
const leaves = form.rows.map(rowToLeaf);
|
|
473
|
+
let condition = null;
|
|
474
|
+
if (leaves.length === 1) condition = leaves[0];
|
|
475
|
+
else if (leaves.length > 1) condition = { [form.join]: leaves };
|
|
476
|
+
return {
|
|
477
|
+
...form.id ? { id: form.id } : {},
|
|
478
|
+
name,
|
|
479
|
+
description: form.description.trim() || null,
|
|
480
|
+
enabled: form.enabled,
|
|
481
|
+
priority,
|
|
482
|
+
effect: form.effect,
|
|
483
|
+
matcher: {
|
|
484
|
+
nodeTypes: splitList(form.nodeTypes),
|
|
485
|
+
operations: splitList(form.operations),
|
|
486
|
+
connectionProviders: splitList(form.connectionProviders),
|
|
487
|
+
workflowTags: splitList(form.workflowTags)
|
|
488
|
+
},
|
|
489
|
+
condition,
|
|
490
|
+
environments: form.environments
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function isRecord(value) {
|
|
494
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
495
|
+
}
|
|
496
|
+
function leafToRow(leaf) {
|
|
497
|
+
if (!isRecord(leaf) || typeof leaf.path !== "string") return null;
|
|
498
|
+
const op = String(leaf.op);
|
|
499
|
+
if (!OPERATOR_KIND.has(op)) return null;
|
|
500
|
+
const value = Array.isArray(leaf.value) ? leaf.value.map(formatScalar).join(", ") : leaf.value === void 0 ? "" : formatScalar(leaf.value);
|
|
501
|
+
return { path: leaf.path, op, value };
|
|
502
|
+
}
|
|
503
|
+
function policyToForm(policy) {
|
|
504
|
+
const base = {
|
|
505
|
+
id: policy.id,
|
|
506
|
+
name: policy.name,
|
|
507
|
+
description: policy.description ?? "",
|
|
508
|
+
enabled: policy.enabled,
|
|
509
|
+
priority: String(policy.priority),
|
|
510
|
+
effect: policy.effect,
|
|
511
|
+
nodeTypes: policy.matcher.nodeTypes.join(", "),
|
|
512
|
+
operations: policy.matcher.operations.join(", "),
|
|
513
|
+
connectionProviders: policy.matcher.connectionProviders.join(", "),
|
|
514
|
+
workflowTags: policy.matcher.workflowTags.join(", "),
|
|
515
|
+
environments: policy.environments,
|
|
516
|
+
join: "all",
|
|
517
|
+
rows: []
|
|
518
|
+
};
|
|
519
|
+
const condition = policy.condition;
|
|
520
|
+
if (condition === null || condition === void 0)
|
|
521
|
+
return { ...base, conditionEditable: true };
|
|
522
|
+
const single = leafToRow(condition);
|
|
523
|
+
if (single) return { ...base, rows: [single], conditionEditable: true };
|
|
524
|
+
if (isRecord(condition)) {
|
|
525
|
+
const join = Array.isArray(condition.all) ? "all" : Array.isArray(condition.any) ? "any" : null;
|
|
526
|
+
const children = join ? condition[join] : [];
|
|
527
|
+
const rows = children.map(leafToRow);
|
|
528
|
+
if (join && rows.every((row) => row !== null))
|
|
529
|
+
return { ...base, join, rows, conditionEditable: true };
|
|
530
|
+
}
|
|
531
|
+
return { ...base, conditionEditable: false };
|
|
532
|
+
}
|
|
533
|
+
function describePolicy(policy) {
|
|
534
|
+
const scope = [
|
|
535
|
+
policy.matcher.nodeTypes.length ? `nodes ${policy.matcher.nodeTypes.join(", ")}` : "",
|
|
536
|
+
policy.matcher.operations.length ? `operations ${policy.matcher.operations.join(", ")}` : "",
|
|
537
|
+
policy.matcher.connectionProviders.length ? `connections ${policy.matcher.connectionProviders.join(", ")}` : "",
|
|
538
|
+
policy.matcher.workflowTags.length ? `tagged ${policy.matcher.workflowTags.join(", ")}` : ""
|
|
539
|
+
].filter(Boolean);
|
|
540
|
+
const where = scope.length ? scope.join("; ") : "every outbound action";
|
|
541
|
+
const env = policy.environments.length ? ` in ${policy.environments.join("/")}` : "";
|
|
542
|
+
const form = policyToForm(policy);
|
|
543
|
+
if (!form.conditionEditable) return `${where}${env}, with a custom condition`;
|
|
544
|
+
if (form.rows.length === 0) return `${where}${env}`;
|
|
545
|
+
const parts = form.rows.map((row) => {
|
|
546
|
+
const label = CONDITION_OPERATORS.find((operator) => operator.value === row.op)?.label ?? row.op;
|
|
547
|
+
return operatorKind(row.op) === "none" ? `${row.path} ${label}` : `${row.path} ${label} ${row.value}`;
|
|
548
|
+
});
|
|
549
|
+
return `${where}${env}, when ${parts.join(form.join === "all" ? " and " : " or ")}`;
|
|
550
|
+
}
|
|
551
|
+
function needsAllowFallback(draft, others) {
|
|
552
|
+
if (draft.effect === "ALLOW" || draft.condition === null) return false;
|
|
553
|
+
return !others.some(
|
|
554
|
+
(policy) => policy.enabled && policy.effect === "ALLOW" && (policy.condition === null || policy.condition === void 0)
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
var FIELD = "rounded-md border border-border-secondary bg-background-secondary px-2 py-1.5 text-sm text-text-primary";
|
|
558
|
+
function errorMessage(err) {
|
|
559
|
+
if (err instanceof PolicyFormError) return err.message;
|
|
560
|
+
return describeOperationError(err);
|
|
561
|
+
}
|
|
562
|
+
function DryRunSummary({ result }) {
|
|
563
|
+
const tr = useTr();
|
|
564
|
+
return /* @__PURE__ */ jsxs(
|
|
565
|
+
"div",
|
|
566
|
+
{
|
|
567
|
+
className: "rounded-md border border-border-secondary bg-background-secondary p-3 space-y-3",
|
|
568
|
+
"data-testid": "action-policy-dry-run",
|
|
569
|
+
children: [
|
|
570
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-text-primary", children: tr(
|
|
571
|
+
"fluidgrids.actionPolicies.dryRun.summary",
|
|
572
|
+
"Across the last {{runs}} runs, {{actions}} outbound actions were checked: {{approval}} would wait for approval and {{deny}} would be denied ({{affected}} runs affected, {{changed}} decisions change).",
|
|
573
|
+
{
|
|
574
|
+
runs: result.runsScanned,
|
|
575
|
+
actions: result.actionsEvaluated,
|
|
576
|
+
approval: result.wouldRequireApproval,
|
|
577
|
+
deny: result.wouldDeny,
|
|
578
|
+
affected: result.runsAffected,
|
|
579
|
+
changed: result.changed
|
|
580
|
+
}
|
|
581
|
+
) }),
|
|
582
|
+
result.rows.length > 0 ? /* @__PURE__ */ jsx("div", { className: "max-h-72 overflow-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
583
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-text-secondary", children: [
|
|
584
|
+
/* @__PURE__ */ jsx("th", { className: "py-1 pr-2 font-medium", children: tr("fluidgrids.actionPolicies.dryRun.workflow", "Workflow") }),
|
|
585
|
+
/* @__PURE__ */ jsx("th", { className: "py-1 pr-2 font-medium", children: tr("fluidgrids.actionPolicies.dryRun.node", "Node") }),
|
|
586
|
+
/* @__PURE__ */ jsx("th", { className: "py-1 pr-2 font-medium", children: tr("fluidgrids.actionPolicies.dryRun.env", "Env") }),
|
|
587
|
+
/* @__PURE__ */ jsx("th", { className: "py-1 pr-2 font-medium", children: tr("fluidgrids.actionPolicies.dryRun.now", "Today") }),
|
|
588
|
+
/* @__PURE__ */ jsx("th", { className: "py-1 font-medium", children: tr("fluidgrids.actionPolicies.dryRun.with", "With draft") })
|
|
589
|
+
] }) }),
|
|
590
|
+
/* @__PURE__ */ jsx("tbody", { children: result.rows.map((row) => /* @__PURE__ */ jsxs(
|
|
591
|
+
"tr",
|
|
592
|
+
{
|
|
593
|
+
className: "border-t border-border-secondary text-text-primary",
|
|
594
|
+
children: [
|
|
595
|
+
/* @__PURE__ */ jsxs("td", { className: "py-1 pr-2", children: [
|
|
596
|
+
/* @__PURE__ */ jsx("div", { children: row.workflowKey }),
|
|
597
|
+
/* @__PURE__ */ jsx("div", { className: "text-text-tertiary", children: new Date(row.runCreatedAt).toLocaleString() })
|
|
598
|
+
] }),
|
|
599
|
+
/* @__PURE__ */ jsxs("td", { className: "py-1 pr-2", children: [
|
|
600
|
+
/* @__PURE__ */ jsx("div", { children: row.nodeId }),
|
|
601
|
+
/* @__PURE__ */ jsxs("div", { className: "font-mono text-text-tertiary", children: [
|
|
602
|
+
row.nodeType,
|
|
603
|
+
row.operation ? ` \xB7 ${row.operation}` : ""
|
|
604
|
+
] })
|
|
605
|
+
] }),
|
|
606
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 pr-2", children: row.environment }),
|
|
607
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 pr-2", children: /* @__PURE__ */ jsx(EffectBadge, { effect: row.currentEffect }) }),
|
|
608
|
+
/* @__PURE__ */ jsxs("td", { className: "py-1", children: [
|
|
609
|
+
/* @__PURE__ */ jsx(EffectBadge, { effect: row.effect }),
|
|
610
|
+
row.policyName ? /* @__PURE__ */ jsx("div", { className: "text-text-tertiary", children: row.policyName }) : null
|
|
611
|
+
] })
|
|
612
|
+
]
|
|
613
|
+
},
|
|
614
|
+
`${row.runId}:${row.nodeId}`
|
|
615
|
+
)) })
|
|
616
|
+
] }) }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
617
|
+
"fluidgrids.actionPolicies.dryRun.nothingBlocked",
|
|
618
|
+
"Nothing in these runs would be blocked or change."
|
|
619
|
+
) })
|
|
620
|
+
]
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
function PolicyEditor({
|
|
625
|
+
initial,
|
|
626
|
+
conditionEditable,
|
|
627
|
+
storedCondition,
|
|
628
|
+
others,
|
|
629
|
+
onCancel,
|
|
630
|
+
onSave,
|
|
631
|
+
onDryRun
|
|
632
|
+
}) {
|
|
633
|
+
const tr = useTr();
|
|
634
|
+
const [form, setForm] = useState(initial);
|
|
635
|
+
const [busy, setBusy] = useState(false);
|
|
636
|
+
const keepCondition = !conditionEditable;
|
|
637
|
+
const fallbackWarning = useMemo(() => {
|
|
638
|
+
try {
|
|
639
|
+
const input = formToInput(form);
|
|
640
|
+
return needsAllowFallback(
|
|
641
|
+
{
|
|
642
|
+
effect: input.effect,
|
|
643
|
+
condition: keepCondition ? storedCondition : input.condition
|
|
644
|
+
},
|
|
645
|
+
others
|
|
646
|
+
);
|
|
647
|
+
} catch {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
}, [form, others, keepCondition, storedCondition]);
|
|
651
|
+
const run = async (action) => {
|
|
652
|
+
setBusy(true);
|
|
653
|
+
try {
|
|
654
|
+
await action();
|
|
655
|
+
} catch (err) {
|
|
656
|
+
toast.error(errorMessage(err));
|
|
657
|
+
} finally {
|
|
658
|
+
setBusy(false);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
const setRow = (index, patch) => setForm((current) => ({
|
|
662
|
+
...current,
|
|
663
|
+
rows: current.rows.map(
|
|
664
|
+
(row, i) => i === index ? { ...row, ...patch } : row
|
|
665
|
+
)
|
|
666
|
+
}));
|
|
667
|
+
return /* @__PURE__ */ jsxs(
|
|
668
|
+
"div",
|
|
669
|
+
{
|
|
670
|
+
className: "rounded-lg border border-border-secondary bg-background-primary p-4 space-y-4",
|
|
671
|
+
"data-testid": "action-policy-editor",
|
|
672
|
+
children: [
|
|
673
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
674
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
675
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "policy-name", children: tr("fluidgrids.actionPolicies.name", "Name") }),
|
|
676
|
+
/* @__PURE__ */ jsx(
|
|
677
|
+
Input,
|
|
678
|
+
{
|
|
679
|
+
id: "policy-name",
|
|
680
|
+
value: form.name,
|
|
681
|
+
maxLength: 200,
|
|
682
|
+
onChange: (e) => setForm({ ...form, name: e.target.value }),
|
|
683
|
+
placeholder: tr(
|
|
684
|
+
"fluidgrids.actionPolicies.namePlaceholder",
|
|
685
|
+
"Payments over 1,000 need approval"
|
|
686
|
+
)
|
|
687
|
+
}
|
|
688
|
+
)
|
|
689
|
+
] }),
|
|
690
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
691
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
692
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "policy-effect", children: tr("fluidgrids.actionPolicies.effectLabel", "Effect") }),
|
|
693
|
+
/* @__PURE__ */ jsx(
|
|
694
|
+
"select",
|
|
695
|
+
{
|
|
696
|
+
id: "policy-effect",
|
|
697
|
+
className: `w-full ${FIELD}`,
|
|
698
|
+
value: form.effect,
|
|
699
|
+
onChange: (e) => setForm({ ...form, effect: e.target.value }),
|
|
700
|
+
children: POLICY_EFFECTS.map((effect) => /* @__PURE__ */ jsx("option", { value: effect, children: effectLabel(effect, tr) }, effect))
|
|
701
|
+
}
|
|
702
|
+
)
|
|
703
|
+
] }),
|
|
704
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
705
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "policy-priority", children: tr("fluidgrids.actionPolicies.priority", "Priority") }),
|
|
706
|
+
/* @__PURE__ */ jsx(
|
|
707
|
+
Input,
|
|
708
|
+
{
|
|
709
|
+
id: "policy-priority",
|
|
710
|
+
inputMode: "numeric",
|
|
711
|
+
value: form.priority,
|
|
712
|
+
onChange: (e) => setForm({ ...form, priority: e.target.value })
|
|
713
|
+
}
|
|
714
|
+
)
|
|
715
|
+
] })
|
|
716
|
+
] })
|
|
717
|
+
] }),
|
|
718
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
719
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "policy-description", children: tr("fluidgrids.actionPolicies.description", "Description") }),
|
|
720
|
+
/* @__PURE__ */ jsx(
|
|
721
|
+
Input,
|
|
722
|
+
{
|
|
723
|
+
id: "policy-description",
|
|
724
|
+
value: form.description,
|
|
725
|
+
maxLength: 2e3,
|
|
726
|
+
onChange: (e) => setForm({ ...form, description: e.target.value })
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
] }),
|
|
730
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
731
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-text-primary", children: tr("fluidgrids.actionPolicies.appliesTo", "Applies to") }),
|
|
732
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
733
|
+
"fluidgrids.actionPolicies.appliesToHelp",
|
|
734
|
+
"Comma-separated. Node types and operations accept * and ? wildcards (store/stripe*, internal:http). Leave a field empty to match anything."
|
|
735
|
+
) }),
|
|
736
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
737
|
+
["nodeTypes", "Node types", "store/stripe*, internal:http"],
|
|
738
|
+
["operations", "Operations", "refund*, createCharge"],
|
|
739
|
+
["connectionProviders", "Connection providers", "stripe"],
|
|
740
|
+
["workflowTags", "Workflow tags", "finance"]
|
|
741
|
+
].map(([key, label, placeholder]) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
742
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `policy-${key}`, children: tr(`fluidgrids.actionPolicies.matcher.${key}`, label) }),
|
|
743
|
+
/* @__PURE__ */ jsx(
|
|
744
|
+
Input,
|
|
745
|
+
{
|
|
746
|
+
id: `policy-${key}`,
|
|
747
|
+
value: form[key],
|
|
748
|
+
placeholder,
|
|
749
|
+
onChange: (e) => setForm({ ...form, [key]: e.target.value })
|
|
750
|
+
}
|
|
751
|
+
)
|
|
752
|
+
] }, key)) }),
|
|
753
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-4 pt-1", children: [
|
|
754
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-text-secondary", children: tr("fluidgrids.actionPolicies.environments", "Environments") }),
|
|
755
|
+
POLICY_ENVIRONMENTS.map((environment) => /* @__PURE__ */ jsxs(
|
|
756
|
+
"label",
|
|
757
|
+
{
|
|
758
|
+
className: "flex items-center gap-1.5 text-sm text-text-primary",
|
|
759
|
+
children: [
|
|
760
|
+
/* @__PURE__ */ jsx(
|
|
761
|
+
"input",
|
|
762
|
+
{
|
|
763
|
+
type: "checkbox",
|
|
764
|
+
checked: form.environments.includes(environment),
|
|
765
|
+
onChange: (e) => setForm({
|
|
766
|
+
...form,
|
|
767
|
+
environments: e.target.checked ? [...form.environments, environment] : form.environments.filter(
|
|
768
|
+
(item) => item !== environment
|
|
769
|
+
)
|
|
770
|
+
})
|
|
771
|
+
}
|
|
772
|
+
),
|
|
773
|
+
environment
|
|
774
|
+
]
|
|
775
|
+
},
|
|
776
|
+
environment
|
|
777
|
+
)),
|
|
778
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-text-tertiary", children: tr(
|
|
779
|
+
"fluidgrids.actionPolicies.environmentsHelp",
|
|
780
|
+
"None checked applies in every environment. Test runs are DEV; triggered and API runs are PROD."
|
|
781
|
+
) })
|
|
782
|
+
] })
|
|
783
|
+
] }),
|
|
784
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
785
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
786
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-text-primary", children: tr("fluidgrids.actionPolicies.condition", "Condition") }),
|
|
787
|
+
conditionEditable && form.rows.length > 1 ? /* @__PURE__ */ jsxs(
|
|
788
|
+
"select",
|
|
789
|
+
{
|
|
790
|
+
"aria-label": tr(
|
|
791
|
+
"fluidgrids.actionPolicies.join",
|
|
792
|
+
"Combine conditions"
|
|
793
|
+
),
|
|
794
|
+
className: FIELD,
|
|
795
|
+
value: form.join,
|
|
796
|
+
onChange: (e) => setForm({ ...form, join: e.target.value }),
|
|
797
|
+
children: [
|
|
798
|
+
/* @__PURE__ */ jsx("option", { value: "all", children: tr("fluidgrids.actionPolicies.joinAll", "all must hold") }),
|
|
799
|
+
/* @__PURE__ */ jsx("option", { value: "any", children: tr("fluidgrids.actionPolicies.joinAny", "any may hold") })
|
|
800
|
+
]
|
|
801
|
+
}
|
|
802
|
+
) : null
|
|
803
|
+
] }),
|
|
804
|
+
!conditionEditable ? /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
805
|
+
"fluidgrids.actionPolicies.customCondition",
|
|
806
|
+
"This policy has a nested condition saved through the API. It is kept as it is."
|
|
807
|
+
) }) : form.rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
808
|
+
"fluidgrids.actionPolicies.noCondition",
|
|
809
|
+
"No condition: the policy fires on every action it applies to."
|
|
810
|
+
) }) : form.rows.map((row, index) => /* @__PURE__ */ jsxs(
|
|
811
|
+
"div",
|
|
812
|
+
{
|
|
813
|
+
className: "flex flex-col gap-2 sm:flex-row sm:items-center",
|
|
814
|
+
"data-testid": "action-policy-condition-row",
|
|
815
|
+
children: [
|
|
816
|
+
/* @__PURE__ */ jsx(
|
|
817
|
+
Input,
|
|
818
|
+
{
|
|
819
|
+
"aria-label": tr(
|
|
820
|
+
"fluidgrids.actionPolicies.path",
|
|
821
|
+
"Parameter path"
|
|
822
|
+
),
|
|
823
|
+
className: "sm:w-56",
|
|
824
|
+
value: row.path,
|
|
825
|
+
placeholder: "body.amount",
|
|
826
|
+
onChange: (e) => setRow(index, { path: e.target.value })
|
|
827
|
+
}
|
|
828
|
+
),
|
|
829
|
+
/* @__PURE__ */ jsx(
|
|
830
|
+
"select",
|
|
831
|
+
{
|
|
832
|
+
"aria-label": tr(
|
|
833
|
+
"fluidgrids.actionPolicies.operator",
|
|
834
|
+
"Operator"
|
|
835
|
+
),
|
|
836
|
+
className: FIELD,
|
|
837
|
+
value: row.op,
|
|
838
|
+
onChange: (e) => setRow(index, { op: e.target.value }),
|
|
839
|
+
children: CONDITION_OPERATORS.map((operator) => /* @__PURE__ */ jsx("option", { value: operator.value, children: tr(
|
|
840
|
+
`fluidgrids.actionPolicies.op.${operator.value}`,
|
|
841
|
+
operator.label
|
|
842
|
+
) }, operator.value))
|
|
843
|
+
}
|
|
844
|
+
),
|
|
845
|
+
operatorKind(row.op) === "none" ? null : /* @__PURE__ */ jsx(
|
|
846
|
+
Input,
|
|
847
|
+
{
|
|
848
|
+
"aria-label": tr("fluidgrids.actionPolicies.value", "Value"),
|
|
849
|
+
className: "sm:w-48",
|
|
850
|
+
value: row.value,
|
|
851
|
+
placeholder: operatorKind(row.op) === "list" ? "usd, eur" : "1000",
|
|
852
|
+
onChange: (e) => setRow(index, { value: e.target.value })
|
|
853
|
+
}
|
|
854
|
+
),
|
|
855
|
+
/* @__PURE__ */ jsx(
|
|
856
|
+
Button,
|
|
857
|
+
{
|
|
858
|
+
variant: "ghost",
|
|
859
|
+
size: "sm",
|
|
860
|
+
"aria-label": tr(
|
|
861
|
+
"fluidgrids.actionPolicies.removeCondition",
|
|
862
|
+
"Remove condition"
|
|
863
|
+
),
|
|
864
|
+
onClick: () => setForm({
|
|
865
|
+
...form,
|
|
866
|
+
rows: form.rows.filter((_, i) => i !== index)
|
|
867
|
+
}),
|
|
868
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:close", width: 16 })
|
|
869
|
+
}
|
|
870
|
+
)
|
|
871
|
+
]
|
|
872
|
+
},
|
|
873
|
+
index
|
|
874
|
+
)),
|
|
875
|
+
conditionEditable ? /* @__PURE__ */ jsxs(
|
|
876
|
+
Button,
|
|
877
|
+
{
|
|
878
|
+
variant: "outline",
|
|
879
|
+
size: "sm",
|
|
880
|
+
onClick: () => setForm({
|
|
881
|
+
...form,
|
|
882
|
+
rows: [...form.rows, { path: "", op: "gt", value: "" }]
|
|
883
|
+
}),
|
|
884
|
+
children: [
|
|
885
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", width: 16, className: "mr-1" }),
|
|
886
|
+
tr("fluidgrids.actionPolicies.addCondition", "Add condition")
|
|
887
|
+
]
|
|
888
|
+
}
|
|
889
|
+
) : null
|
|
890
|
+
] }),
|
|
891
|
+
fallbackWarning ? /* @__PURE__ */ jsx("p", { className: "rounded-md border border-status-warning-border bg-status-warning-bg-subtle p-2 text-xs text-status-warning-text", children: tr(
|
|
892
|
+
"fluidgrids.actionPolicies.defaultDenyWarning",
|
|
893
|
+
"Default deny: actions this policy applies to but whose condition does not hold are denied unless another policy allows them. Add an Allow policy with the same scope and a higher priority number to let the rest through, and check the dry run."
|
|
894
|
+
) }) : null,
|
|
895
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
896
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm text-text-primary", children: [
|
|
897
|
+
/* @__PURE__ */ jsx(
|
|
898
|
+
Switch,
|
|
899
|
+
{
|
|
900
|
+
checked: form.enabled,
|
|
901
|
+
onCheckedChange: (enabled) => setForm({ ...form, enabled })
|
|
902
|
+
}
|
|
903
|
+
),
|
|
904
|
+
tr("fluidgrids.actionPolicies.enabled", "Enabled")
|
|
905
|
+
] }),
|
|
906
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
907
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, disabled: busy, children: tr("fluidgrids.actionPolicies.cancel", "Cancel") }),
|
|
908
|
+
/* @__PURE__ */ jsxs(
|
|
909
|
+
Button,
|
|
910
|
+
{
|
|
911
|
+
variant: "outline",
|
|
912
|
+
disabled: busy,
|
|
913
|
+
onClick: () => run(() => onDryRun(form, keepCondition)),
|
|
914
|
+
children: [
|
|
915
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:test-tube", width: 16, className: "mr-1" }),
|
|
916
|
+
tr("fluidgrids.actionPolicies.tryIt", "What would this block?")
|
|
917
|
+
]
|
|
918
|
+
}
|
|
919
|
+
),
|
|
920
|
+
/* @__PURE__ */ jsx(
|
|
921
|
+
Button,
|
|
922
|
+
{
|
|
923
|
+
disabled: busy,
|
|
924
|
+
onClick: () => run(() => onSave(form, keepCondition)),
|
|
925
|
+
children: tr("fluidgrids.actionPolicies.save", "Save policy")
|
|
926
|
+
}
|
|
927
|
+
)
|
|
928
|
+
] })
|
|
929
|
+
] })
|
|
930
|
+
]
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
function ApprovalsList({
|
|
935
|
+
approvals,
|
|
936
|
+
onDecide
|
|
937
|
+
}) {
|
|
938
|
+
const tr = useTr();
|
|
939
|
+
const [busy, setBusy] = useState(null);
|
|
940
|
+
if (approvals.length === 0) return null;
|
|
941
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "action-policy-approvals", children: [
|
|
942
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr("fluidgrids.actionPolicies.pendingTitle", "Waiting for approval") }),
|
|
943
|
+
approvals.map((approval) => /* @__PURE__ */ jsxs(
|
|
944
|
+
"div",
|
|
945
|
+
{
|
|
946
|
+
className: "flex flex-col gap-2 rounded-md border border-status-warning-border bg-status-warning-bg-subtle p-3 sm:flex-row sm:items-center sm:justify-between",
|
|
947
|
+
children: [
|
|
948
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 text-sm text-text-primary", children: [
|
|
949
|
+
/* @__PURE__ */ jsxs("div", { className: "font-medium", children: [
|
|
950
|
+
approval.workflowKey,
|
|
951
|
+
" \xB7 ",
|
|
952
|
+
approval.nodeId
|
|
953
|
+
] }),
|
|
954
|
+
/* @__PURE__ */ jsxs("div", { className: "text-xs text-text-secondary", children: [
|
|
955
|
+
approval.policyName ?? "",
|
|
956
|
+
" \xB7 ",
|
|
957
|
+
approval.nodeType,
|
|
958
|
+
approval.operation ? ` \xB7 ${approval.operation}` : "",
|
|
959
|
+
" \xB7",
|
|
960
|
+
" ",
|
|
961
|
+
approval.environment,
|
|
962
|
+
approval.reason === "PAYLOAD_CHANGED" ? ` \xB7 ${tr("fluidgrids.actionPolicies.payloadChanged", "changed after an earlier approval")}` : ""
|
|
963
|
+
] }),
|
|
964
|
+
/* @__PURE__ */ jsxs("div", { className: "font-mono text-[10px] text-text-tertiary", children: [
|
|
965
|
+
tr("fluidgrids.actionPolicies.payload", "Payload"),
|
|
966
|
+
" ",
|
|
967
|
+
approval.payloadHash.slice(0, 16)
|
|
968
|
+
] })
|
|
969
|
+
] }),
|
|
970
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
971
|
+
/* @__PURE__ */ jsx(
|
|
972
|
+
Button,
|
|
973
|
+
{
|
|
974
|
+
size: "sm",
|
|
975
|
+
disabled: busy === approval.id,
|
|
976
|
+
onClick: async () => {
|
|
977
|
+
setBusy(approval.id);
|
|
978
|
+
try {
|
|
979
|
+
await onDecide(approval.id, true);
|
|
980
|
+
toast.success(
|
|
981
|
+
tr(
|
|
982
|
+
"fluidgrids.actionPolicies.approved",
|
|
983
|
+
"Approved \u2014 the run continues"
|
|
984
|
+
)
|
|
985
|
+
);
|
|
986
|
+
} catch (err) {
|
|
987
|
+
toast.error(errorMessage(err));
|
|
988
|
+
} finally {
|
|
989
|
+
setBusy(null);
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
children: tr("fluidgrids.actionPolicies.approve", "Approve")
|
|
993
|
+
}
|
|
994
|
+
),
|
|
995
|
+
/* @__PURE__ */ jsx(
|
|
996
|
+
Button,
|
|
997
|
+
{
|
|
998
|
+
size: "sm",
|
|
999
|
+
variant: "outline",
|
|
1000
|
+
disabled: busy === approval.id,
|
|
1001
|
+
onClick: async () => {
|
|
1002
|
+
setBusy(approval.id);
|
|
1003
|
+
try {
|
|
1004
|
+
await onDecide(approval.id, false);
|
|
1005
|
+
toast.success(
|
|
1006
|
+
tr(
|
|
1007
|
+
"fluidgrids.actionPolicies.declined",
|
|
1008
|
+
"Declined \u2014 the node fails"
|
|
1009
|
+
)
|
|
1010
|
+
);
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
toast.error(errorMessage(err));
|
|
1013
|
+
} finally {
|
|
1014
|
+
setBusy(null);
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
children: tr("fluidgrids.actionPolicies.decline", "Decline")
|
|
1018
|
+
}
|
|
1019
|
+
)
|
|
1020
|
+
] })
|
|
1021
|
+
]
|
|
1022
|
+
},
|
|
1023
|
+
approval.id
|
|
1024
|
+
))
|
|
1025
|
+
] });
|
|
1026
|
+
}
|
|
1027
|
+
function ActionPoliciesTab() {
|
|
1028
|
+
const tr = useTr();
|
|
1029
|
+
const { policies, approvals, loading, error, save, remove, dryRun, decide } = useActionPolicies();
|
|
1030
|
+
const [editing, setEditing] = useState(null);
|
|
1031
|
+
const [preview, setPreview] = useState(null);
|
|
1032
|
+
const [confirmDelete, setConfirmDelete] = useState(null);
|
|
1033
|
+
const toInput = (form, keepCondition) => {
|
|
1034
|
+
const input = formToInput(form);
|
|
1035
|
+
return keepCondition && editing ? { ...input, condition: editing.storedCondition } : input;
|
|
1036
|
+
};
|
|
1037
|
+
const others = policies.filter((policy) => policy.id !== editing?.form.id);
|
|
1038
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", "data-testid": "action-policies-tab", children: [
|
|
1039
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
|
|
1040
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1041
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-text-primary", children: tr("fluidgrids.actionPolicies.title", "Action policies") }),
|
|
1042
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary max-w-2xl", children: tr(
|
|
1043
|
+
"fluidgrids.actionPolicies.subtitle",
|
|
1044
|
+
"Rules every workflow in this workspace follows before it calls the outside world, whoever built it: require approval above a threshold, deny an operation, or log it. Policies are checked in priority order (lowest first). Without a policy that applies, an action runs as before."
|
|
1045
|
+
) })
|
|
1046
|
+
] }),
|
|
1047
|
+
!editing ? /* @__PURE__ */ jsxs(
|
|
1048
|
+
Button,
|
|
1049
|
+
{
|
|
1050
|
+
onClick: () => {
|
|
1051
|
+
setPreview(null);
|
|
1052
|
+
setEditing({
|
|
1053
|
+
form: emptyPolicyForm(),
|
|
1054
|
+
conditionEditable: true,
|
|
1055
|
+
storedCondition: null
|
|
1056
|
+
});
|
|
1057
|
+
},
|
|
1058
|
+
children: [
|
|
1059
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", width: 16, className: "mr-1" }),
|
|
1060
|
+
tr("fluidgrids.actionPolicies.add", "Add policy")
|
|
1061
|
+
]
|
|
1062
|
+
}
|
|
1063
|
+
) : null
|
|
1064
|
+
] }),
|
|
1065
|
+
/* @__PURE__ */ jsx(ApprovalsList, { approvals, onDecide: decide }),
|
|
1066
|
+
editing ? /* @__PURE__ */ jsx(
|
|
1067
|
+
PolicyEditor,
|
|
1068
|
+
{
|
|
1069
|
+
initial: editing.form,
|
|
1070
|
+
conditionEditable: editing.conditionEditable,
|
|
1071
|
+
storedCondition: editing.storedCondition,
|
|
1072
|
+
others,
|
|
1073
|
+
onCancel: () => {
|
|
1074
|
+
setEditing(null);
|
|
1075
|
+
setPreview(null);
|
|
1076
|
+
},
|
|
1077
|
+
onDryRun: async (form, keepCondition) => {
|
|
1078
|
+
setPreview(await dryRun({ policy: toInput(form, keepCondition) }));
|
|
1079
|
+
},
|
|
1080
|
+
onSave: async (form, keepCondition) => {
|
|
1081
|
+
await save(toInput(form, keepCondition));
|
|
1082
|
+
toast.success(
|
|
1083
|
+
tr("fluidgrids.actionPolicies.saved", "Policy saved")
|
|
1084
|
+
);
|
|
1085
|
+
setEditing(null);
|
|
1086
|
+
setPreview(null);
|
|
1087
|
+
}
|
|
1088
|
+
},
|
|
1089
|
+
editing.form.id ?? "new"
|
|
1090
|
+
) : null,
|
|
1091
|
+
preview ? /* @__PURE__ */ jsx(DryRunSummary, { result: preview }) : null,
|
|
1092
|
+
loading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr("fluidgrids.actionPolicies.loading", "Loading policies\u2026") }) : error ? /* @__PURE__ */ jsx("p", { className: "text-sm text-status-error-text", children: describeOperationError(error) }) : policies.length === 0 ? /* @__PURE__ */ jsx(
|
|
1093
|
+
"p",
|
|
1094
|
+
{
|
|
1095
|
+
className: "text-sm text-text-secondary",
|
|
1096
|
+
"data-testid": "action-policies-empty",
|
|
1097
|
+
children: tr(
|
|
1098
|
+
"fluidgrids.actionPolicies.empty",
|
|
1099
|
+
"No policies yet. Every action runs as its workflow says."
|
|
1100
|
+
)
|
|
1101
|
+
}
|
|
1102
|
+
) : /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border-secondary rounded-lg border border-border-secondary", children: policies.map((policy) => /* @__PURE__ */ jsxs(
|
|
1103
|
+
"li",
|
|
1104
|
+
{
|
|
1105
|
+
className: "flex flex-col gap-2 p-3 sm:flex-row sm:items-center sm:justify-between",
|
|
1106
|
+
"data-testid": "action-policy-row",
|
|
1107
|
+
children: [
|
|
1108
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1109
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1110
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-text-primary", children: policy.name }),
|
|
1111
|
+
/* @__PURE__ */ jsx(EffectBadge, { effect: policy.effect }),
|
|
1112
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: tr(
|
|
1113
|
+
"fluidgrids.actionPolicies.priorityShort",
|
|
1114
|
+
"#{{priority}}",
|
|
1115
|
+
{
|
|
1116
|
+
priority: policy.priority
|
|
1117
|
+
}
|
|
1118
|
+
) }),
|
|
1119
|
+
!policy.enabled ? /* @__PURE__ */ jsx(
|
|
1120
|
+
Badge,
|
|
1121
|
+
{
|
|
1122
|
+
variant: "outline",
|
|
1123
|
+
className: "text-xs text-text-tertiary",
|
|
1124
|
+
children: tr("fluidgrids.actionPolicies.disabled", "Disabled")
|
|
1125
|
+
}
|
|
1126
|
+
) : null
|
|
1127
|
+
] }),
|
|
1128
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: describePolicy(policy) })
|
|
1129
|
+
] }),
|
|
1130
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
|
|
1131
|
+
/* @__PURE__ */ jsx(
|
|
1132
|
+
Button,
|
|
1133
|
+
{
|
|
1134
|
+
variant: "ghost",
|
|
1135
|
+
size: "sm",
|
|
1136
|
+
onClick: async () => {
|
|
1137
|
+
try {
|
|
1138
|
+
setPreview(await dryRun({ excludePolicyId: policy.id }));
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
toast.error(errorMessage(err));
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
children: tr("fluidgrids.actionPolicies.withoutIt", "Without it")
|
|
1144
|
+
}
|
|
1145
|
+
),
|
|
1146
|
+
/* @__PURE__ */ jsx(
|
|
1147
|
+
Button,
|
|
1148
|
+
{
|
|
1149
|
+
variant: "ghost",
|
|
1150
|
+
size: "sm",
|
|
1151
|
+
onClick: () => {
|
|
1152
|
+
const { conditionEditable, ...form } = policyToForm(policy);
|
|
1153
|
+
setPreview(null);
|
|
1154
|
+
setEditing({
|
|
1155
|
+
form,
|
|
1156
|
+
conditionEditable,
|
|
1157
|
+
storedCondition: policy.condition ?? null
|
|
1158
|
+
});
|
|
1159
|
+
},
|
|
1160
|
+
children: tr("fluidgrids.actionPolicies.edit", "Edit")
|
|
1161
|
+
}
|
|
1162
|
+
),
|
|
1163
|
+
/* @__PURE__ */ jsx(
|
|
1164
|
+
Button,
|
|
1165
|
+
{
|
|
1166
|
+
variant: "ghost",
|
|
1167
|
+
size: "sm",
|
|
1168
|
+
className: "text-destructive",
|
|
1169
|
+
onClick: async () => {
|
|
1170
|
+
if (confirmDelete !== policy.id) {
|
|
1171
|
+
setConfirmDelete(policy.id);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
setConfirmDelete(null);
|
|
1175
|
+
try {
|
|
1176
|
+
await remove(policy.id);
|
|
1177
|
+
toast.success(
|
|
1178
|
+
tr(
|
|
1179
|
+
"fluidgrids.actionPolicies.deleted",
|
|
1180
|
+
"Policy deleted"
|
|
1181
|
+
)
|
|
1182
|
+
);
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
toast.error(errorMessage(err));
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1187
|
+
children: confirmDelete === policy.id ? tr(
|
|
1188
|
+
"fluidgrids.actionPolicies.confirmDelete",
|
|
1189
|
+
"Confirm delete"
|
|
1190
|
+
) : tr("fluidgrids.actionPolicies.delete", "Delete")
|
|
1191
|
+
}
|
|
1192
|
+
)
|
|
1193
|
+
] })
|
|
1194
|
+
]
|
|
1195
|
+
},
|
|
1196
|
+
policy.id
|
|
1197
|
+
)) })
|
|
1198
|
+
] });
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
export { ActionPoliciesTab, RunPolicyDecisions, useRunPolicyDecisions };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ActionPoliciesTab } from './chunk-VUVTOUQ5.js';
|
|
1
2
|
import { useFluidGridsFlag, FLUIDGRIDS_FLAGS, LogStreamingTab } from './chunk-LWF5J43N.js';
|
|
2
3
|
import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
|
|
3
4
|
import { useFlowStore, useSaveStatusStore, useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, throwIfResultError } from './chunk-5LKZ4PNK.js';
|
|
@@ -1240,6 +1241,18 @@ function SettingsPage({
|
|
|
1240
1241
|
/* @__PURE__ */ jsx(Icon, { icon: "mdi:database-clock-outline", width: 16 }),
|
|
1241
1242
|
tr("fluidgrids.settings.page.runDataTab", "Run data")
|
|
1242
1243
|
] }),
|
|
1244
|
+
/* @__PURE__ */ jsxs(
|
|
1245
|
+
TabsTrigger,
|
|
1246
|
+
{
|
|
1247
|
+
value: "policies",
|
|
1248
|
+
className: "gap-1.5",
|
|
1249
|
+
"data-tour": "fluidgrids-settings-policies-tab",
|
|
1250
|
+
children: [
|
|
1251
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:shield-check-outline", width: 16 }),
|
|
1252
|
+
tr("fluidgrids.settings.page.policiesTab", "Policies")
|
|
1253
|
+
]
|
|
1254
|
+
}
|
|
1255
|
+
),
|
|
1243
1256
|
/* @__PURE__ */ jsxs(TabsTrigger, { value: "notifications", className: "gap-1.5", children: [
|
|
1244
1257
|
/* @__PURE__ */ jsx(Icon, { icon: "mdi:bell-outline", width: 16 }),
|
|
1245
1258
|
tr("fluidgrids.settings.page.notificationsTab", "Notifications")
|
|
@@ -1266,6 +1279,7 @@ function SettingsPage({
|
|
|
1266
1279
|
/* @__PURE__ */ jsx(TabsContent, { value: "general", children: /* @__PURE__ */ jsx(GeneralTab, {}) }),
|
|
1267
1280
|
/* @__PURE__ */ jsx(TabsContent, { value: "execution", children: /* @__PURE__ */ jsx(ExecutionTab, { workflowKey }) }),
|
|
1268
1281
|
/* @__PURE__ */ jsx(TabsContent, { value: "run-data", children: /* @__PURE__ */ jsx(RunDataTab, {}) }),
|
|
1282
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "policies", children: /* @__PURE__ */ jsx(ActionPoliciesTab, {}) }),
|
|
1269
1283
|
/* @__PURE__ */ jsx(TabsContent, { value: "notifications", children: /* @__PURE__ */ jsx(NotificationsTab, {}) }),
|
|
1270
1284
|
logStreamsEnabled ? /* @__PURE__ */ jsx(TabsContent, { value: "log-streaming", children: /* @__PURE__ */ jsx(LogStreamingTab, {}) }) : null,
|
|
1271
1285
|
/* @__PURE__ */ jsx(TabsContent, { value: "api", children: /* @__PURE__ */ jsx(ApiTab, {}) })
|
package/dist/executions.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import './chunk-AD3BXTJS.js';
|
|
2
2
|
export { ExecutionsListPage } from './chunk-H7CJXSMI.js';
|
|
3
|
-
export { ExecutionDetailPage } from './chunk-
|
|
3
|
+
export { ExecutionDetailPage } from './chunk-S4VYW4BR.js';
|
|
4
4
|
import './chunk-SSJIK6WS.js';
|
|
5
5
|
import './chunk-UUUDSVC7.js';
|
|
6
|
+
import './chunk-VUVTOUQ5.js';
|
|
6
7
|
import './chunk-5ERAJC2I.js';
|
|
7
8
|
import './chunk-JAKWVVVQ.js';
|
|
8
9
|
import './chunk-QGXA2USP.js';
|
package/dist/index.js
CHANGED
|
@@ -5,14 +5,15 @@ export { FirstWorkflowChecklist, FirstWorkflowChecklistView, WorkflowsList, useC
|
|
|
5
5
|
export { CreateWorkflowRefusalDialog, GUIDED_FIRST_WORKFLOW_NAV, GUIDED_FIRST_WORKFLOW_STATE, GUIDED_TOUR_STATE, firstWorkflowProgress, isGuidedTourIntent, pickGuidedTour } from './chunk-CSBKB5EX.js';
|
|
6
6
|
import './chunk-AD3BXTJS.js';
|
|
7
7
|
export { ExecutionsListPage } from './chunk-H7CJXSMI.js';
|
|
8
|
-
import { usePendingHumanRequests, HumanRequestCard } from './chunk-
|
|
9
|
-
export { ExecutionDetailPage, HumanRequestCard, RunHumanRequests, usePendingHumanRequests, useRespondToHumanRequest, useRunHumanRequests } from './chunk-
|
|
8
|
+
import { usePendingHumanRequests, HumanRequestCard } from './chunk-S4VYW4BR.js';
|
|
9
|
+
export { ExecutionDetailPage, HumanRequestCard, RunHumanRequests, usePendingHumanRequests, useRespondToHumanRequest, useRunHumanRequests } from './chunk-S4VYW4BR.js';
|
|
10
10
|
import { TagFilterSelect } from './chunk-SSJIK6WS.js';
|
|
11
11
|
import './chunk-UUUDSVC7.js';
|
|
12
12
|
export { HistoryPage } from './chunk-Y6YOY33Z.js';
|
|
13
13
|
export { NodesPage, fetchAllNodesWithManifests, useLazyNodeManifest, useNodeEndpoint, useNodeEndpoints, useNodeManifest } from './chunk-AYFKS4MM.js';
|
|
14
14
|
export { DataSinkDashboardPage, ErrorAnalyticsPage, InsightsPage } from './chunk-FOEEDKBP.js';
|
|
15
|
-
export { SettingsPage } from './chunk-
|
|
15
|
+
export { SettingsPage } from './chunk-WRMIYMXE.js';
|
|
16
|
+
import './chunk-VUVTOUQ5.js';
|
|
16
17
|
export { FLUIDGRIDS_FLAGS, useDataSinkData, useDataSinks, useFluidGridsFlag, useInsightsData, useLogStreamDestinationActions, useLogStreamDestinations, useRunReplayActions } from './chunk-LWF5J43N.js';
|
|
17
18
|
import './chunk-5ERAJC2I.js';
|
|
18
19
|
import { ThemeProvider, extractTemplateGraph } from './chunk-ZF55RVJ3.js';
|
|
@@ -82,7 +83,7 @@ var ExecutionsListPage2 = lazy(
|
|
|
82
83
|
}))
|
|
83
84
|
);
|
|
84
85
|
var ExecutionDetailPage2 = lazy(
|
|
85
|
-
() => import('./ExecutionDetailPage-
|
|
86
|
+
() => import('./ExecutionDetailPage-UFB7VMGE.js').then((m) => ({
|
|
86
87
|
default: m.ExecutionDetailPage
|
|
87
88
|
}))
|
|
88
89
|
);
|
package/dist/settings.js
CHANGED