@burdenoff/fluidgrids-fe-sdk 2026.1107.1 → 2026.1108.2
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-TQST7JKC.js → ExecutionDetailPage-PY5OE2RJ.js} +16 -15
- package/dist/{ExecutionsListPage-QQKEGXSC.js → ExecutionsListPage-LKNYPXNZ.js} +12 -11
- package/dist/{MissingNodesInstallModal-OSAJLIBE.js → MissingNodesInstallModal-Q6OPULJU.js} +2 -1
- package/dist/{TemplateConfigurationModal-SILENYXX.js → TemplateConfigurationModal-PWCDDPKT.js} +2 -1
- package/dist/{TemplateDetailPanel-7MPY5MYT.js → TemplateDetailPanel-5WY75DVB.js} +3 -2
- package/dist/WorkflowBuilder-YFPBMOWZ.js +37 -0
- package/dist/{WorkflowsList-2WX7N526.js → WorkflowsList-TETNEMYP.js} +11 -11
- package/dist/{agentTools-MEEGOAE2.js → agentTools-BW2XMHRV.js} +7 -7
- package/dist/{chunk-KE677WYY.js → chunk-2CXUTQUH.js} +1 -1
- package/dist/{chunk-H4ERTSMX.js → chunk-2VSYOGNI.js} +1948 -35
- package/dist/{chunk-NZMUKAXG.js → chunk-54NXUOHI.js} +1 -1
- package/dist/{chunk-E3PNEIBJ.js → chunk-62RJORF7.js} +5 -5
- package/dist/{chunk-4F54UT62.js → chunk-66AKFXP7.js} +21 -18
- package/dist/{chunk-MESGVHUU.js → chunk-7SSMRID2.js} +5 -5
- package/dist/{chunk-752BT4WM.js → chunk-7XW6BC4B.js} +1 -1
- package/dist/{chunk-5375NUIO.js → chunk-AR22A74Y.js} +11 -0
- package/dist/{chunk-5KK3ALQ4.js → chunk-D5Y3DHGJ.js} +1 -1
- package/dist/{chunk-NSDBXKIC.js → chunk-DG4UO5OY.js} +5 -5
- package/dist/{chunk-NMYENCP2.js → chunk-E7KIH3CF.js} +1 -1
- package/dist/{chunk-HUCRE45R.js → chunk-GI6BQIQA.js} +127 -42
- package/dist/{chunk-Z3ABLERJ.js → chunk-GOZR72NK.js} +64 -1
- package/dist/{chunk-KHS2DZHH.js → chunk-HICQ4J4W.js} +1 -1
- package/dist/chunk-HPNZ3PYU.js +1448 -0
- package/dist/{chunk-RI4NFNDX.js → chunk-HQSSW7K3.js} +8 -8
- package/dist/{chunk-CTKWMPQQ.js → chunk-HSOSMPRC.js} +3 -1
- package/dist/{chunk-JRSCUUBT.js → chunk-IPJV2IOH.js} +1 -1
- package/dist/{chunk-PYSEOWLW.js → chunk-IWLNBDR2.js} +5 -5
- package/dist/{chunk-MYAFMXFI.js → chunk-IZOPKR64.js} +3 -3
- package/dist/{chunk-ZWDKN6PU.js → chunk-JRR4VKYD.js} +95 -6
- package/dist/{chunk-TDFC5U66.js → chunk-JSJSBB76.js} +2 -2
- package/dist/{chunk-V2K4HJY4.js → chunk-KJB3WZVD.js} +2 -2
- package/dist/{chunk-OHKSDHNC.js → chunk-L7PHZ7RY.js} +2 -2
- package/dist/{chunk-JOOGRWYY.js → chunk-LOMLLWHD.js} +705 -12
- package/dist/{chunk-ZTZNQ5GF.js → chunk-MYPB2KTJ.js} +13 -6
- package/dist/{chunk-HY3XCZLB.js → chunk-N2QXEV5Y.js} +8 -8
- package/dist/{chunk-EXJW7EKR.js → chunk-OJHBCOBA.js} +164 -1
- package/dist/{chunk-BLLBUHVF.js → chunk-POYMF7L6.js} +1 -1
- package/dist/{chunk-BQXVT32O.js → chunk-QJAZXCFQ.js} +1 -23
- package/dist/{chunk-GA3V3X7Q.js → chunk-RNYFPG7R.js} +4 -4
- package/dist/chunk-TFCIZFDM.js +28 -0
- package/dist/{chunk-PASKZCII.js → chunk-TN3NGTF7.js} +3 -3
- package/dist/{chunk-KXCJPWYE.js → chunk-VH3EET6H.js} +2 -2
- package/dist/{chunk-UZJKX5FN.js → chunk-X3F5IQBP.js} +5 -3
- package/dist/chunk-XRGAH6IQ.js +110 -0
- package/dist/chunk-YASVILWQ.js +398 -0
- package/dist/{chunk-WBVBRXDI.js → chunk-ZHXJYOOG.js} +2115 -25
- package/dist/executions.js +17 -16
- package/dist/history.js +9 -9
- package/dist/index.d.ts +15474 -238
- package/dist/index.js +1064 -58
- package/dist/{node-detail-view-AQCGO4XQ.js → node-detail-view-BGWKE6KW.js} +490 -22
- package/dist/nodes.js +5 -2
- package/dist/observability.js +9 -9
- package/dist/{quick-config-panel-G3T4JCF6.js → quick-config-panel-67BU4I23.js} +9 -9
- package/dist/settings.js +11 -9
- package/dist/templates.js +19 -17
- package/dist/{workflow-builder-C261exmL.d.ts → workflow-builder-D7eMRluc.d.ts} +3 -1
- package/dist/workflow-builder.d.ts +1 -1
- package/dist/workflow-builder.js +21 -19
- package/dist/workflow-preview.js +2 -2
- package/dist/workflows-list.js +11 -11
- package/package.json +2 -2
- package/dist/WorkflowBuilder-R7VIOVRO.js +0 -35
- package/dist/chunk-VKC3ICRA.js +0 -529
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { useDurableRunFields, useRunHistory, useDeterminismStatus, useRunStackTrace, RunHistoryView, SignalRunDialog, terminateDurableRun } from './chunk-
|
|
2
|
-
import { useDurableParityRunFields, hasAgentNodeState, agentNodeTraces, orderNodesByExecution,
|
|
3
|
-
import { useExecution, useExecutionActions, useAutoRefresh, runChainLinks, shortRunId, callbackTone, parseMetadataDefinition, overrideRequestOf } from './chunk-
|
|
4
|
-
import { fetchRunFailureClusters, OPEN_STATUSES, dismissRunFailureCluster, BUCKET_LABELS } from './chunk-
|
|
1
|
+
import { useDurableRunFields, useRunHistory, useDeterminismStatus, useRunStackTrace, RunHistoryView, SignalRunDialog, terminateDurableRun } from './chunk-MYPB2KTJ.js';
|
|
2
|
+
import { useDurableParityRunFields, hasAgentNodeState, agentNodeTraces, orderNodesByExecution, useRunPending, useRunMetadata, ExecutionStatusBadge, NodeTimeline, RunLogViewer, NodeStatusBadge, setRunVersioningOverride } from './chunk-RNYFPG7R.js';
|
|
3
|
+
import { useExecution, useExecutionActions, useAutoRefresh, runChainLinks, shortRunId, callbackTone, parseMetadataDefinition, overrideRequestOf } from './chunk-JRR4VKYD.js';
|
|
4
|
+
import { fetchRunFailureClusters, OPEN_STATUSES, dismissRunFailureCluster, BUCKET_LABELS } from './chunk-54NXUOHI.js';
|
|
5
5
|
import { aiCost, formatTokens, modelLabel } from './chunk-NZAYYUQ4.js';
|
|
6
|
-
import { useRunPolicyDecisions, RunPolicyDecisions } from './chunk-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { useRunPolicyDecisions, RunPolicyDecisions, checkCodecServerUrl, containsEncodedPayload, isEncodedPayload, encodedPayloadBytes, decodePayloads, CodecServerError } from './chunk-OJHBCOBA.js';
|
|
7
|
+
import { readTyped, writeTyped, useResource, fetchCodecServerSettings, fetchRunControlFields, fetchPauseRules, RUN_CONTROL_DIALOG_STYLE, fetchRunHistoryArchive, parseJsonInput, NativeSelect, JsonField, FieldError, StatusPill, JsonBlock, fetchSignalTokens, parseJsonObjectInput, queryRun, revokeSignalToken, upsertRunSearchAttributes, newRunControlId, fetchRunUpdate, createSignalToken, pauseActivities, unpauseActivities, resetActivities, updateActivityOptions, completeActivityById, failActivityById, sendRunUpdate, resetRunToLastCleanTask, resetRun } from './chunk-YASVILWQ.js';
|
|
8
|
+
import { EntityTagPicker, canSaveRunAsTestCase, useWorkflowProposal, WorkflowProposalCard, MAX_CASE_NAME, saveRunAsTestCase } from './chunk-TN3NGTF7.js';
|
|
9
|
+
import { buildNodeDisplayMap, formatRunVersion, nodeDisplayTitle } from './chunk-HICQ4J4W.js';
|
|
9
10
|
import { AiCreditBalanceSlot } from './chunk-Y53CVZVV.js';
|
|
10
|
-
import { useWorkflowVersions } from './chunk-
|
|
11
|
-
import { useDurableFunctionNodesEnabled, useDurableParityEnabled, runSignalTargets, canSignalRun, formatElapsedMs, runDurationMs, useWorkflowTestsEnabled } from './chunk-
|
|
12
|
-
import { useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, SdkPermissionButton } from './chunk-
|
|
13
|
-
import { RunLogsDocument, RunLogAppendedDocument, ExplainRunDocument, RunAiUsageDocument } from './chunk-
|
|
11
|
+
import { useWorkflowVersions } from './chunk-KJB3WZVD.js';
|
|
12
|
+
import { useDurableFunctionNodesEnabled, useDurableParityEnabled, usePhaseCFlags, runSignalTargets, canSignalRun, formatElapsedMs, runDurationMs, useWorkflowTestsEnabled, durableInterfaceOf } from './chunk-GI6BQIQA.js';
|
|
13
|
+
import { useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, SdkPermissionButton } from './chunk-AR22A74Y.js';
|
|
14
|
+
import { TaskQueuesDocument, TaskQueueStatsDocument, CreateTaskQueueDocument, UpdateTaskQueueDocument, DeleteTaskQueueDocument, WorkflowWorkersDocument, WorkerTokensDocument, CreateWorkerTokenDocument, RotateWorkerTokenDocument, DeleteWorkerTokenDocument, SetWorkerTokenConnectionAllowlistDocument, RunLogsDocument, RunLogAppendedDocument, ExplainRunDocument, RunAiUsageDocument } from './chunk-GOZR72NK.js';
|
|
14
15
|
import { useTr } from './chunk-UULVIT25.js';
|
|
15
16
|
import { getFluidGridsClient, getFluidGridsClientSafe } from './chunk-PDODDDHY.js';
|
|
16
17
|
import { throwIfResultError, describeOperationError } from './chunk-JKLZ6YZ3.js';
|
|
@@ -20,7 +21,7 @@ import { useParams, useNavigate, useSearchParams, Link } from 'react-router';
|
|
|
20
21
|
import { Icon } from '@iconify/react';
|
|
21
22
|
import { toast } from 'sonner';
|
|
22
23
|
import { Button } from '@burdenoff/fe-libs/button';
|
|
23
|
-
import { Card, CardHeader, Badge, CardContent, Label, Textarea, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, Input, Button as Button$1, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@burdenoff/fe-libs/ui';
|
|
24
|
+
import { Card, CardHeader, Badge, CardContent, Label, Textarea, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, Input, Button as Button$1, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DropdownMenuSeparator, Switch } from '@burdenoff/fe-libs/ui';
|
|
24
25
|
import { useActorProfiles, buildActorFallback } from '@burdenoff/fe-libs/shared';
|
|
25
26
|
import { gql } from '@apollo/client';
|
|
26
27
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
@@ -463,7 +464,7 @@ function HumanRequestCard({
|
|
|
463
464
|
}) {
|
|
464
465
|
const tr = useTr();
|
|
465
466
|
const { respond, loading } = useRespondToHumanRequest();
|
|
466
|
-
const [
|
|
467
|
+
const [text2, setText] = useState("");
|
|
467
468
|
const [values, setValues] = useState({});
|
|
468
469
|
const submit = async (answer) => {
|
|
469
470
|
try {
|
|
@@ -490,7 +491,7 @@ function HumanRequestCard({
|
|
|
490
491
|
const missingRequired = request.responseType === "CUSTOM_FORM" && request.fields.some(
|
|
491
492
|
(field) => field.required && !(values[field.name] ?? "").trim()
|
|
492
493
|
);
|
|
493
|
-
const emptyText = request.responseType === "FREE_TEXT" && !
|
|
494
|
+
const emptyText = request.responseType === "FREE_TEXT" && !text2.trim();
|
|
494
495
|
const recipient = useRecipientLabel(
|
|
495
496
|
request,
|
|
496
497
|
request.channel === "IN_APP" ? tr("fluidgrids.humanResponse.anyMember", "any workspace member") : ""
|
|
@@ -547,7 +548,7 @@ function HumanRequestCard({
|
|
|
547
548
|
Textarea,
|
|
548
549
|
{
|
|
549
550
|
id: `human-text-${request.id}`,
|
|
550
|
-
value:
|
|
551
|
+
value: text2,
|
|
551
552
|
onChange: (event) => setText(event.target.value)
|
|
552
553
|
}
|
|
553
554
|
)
|
|
@@ -598,7 +599,7 @@ function HumanRequestCard({
|
|
|
598
599
|
size: "sm",
|
|
599
600
|
disabled: loading || missingRequired || emptyText,
|
|
600
601
|
onClick: () => submit(
|
|
601
|
-
request.responseType === "FREE_TEXT" ? { text } : { values }
|
|
602
|
+
request.responseType === "FREE_TEXT" ? { text: text2 } : { values }
|
|
602
603
|
),
|
|
603
604
|
"data-testid": "human-request-submit",
|
|
604
605
|
children: [
|
|
@@ -1292,7 +1293,7 @@ function urlTransform(url, key) {
|
|
|
1292
1293
|
return safeMarkdownHref(url);
|
|
1293
1294
|
}
|
|
1294
1295
|
function RunMarkdown({
|
|
1295
|
-
text,
|
|
1296
|
+
text: text2,
|
|
1296
1297
|
"data-testid": testId
|
|
1297
1298
|
}) {
|
|
1298
1299
|
return /* @__PURE__ */ jsx(
|
|
@@ -1306,7 +1307,7 @@ function RunMarkdown({
|
|
|
1306
1307
|
components: COMPONENTS,
|
|
1307
1308
|
skipHtml: true,
|
|
1308
1309
|
urlTransform,
|
|
1309
|
-
children:
|
|
1310
|
+
children: text2
|
|
1310
1311
|
}
|
|
1311
1312
|
)
|
|
1312
1313
|
}
|
|
@@ -1981,6 +1982,2042 @@ function useRunAiUsage(runId, { enabled, live }) {
|
|
|
1981
1982
|
refetch: read
|
|
1982
1983
|
};
|
|
1983
1984
|
}
|
|
1985
|
+
|
|
1986
|
+
// src/modules/task-queues/api.ts
|
|
1987
|
+
async function fetchTaskQueues() {
|
|
1988
|
+
return (await readTyped(TaskQueuesDocument, {})).taskQueues;
|
|
1989
|
+
}
|
|
1990
|
+
async function fetchTaskQueueStats(name, environment) {
|
|
1991
|
+
return (await readTyped(TaskQueueStatsDocument, {
|
|
1992
|
+
name,
|
|
1993
|
+
environment: environment ?? null
|
|
1994
|
+
})).taskQueueStats;
|
|
1995
|
+
}
|
|
1996
|
+
async function createTaskQueue(input) {
|
|
1997
|
+
return (await writeTyped(CreateTaskQueueDocument, { input })).createTaskQueue;
|
|
1998
|
+
}
|
|
1999
|
+
async function updateTaskQueue(id, input) {
|
|
2000
|
+
return (await writeTyped(UpdateTaskQueueDocument, { id, input })).updateTaskQueue;
|
|
2001
|
+
}
|
|
2002
|
+
async function deleteTaskQueue(id) {
|
|
2003
|
+
return (await writeTyped(DeleteTaskQueueDocument, { id })).deleteTaskQueue;
|
|
2004
|
+
}
|
|
2005
|
+
async function fetchWorkflowWorkers(filter) {
|
|
2006
|
+
return (await readTyped(WorkflowWorkersDocument, { filter: filter ?? null })).workflowWorkers;
|
|
2007
|
+
}
|
|
2008
|
+
async function fetchWorkerTokens() {
|
|
2009
|
+
return (await readTyped(WorkerTokensDocument, {})).workerTokens;
|
|
2010
|
+
}
|
|
2011
|
+
async function createWorkerToken(input) {
|
|
2012
|
+
return (await writeTyped(CreateWorkerTokenDocument, input)).createWorkerToken;
|
|
2013
|
+
}
|
|
2014
|
+
async function rotateWorkerToken(id) {
|
|
2015
|
+
return (await writeTyped(RotateWorkerTokenDocument, { id })).rotateWorkerToken;
|
|
2016
|
+
}
|
|
2017
|
+
async function deleteWorkerToken(id) {
|
|
2018
|
+
return (await writeTyped(DeleteWorkerTokenDocument, { id })).deleteWorkerToken;
|
|
2019
|
+
}
|
|
2020
|
+
async function setWorkerTokenConnectionAllowlist(id, names) {
|
|
2021
|
+
return (await writeTyped(SetWorkerTokenConnectionAllowlistDocument, { id, names })).setWorkerTokenConnectionAllowlist;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/modules/run-control/utils/activity-pause.ts
|
|
2025
|
+
function text(value) {
|
|
2026
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2027
|
+
}
|
|
2028
|
+
function activityPauseStates(events) {
|
|
2029
|
+
const states = /* @__PURE__ */ new Map();
|
|
2030
|
+
const ordered = [...events].sort((a, b) => a.seq - b.seq);
|
|
2031
|
+
for (const event of ordered) {
|
|
2032
|
+
if (event.type !== "ACTIVITY_PAUSED" && event.type !== "ACTIVITY_UNPAUSED")
|
|
2033
|
+
continue;
|
|
2034
|
+
const activityId = text(event.payload?.["activityId"]);
|
|
2035
|
+
if (!activityId) continue;
|
|
2036
|
+
if (event.type === "ACTIVITY_PAUSED") {
|
|
2037
|
+
states.set(activityId, {
|
|
2038
|
+
paused: true,
|
|
2039
|
+
reason: text(event.payload?.["reason"]),
|
|
2040
|
+
ruleId: text(event.payload?.["ruleId"]),
|
|
2041
|
+
seq: event.seq
|
|
2042
|
+
});
|
|
2043
|
+
} else {
|
|
2044
|
+
states.set(activityId, {
|
|
2045
|
+
paused: false,
|
|
2046
|
+
reason: null,
|
|
2047
|
+
ruleId: null,
|
|
2048
|
+
seq: event.seq
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
return states;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
// src/modules/run-control/utils/handlers.ts
|
|
2056
|
+
var GRAPH_RUN_QUERIES = [
|
|
2057
|
+
"status",
|
|
2058
|
+
"progress",
|
|
2059
|
+
"nodeStates",
|
|
2060
|
+
"nodeOutput"
|
|
2061
|
+
];
|
|
2062
|
+
function isRecord2(value) {
|
|
2063
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2064
|
+
}
|
|
2065
|
+
function declaredHandlers(definitionJson, workflowFn, kind) {
|
|
2066
|
+
const iface = durableInterfaceOf(definitionJson, workflowFn);
|
|
2067
|
+
if (!iface) return { names: [], openNames: false };
|
|
2068
|
+
const declared = iface[kind];
|
|
2069
|
+
const names = isRecord2(declared) ? Object.keys(declared).sort() : Array.isArray(declared) ? declared.filter((item) => typeof item === "string").sort() : [];
|
|
2070
|
+
const open = isRecord2(iface.open) && iface.open[kind] === true;
|
|
2071
|
+
return { names, openNames: open };
|
|
2072
|
+
}
|
|
2073
|
+
function isFinalUpdateStatus(status) {
|
|
2074
|
+
return status === "COMPLETED" || status === "REJECTED";
|
|
2075
|
+
}
|
|
2076
|
+
function updateReached(status, waitFor) {
|
|
2077
|
+
if (isFinalUpdateStatus(status)) return true;
|
|
2078
|
+
return waitFor === "ACCEPTED" && status === "ACCEPTED";
|
|
2079
|
+
}
|
|
2080
|
+
function RunActivitiesCard({
|
|
2081
|
+
runId,
|
|
2082
|
+
activities,
|
|
2083
|
+
pauseStates,
|
|
2084
|
+
pauseRules,
|
|
2085
|
+
canControl,
|
|
2086
|
+
onChanged
|
|
2087
|
+
}) {
|
|
2088
|
+
const tr = useTr();
|
|
2089
|
+
const [action, setAction] = useState(null);
|
|
2090
|
+
const rules = useMemo(
|
|
2091
|
+
() => new Map(pauseRules.map((rule) => [rule.id, rule])),
|
|
2092
|
+
[pauseRules]
|
|
2093
|
+
);
|
|
2094
|
+
if (activities.length === 0) return null;
|
|
2095
|
+
return /* @__PURE__ */ jsxs(
|
|
2096
|
+
"section",
|
|
2097
|
+
{
|
|
2098
|
+
className: "mb-6 rounded-lg border border-border-secondary bg-background-secondary",
|
|
2099
|
+
"data-testid": "run-activities-card",
|
|
2100
|
+
children: [
|
|
2101
|
+
/* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-center gap-2 border-b border-border-secondary px-4 py-3", children: [
|
|
2102
|
+
/* @__PURE__ */ jsx(
|
|
2103
|
+
Icon,
|
|
2104
|
+
{
|
|
2105
|
+
icon: "mdi:cog-play-outline",
|
|
2106
|
+
width: 18,
|
|
2107
|
+
className: "text-text-secondary"
|
|
2108
|
+
}
|
|
2109
|
+
),
|
|
2110
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr("fluidgrids.runControl.activities.title", "Activities") }),
|
|
2111
|
+
canControl && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex gap-2", children: [
|
|
2112
|
+
/* @__PURE__ */ jsxs(
|
|
2113
|
+
Button,
|
|
2114
|
+
{
|
|
2115
|
+
variant: "outline",
|
|
2116
|
+
size: "sm",
|
|
2117
|
+
onClick: () => setAction({ kind: "pause", activity: null }),
|
|
2118
|
+
"data-testid": "run-activities-pause-all",
|
|
2119
|
+
children: [
|
|
2120
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:pause", className: "mr-1", width: 14 }),
|
|
2121
|
+
tr("fluidgrids.runControl.activities.pauseAll", "Pause all")
|
|
2122
|
+
]
|
|
2123
|
+
}
|
|
2124
|
+
),
|
|
2125
|
+
/* @__PURE__ */ jsxs(
|
|
2126
|
+
Button,
|
|
2127
|
+
{
|
|
2128
|
+
variant: "outline",
|
|
2129
|
+
size: "sm",
|
|
2130
|
+
onClick: () => setAction({ kind: "unpause", activity: null }),
|
|
2131
|
+
"data-testid": "run-activities-unpause-all",
|
|
2132
|
+
children: [
|
|
2133
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:play", className: "mr-1", width: 14 }),
|
|
2134
|
+
tr("fluidgrids.runControl.activities.unpauseAll", "Unpause all")
|
|
2135
|
+
]
|
|
2136
|
+
}
|
|
2137
|
+
)
|
|
2138
|
+
] })
|
|
2139
|
+
] }),
|
|
2140
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border-secondary", children: activities.map((activity) => {
|
|
2141
|
+
const pause = pauseStates.get(activity.activityId);
|
|
2142
|
+
const rule = pause?.ruleId ? rules.get(pause.ruleId) : void 0;
|
|
2143
|
+
return /* @__PURE__ */ jsxs(
|
|
2144
|
+
"li",
|
|
2145
|
+
{
|
|
2146
|
+
className: "flex flex-wrap items-center gap-3 px-4 py-2.5 text-sm",
|
|
2147
|
+
"data-testid": `run-activity-${activity.activityId}`,
|
|
2148
|
+
children: [
|
|
2149
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs text-text-primary", children: activity.activityType }),
|
|
2150
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-text-secondary", children: [
|
|
2151
|
+
activity.state,
|
|
2152
|
+
" \xB7",
|
|
2153
|
+
" ",
|
|
2154
|
+
tr(
|
|
2155
|
+
"fluidgrids.runControl.activities.attempt",
|
|
2156
|
+
"attempt {{attempt}}",
|
|
2157
|
+
{
|
|
2158
|
+
attempt: activity.maxAttempts ? `${activity.attempt}/${activity.maxAttempts}` : activity.attempt
|
|
2159
|
+
}
|
|
2160
|
+
)
|
|
2161
|
+
] }),
|
|
2162
|
+
pause?.paused && (pause.ruleId ? /* @__PURE__ */ jsx("span", { title: rule?.reason ?? pause.reason ?? void 0, children: /* @__PURE__ */ jsx(StatusPill, { tone: "warning", testId: "activity-paused-by-rule", children: tr(
|
|
2163
|
+
"fluidgrids.runControl.activities.pausedByRule",
|
|
2164
|
+
"Paused by rule"
|
|
2165
|
+
) }) }) : /* @__PURE__ */ jsx("span", { title: pause.reason ?? void 0, children: /* @__PURE__ */ jsx(StatusPill, { tone: "warning", testId: "activity-paused", children: tr("fluidgrids.runControl.activities.paused", "Paused") }) })),
|
|
2166
|
+
canControl && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
2167
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
2168
|
+
Button,
|
|
2169
|
+
{
|
|
2170
|
+
variant: "ghost",
|
|
2171
|
+
size: "sm",
|
|
2172
|
+
className: "ml-auto",
|
|
2173
|
+
"aria-label": tr(
|
|
2174
|
+
"fluidgrids.runControl.activities.menu",
|
|
2175
|
+
"Activity actions"
|
|
2176
|
+
),
|
|
2177
|
+
"data-testid": `run-activity-menu-${activity.activityId}`,
|
|
2178
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:dots-vertical", width: 16 })
|
|
2179
|
+
}
|
|
2180
|
+
) }),
|
|
2181
|
+
/* @__PURE__ */ jsxs(
|
|
2182
|
+
DropdownMenuContent,
|
|
2183
|
+
{
|
|
2184
|
+
align: "end",
|
|
2185
|
+
style: RUN_CONTROL_DIALOG_STYLE,
|
|
2186
|
+
children: [
|
|
2187
|
+
pause?.paused ? /* @__PURE__ */ jsx(
|
|
2188
|
+
DropdownMenuItem,
|
|
2189
|
+
{
|
|
2190
|
+
onSelect: () => setAction({ kind: "unpause", activity }),
|
|
2191
|
+
children: tr(
|
|
2192
|
+
"fluidgrids.runControl.activities.unpause",
|
|
2193
|
+
"Unpause"
|
|
2194
|
+
)
|
|
2195
|
+
}
|
|
2196
|
+
) : /* @__PURE__ */ jsx(
|
|
2197
|
+
DropdownMenuItem,
|
|
2198
|
+
{
|
|
2199
|
+
onSelect: () => setAction({ kind: "pause", activity }),
|
|
2200
|
+
children: tr("fluidgrids.runControl.activities.pause", "Pause")
|
|
2201
|
+
}
|
|
2202
|
+
),
|
|
2203
|
+
/* @__PURE__ */ jsx(
|
|
2204
|
+
DropdownMenuItem,
|
|
2205
|
+
{
|
|
2206
|
+
onSelect: () => setAction({ kind: "reset", activity }),
|
|
2207
|
+
children: tr(
|
|
2208
|
+
"fluidgrids.runControl.activities.reset",
|
|
2209
|
+
"Reset attempts"
|
|
2210
|
+
)
|
|
2211
|
+
}
|
|
2212
|
+
),
|
|
2213
|
+
/* @__PURE__ */ jsx(
|
|
2214
|
+
DropdownMenuItem,
|
|
2215
|
+
{
|
|
2216
|
+
onSelect: () => setAction({ kind: "options", activity }),
|
|
2217
|
+
children: tr(
|
|
2218
|
+
"fluidgrids.runControl.activities.options",
|
|
2219
|
+
"Change options\u2026"
|
|
2220
|
+
)
|
|
2221
|
+
}
|
|
2222
|
+
),
|
|
2223
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
2224
|
+
/* @__PURE__ */ jsx(
|
|
2225
|
+
DropdownMenuItem,
|
|
2226
|
+
{
|
|
2227
|
+
onSelect: () => setAction({ kind: "complete", activity }),
|
|
2228
|
+
children: tr(
|
|
2229
|
+
"fluidgrids.runControl.activities.complete",
|
|
2230
|
+
"Complete\u2026"
|
|
2231
|
+
)
|
|
2232
|
+
}
|
|
2233
|
+
),
|
|
2234
|
+
/* @__PURE__ */ jsx(
|
|
2235
|
+
DropdownMenuItem,
|
|
2236
|
+
{
|
|
2237
|
+
onSelect: () => setAction({ kind: "fail", activity }),
|
|
2238
|
+
children: tr("fluidgrids.runControl.activities.fail", "Fail\u2026")
|
|
2239
|
+
}
|
|
2240
|
+
)
|
|
2241
|
+
]
|
|
2242
|
+
}
|
|
2243
|
+
)
|
|
2244
|
+
] })
|
|
2245
|
+
]
|
|
2246
|
+
},
|
|
2247
|
+
activity.activityId
|
|
2248
|
+
);
|
|
2249
|
+
}) }),
|
|
2250
|
+
action && /* @__PURE__ */ jsx(
|
|
2251
|
+
ActivityActionDialog,
|
|
2252
|
+
{
|
|
2253
|
+
runId,
|
|
2254
|
+
action: action.kind,
|
|
2255
|
+
activity: action.activity,
|
|
2256
|
+
onClose: () => setAction(null),
|
|
2257
|
+
onDone: () => {
|
|
2258
|
+
setAction(null);
|
|
2259
|
+
onChanged();
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
)
|
|
2263
|
+
]
|
|
2264
|
+
}
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
function optionalMs(text2) {
|
|
2268
|
+
const value = text2.trim();
|
|
2269
|
+
if (!value) return void 0;
|
|
2270
|
+
const seconds = Number(value);
|
|
2271
|
+
return Number.isFinite(seconds) && seconds > 0 ? Math.round(seconds * 1e3) : null;
|
|
2272
|
+
}
|
|
2273
|
+
function optionalInt(text2) {
|
|
2274
|
+
const value = text2.trim();
|
|
2275
|
+
if (!value) return void 0;
|
|
2276
|
+
return /^\d+$/.test(value) ? Number(value) : null;
|
|
2277
|
+
}
|
|
2278
|
+
function activityOptionsFrom(form) {
|
|
2279
|
+
const maxRetries = optionalInt(form.maxRetries);
|
|
2280
|
+
const retryDelay = optionalMs(form.retryDelaySeconds);
|
|
2281
|
+
const startToClose = optionalMs(form.startToCloseSeconds);
|
|
2282
|
+
const scheduleToClose = optionalMs(form.scheduleToCloseSeconds);
|
|
2283
|
+
const heartbeat = optionalMs(form.heartbeatSeconds);
|
|
2284
|
+
if ([maxRetries, retryDelay, startToClose, scheduleToClose, heartbeat].includes(
|
|
2285
|
+
null
|
|
2286
|
+
))
|
|
2287
|
+
return null;
|
|
2288
|
+
const options = {};
|
|
2289
|
+
const retry = {};
|
|
2290
|
+
if (maxRetries !== void 0) retry.maxRetries = maxRetries;
|
|
2291
|
+
if (retryDelay !== void 0) retry.retryDelay = retryDelay;
|
|
2292
|
+
if (Object.keys(retry).length > 0) options.retry = retry;
|
|
2293
|
+
if (startToClose !== void 0) options.startToCloseTimeoutMs = startToClose;
|
|
2294
|
+
if (scheduleToClose !== void 0)
|
|
2295
|
+
options.scheduleToCloseTimeoutMs = scheduleToClose;
|
|
2296
|
+
if (heartbeat !== void 0) options.heartbeatTimeoutMs = heartbeat;
|
|
2297
|
+
return Object.keys(options).length > 0 ? options : null;
|
|
2298
|
+
}
|
|
2299
|
+
function ActivityActionDialog({
|
|
2300
|
+
runId,
|
|
2301
|
+
action,
|
|
2302
|
+
activity,
|
|
2303
|
+
onClose,
|
|
2304
|
+
onDone
|
|
2305
|
+
}) {
|
|
2306
|
+
const tr = useTr();
|
|
2307
|
+
const [reason, setReason] = useState("");
|
|
2308
|
+
const [resetAttempts, setResetAttempts] = useState(false);
|
|
2309
|
+
const [resetHeartbeat, setResetHeartbeat] = useState(false);
|
|
2310
|
+
const [result, setResult] = useState("");
|
|
2311
|
+
const [failureMessage, setFailureMessage] = useState("");
|
|
2312
|
+
const [nonRetryable, setNonRetryable] = useState(false);
|
|
2313
|
+
const [options, setOptions] = useState({
|
|
2314
|
+
maxRetries: "",
|
|
2315
|
+
retryDelaySeconds: "",
|
|
2316
|
+
startToCloseSeconds: "",
|
|
2317
|
+
scheduleToCloseSeconds: "",
|
|
2318
|
+
heartbeatSeconds: ""
|
|
2319
|
+
});
|
|
2320
|
+
const [busy, setBusy] = useState(false);
|
|
2321
|
+
const [error, setError] = useState(null);
|
|
2322
|
+
const selector = activity ? { activityId: activity.activityId } : { all: true };
|
|
2323
|
+
const parsedResult = parseJsonInput(result);
|
|
2324
|
+
const optionsInput = action === "options" ? activityOptionsFrom(options) : null;
|
|
2325
|
+
const valid = action === "pause" ? reason.trim().length > 0 : action === "complete" ? parsedResult.ok : action === "fail" ? failureMessage.trim().length > 0 : action === "options" ? optionsInput !== null : true;
|
|
2326
|
+
const submit = async () => {
|
|
2327
|
+
if (!valid) return;
|
|
2328
|
+
setBusy(true);
|
|
2329
|
+
setError(null);
|
|
2330
|
+
try {
|
|
2331
|
+
if (action === "pause")
|
|
2332
|
+
await pauseActivities({ runId, selector, reason: reason.trim() });
|
|
2333
|
+
else if (action === "unpause")
|
|
2334
|
+
await unpauseActivities({
|
|
2335
|
+
runId,
|
|
2336
|
+
selector,
|
|
2337
|
+
resetAttempts,
|
|
2338
|
+
resetHeartbeat
|
|
2339
|
+
});
|
|
2340
|
+
else if (action === "reset")
|
|
2341
|
+
await resetActivities({ runId, selector, resetHeartbeat });
|
|
2342
|
+
else if (action === "options" && optionsInput)
|
|
2343
|
+
await updateActivityOptions({ runId, selector, options: optionsInput });
|
|
2344
|
+
else if (action === "complete" && activity && parsedResult.ok)
|
|
2345
|
+
await completeActivityById({
|
|
2346
|
+
runId,
|
|
2347
|
+
activityId: activity.activityId,
|
|
2348
|
+
result: parsedResult.value
|
|
2349
|
+
});
|
|
2350
|
+
else if (action === "fail" && activity)
|
|
2351
|
+
await failActivityById({
|
|
2352
|
+
runId,
|
|
2353
|
+
activityId: activity.activityId,
|
|
2354
|
+
failure: {
|
|
2355
|
+
message: failureMessage.trim(),
|
|
2356
|
+
errorType: "UNKNOWN" /* Unknown */,
|
|
2357
|
+
nonRetryable
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
onDone();
|
|
2361
|
+
} catch (err) {
|
|
2362
|
+
setError(describeOperationError(err));
|
|
2363
|
+
} finally {
|
|
2364
|
+
setBusy(false);
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
const titles = {
|
|
2368
|
+
pause: tr(
|
|
2369
|
+
"fluidgrids.runControl.activities.pauseTitle",
|
|
2370
|
+
"Pause activities"
|
|
2371
|
+
),
|
|
2372
|
+
unpause: tr(
|
|
2373
|
+
"fluidgrids.runControl.activities.unpauseTitle",
|
|
2374
|
+
"Unpause activities"
|
|
2375
|
+
),
|
|
2376
|
+
reset: tr(
|
|
2377
|
+
"fluidgrids.runControl.activities.resetTitle",
|
|
2378
|
+
"Reset the attempt count"
|
|
2379
|
+
),
|
|
2380
|
+
options: tr(
|
|
2381
|
+
"fluidgrids.runControl.activities.optionsTitle",
|
|
2382
|
+
"Change activity options"
|
|
2383
|
+
),
|
|
2384
|
+
complete: tr(
|
|
2385
|
+
"fluidgrids.runControl.activities.completeTitle",
|
|
2386
|
+
"Complete the activity"
|
|
2387
|
+
),
|
|
2388
|
+
fail: tr("fluidgrids.runControl.activities.failTitle", "Fail the activity")
|
|
2389
|
+
};
|
|
2390
|
+
return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(
|
|
2391
|
+
DialogContent,
|
|
2392
|
+
{
|
|
2393
|
+
style: RUN_CONTROL_DIALOG_STYLE,
|
|
2394
|
+
"data-testid": "activity-action-dialog",
|
|
2395
|
+
children: [
|
|
2396
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
2397
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: titles[action] }),
|
|
2398
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: activity ? activity.activityType : tr(
|
|
2399
|
+
"fluidgrids.runControl.activities.everyOpen",
|
|
2400
|
+
"Every open activity of the run"
|
|
2401
|
+
) })
|
|
2402
|
+
] }),
|
|
2403
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
2404
|
+
action === "pause" && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2405
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "activity-pause-reason", children: tr("fluidgrids.runControl.activities.reason", "Reason") }),
|
|
2406
|
+
/* @__PURE__ */ jsx(
|
|
2407
|
+
Input,
|
|
2408
|
+
{
|
|
2409
|
+
id: "activity-pause-reason",
|
|
2410
|
+
value: reason,
|
|
2411
|
+
maxLength: 1e3,
|
|
2412
|
+
onChange: (event) => setReason(event.target.value),
|
|
2413
|
+
"data-testid": "activity-pause-reason"
|
|
2414
|
+
}
|
|
2415
|
+
)
|
|
2416
|
+
] }),
|
|
2417
|
+
action === "unpause" && /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
2418
|
+
tr(
|
|
2419
|
+
"fluidgrids.runControl.activities.resetAttemptsToggle",
|
|
2420
|
+
"Restart the attempt count"
|
|
2421
|
+
),
|
|
2422
|
+
/* @__PURE__ */ jsx(
|
|
2423
|
+
Switch,
|
|
2424
|
+
{
|
|
2425
|
+
checked: resetAttempts,
|
|
2426
|
+
onCheckedChange: setResetAttempts
|
|
2427
|
+
}
|
|
2428
|
+
)
|
|
2429
|
+
] }),
|
|
2430
|
+
(action === "unpause" || action === "reset") && /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
2431
|
+
tr(
|
|
2432
|
+
"fluidgrids.runControl.activities.resetHeartbeat",
|
|
2433
|
+
"Drop the heartbeat details"
|
|
2434
|
+
),
|
|
2435
|
+
/* @__PURE__ */ jsx(
|
|
2436
|
+
Switch,
|
|
2437
|
+
{
|
|
2438
|
+
checked: resetHeartbeat,
|
|
2439
|
+
onCheckedChange: setResetHeartbeat
|
|
2440
|
+
}
|
|
2441
|
+
)
|
|
2442
|
+
] }),
|
|
2443
|
+
action === "options" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
2444
|
+
[
|
|
2445
|
+
[
|
|
2446
|
+
"maxRetries",
|
|
2447
|
+
tr(
|
|
2448
|
+
"fluidgrids.runControl.activities.maxRetries",
|
|
2449
|
+
"Max retries"
|
|
2450
|
+
)
|
|
2451
|
+
],
|
|
2452
|
+
[
|
|
2453
|
+
"retryDelaySeconds",
|
|
2454
|
+
tr(
|
|
2455
|
+
"fluidgrids.runControl.activities.retryDelay",
|
|
2456
|
+
"First retry delay (s)"
|
|
2457
|
+
)
|
|
2458
|
+
],
|
|
2459
|
+
[
|
|
2460
|
+
"startToCloseSeconds",
|
|
2461
|
+
tr(
|
|
2462
|
+
"fluidgrids.runControl.activities.startToClose",
|
|
2463
|
+
"Start-to-close timeout (s)"
|
|
2464
|
+
)
|
|
2465
|
+
],
|
|
2466
|
+
[
|
|
2467
|
+
"scheduleToCloseSeconds",
|
|
2468
|
+
tr(
|
|
2469
|
+
"fluidgrids.runControl.activities.scheduleToClose",
|
|
2470
|
+
"Schedule-to-close timeout (s)"
|
|
2471
|
+
)
|
|
2472
|
+
],
|
|
2473
|
+
[
|
|
2474
|
+
"heartbeatSeconds",
|
|
2475
|
+
tr(
|
|
2476
|
+
"fluidgrids.runControl.activities.heartbeat",
|
|
2477
|
+
"Heartbeat timeout (s)"
|
|
2478
|
+
)
|
|
2479
|
+
]
|
|
2480
|
+
].map(([field, label]) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2481
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `activity-option-${field}`, children: label }),
|
|
2482
|
+
/* @__PURE__ */ jsx(
|
|
2483
|
+
Input,
|
|
2484
|
+
{
|
|
2485
|
+
id: `activity-option-${field}`,
|
|
2486
|
+
inputMode: "decimal",
|
|
2487
|
+
value: options[field],
|
|
2488
|
+
onChange: (event) => setOptions((current) => ({
|
|
2489
|
+
...current,
|
|
2490
|
+
[field]: event.target.value
|
|
2491
|
+
})),
|
|
2492
|
+
"data-testid": `activity-option-${field}`
|
|
2493
|
+
}
|
|
2494
|
+
)
|
|
2495
|
+
] }, field)),
|
|
2496
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary sm:col-span-2", children: tr(
|
|
2497
|
+
"fluidgrids.runControl.activities.optionsHint",
|
|
2498
|
+
"Empty fields stay as they are. The change applies from the next attempt."
|
|
2499
|
+
) })
|
|
2500
|
+
] }),
|
|
2501
|
+
action === "complete" && /* @__PURE__ */ jsx(
|
|
2502
|
+
JsonField,
|
|
2503
|
+
{
|
|
2504
|
+
id: "activity-complete-result",
|
|
2505
|
+
label: tr(
|
|
2506
|
+
"fluidgrids.runControl.activities.result",
|
|
2507
|
+
"Result (JSON, optional)"
|
|
2508
|
+
),
|
|
2509
|
+
value: result,
|
|
2510
|
+
onChange: setResult,
|
|
2511
|
+
error: parsedResult.ok ? null : parsedResult.error,
|
|
2512
|
+
testId: "activity-complete-result"
|
|
2513
|
+
}
|
|
2514
|
+
),
|
|
2515
|
+
action === "fail" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2516
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2517
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "activity-fail-message", children: tr(
|
|
2518
|
+
"fluidgrids.runControl.activities.failureMessage",
|
|
2519
|
+
"Failure message"
|
|
2520
|
+
) }),
|
|
2521
|
+
/* @__PURE__ */ jsx(
|
|
2522
|
+
Input,
|
|
2523
|
+
{
|
|
2524
|
+
id: "activity-fail-message",
|
|
2525
|
+
value: failureMessage,
|
|
2526
|
+
onChange: (event) => setFailureMessage(event.target.value),
|
|
2527
|
+
"data-testid": "activity-fail-message"
|
|
2528
|
+
}
|
|
2529
|
+
)
|
|
2530
|
+
] }),
|
|
2531
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
2532
|
+
tr(
|
|
2533
|
+
"fluidgrids.runControl.activities.nonRetryable",
|
|
2534
|
+
"Do not retry"
|
|
2535
|
+
),
|
|
2536
|
+
/* @__PURE__ */ jsx(
|
|
2537
|
+
Switch,
|
|
2538
|
+
{
|
|
2539
|
+
checked: nonRetryable,
|
|
2540
|
+
onCheckedChange: setNonRetryable
|
|
2541
|
+
}
|
|
2542
|
+
)
|
|
2543
|
+
] })
|
|
2544
|
+
] }),
|
|
2545
|
+
(action === "complete" || action === "fail") && /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
2546
|
+
"fluidgrids.runControl.activities.asyncOnly",
|
|
2547
|
+
"Only an activity that waits for an asynchronous completion can be completed or failed here."
|
|
2548
|
+
) }),
|
|
2549
|
+
/* @__PURE__ */ jsx(FieldError, { message: error, testId: "activity-action-error" })
|
|
2550
|
+
] }),
|
|
2551
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
2552
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: busy, children: tr("fluidgrids.runControl.common.cancel", "Cancel") }),
|
|
2553
|
+
/* @__PURE__ */ jsx(
|
|
2554
|
+
Button,
|
|
2555
|
+
{
|
|
2556
|
+
onClick: () => void submit(),
|
|
2557
|
+
disabled: !valid || busy,
|
|
2558
|
+
"data-testid": "activity-action-confirm",
|
|
2559
|
+
children: busy ? tr("fluidgrids.runControl.common.working", "Working\u2026") : titles[action]
|
|
2560
|
+
}
|
|
2561
|
+
)
|
|
2562
|
+
] })
|
|
2563
|
+
]
|
|
2564
|
+
}
|
|
2565
|
+
) });
|
|
2566
|
+
}
|
|
2567
|
+
var CUSTOM = "__custom__";
|
|
2568
|
+
var REJECT_CONDITIONS = [
|
|
2569
|
+
"NONE" /* None */,
|
|
2570
|
+
"NOT_OPEN" /* NotOpen */,
|
|
2571
|
+
"NOT_COMPLETED_CLEANLY" /* NotCompletedCleanly */
|
|
2572
|
+
];
|
|
2573
|
+
function RunQueryPanel({ runId, handlers }) {
|
|
2574
|
+
const tr = useTr();
|
|
2575
|
+
const [choice, setChoice] = useState(handlers.names[0] ?? CUSTOM);
|
|
2576
|
+
const [customName, setCustomName] = useState("");
|
|
2577
|
+
const [args, setArgs] = useState("");
|
|
2578
|
+
const [rejectCondition, setRejectCondition] = useState("NONE" /* None */);
|
|
2579
|
+
const [running, setRunning] = useState(false);
|
|
2580
|
+
const [error, setError] = useState(null);
|
|
2581
|
+
const [answer, setAnswer] = useState(null);
|
|
2582
|
+
const name = choice === CUSTOM ? customName.trim() : choice;
|
|
2583
|
+
const parsed = parseJsonInput(args);
|
|
2584
|
+
const run = async () => {
|
|
2585
|
+
if (!name || !parsed.ok) return;
|
|
2586
|
+
setRunning(true);
|
|
2587
|
+
setError(null);
|
|
2588
|
+
try {
|
|
2589
|
+
setAnswer(
|
|
2590
|
+
await queryRun({ runId, name, args: parsed.value, rejectCondition })
|
|
2591
|
+
);
|
|
2592
|
+
} catch (err) {
|
|
2593
|
+
setAnswer(null);
|
|
2594
|
+
setError(describeOperationError(err));
|
|
2595
|
+
} finally {
|
|
2596
|
+
setRunning(false);
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
return /* @__PURE__ */ jsxs(
|
|
2600
|
+
"section",
|
|
2601
|
+
{
|
|
2602
|
+
className: "space-y-3 rounded-lg border border-border-secondary bg-background-secondary p-4",
|
|
2603
|
+
"data-testid": "run-query-panel",
|
|
2604
|
+
children: [
|
|
2605
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2606
|
+
/* @__PURE__ */ jsx(
|
|
2607
|
+
Icon,
|
|
2608
|
+
{
|
|
2609
|
+
icon: "mdi:help-circle-outline",
|
|
2610
|
+
width: 18,
|
|
2611
|
+
className: "text-text-secondary"
|
|
2612
|
+
}
|
|
2613
|
+
),
|
|
2614
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr("fluidgrids.runControl.query.title", "Query the run") })
|
|
2615
|
+
] }),
|
|
2616
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
2617
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2618
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "run-query-name", children: tr("fluidgrids.runControl.query.name", "Query") }),
|
|
2619
|
+
handlers.names.length > 0 && /* @__PURE__ */ jsxs(
|
|
2620
|
+
NativeSelect,
|
|
2621
|
+
{
|
|
2622
|
+
id: "run-query-name",
|
|
2623
|
+
value: choice,
|
|
2624
|
+
onChange: setChoice,
|
|
2625
|
+
testId: "run-query-name",
|
|
2626
|
+
children: [
|
|
2627
|
+
handlers.names.map((handler) => /* @__PURE__ */ jsx("option", { value: handler, children: handler }, handler)),
|
|
2628
|
+
/* @__PURE__ */ jsx("option", { value: CUSTOM, children: tr("fluidgrids.runControl.query.other", "Another name\u2026") })
|
|
2629
|
+
]
|
|
2630
|
+
}
|
|
2631
|
+
),
|
|
2632
|
+
(choice === CUSTOM || handlers.names.length === 0) && /* @__PURE__ */ jsx(
|
|
2633
|
+
Input,
|
|
2634
|
+
{
|
|
2635
|
+
id: handlers.names.length === 0 ? "run-query-name" : void 0,
|
|
2636
|
+
value: customName,
|
|
2637
|
+
onChange: (event) => setCustomName(event.target.value),
|
|
2638
|
+
placeholder: tr(
|
|
2639
|
+
"fluidgrids.runControl.query.namePlaceholder",
|
|
2640
|
+
"query-name"
|
|
2641
|
+
),
|
|
2642
|
+
"data-testid": "run-query-custom-name"
|
|
2643
|
+
}
|
|
2644
|
+
)
|
|
2645
|
+
] }),
|
|
2646
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2647
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "run-query-reject", children: tr("fluidgrids.runControl.query.rejectCondition", "Refuse when") }),
|
|
2648
|
+
/* @__PURE__ */ jsxs(
|
|
2649
|
+
NativeSelect,
|
|
2650
|
+
{
|
|
2651
|
+
id: "run-query-reject",
|
|
2652
|
+
value: rejectCondition,
|
|
2653
|
+
onChange: (value) => setRejectCondition(
|
|
2654
|
+
REJECT_CONDITIONS.find((item) => item === value) ?? "NONE" /* None */
|
|
2655
|
+
),
|
|
2656
|
+
testId: "run-query-reject",
|
|
2657
|
+
children: [
|
|
2658
|
+
/* @__PURE__ */ jsx("option", { value: "NONE" /* None */, children: tr("fluidgrids.runControl.query.rejectNone", "Never") }),
|
|
2659
|
+
/* @__PURE__ */ jsx("option", { value: "NOT_OPEN" /* NotOpen */, children: tr(
|
|
2660
|
+
"fluidgrids.runControl.query.rejectNotOpen",
|
|
2661
|
+
"The run has ended"
|
|
2662
|
+
) }),
|
|
2663
|
+
/* @__PURE__ */ jsx("option", { value: "NOT_COMPLETED_CLEANLY" /* NotCompletedCleanly */, children: tr(
|
|
2664
|
+
"fluidgrids.runControl.query.rejectNotClean",
|
|
2665
|
+
"The run did not complete cleanly"
|
|
2666
|
+
) })
|
|
2667
|
+
]
|
|
2668
|
+
}
|
|
2669
|
+
)
|
|
2670
|
+
] })
|
|
2671
|
+
] }),
|
|
2672
|
+
/* @__PURE__ */ jsx(
|
|
2673
|
+
JsonField,
|
|
2674
|
+
{
|
|
2675
|
+
id: "run-query-args",
|
|
2676
|
+
label: tr(
|
|
2677
|
+
"fluidgrids.runControl.query.args",
|
|
2678
|
+
"Arguments (JSON, optional)"
|
|
2679
|
+
),
|
|
2680
|
+
value: args,
|
|
2681
|
+
onChange: setArgs,
|
|
2682
|
+
rows: 2,
|
|
2683
|
+
error: parsed.ok ? null : parsed.error,
|
|
2684
|
+
testId: "run-query-args"
|
|
2685
|
+
}
|
|
2686
|
+
),
|
|
2687
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
|
|
2688
|
+
Button,
|
|
2689
|
+
{
|
|
2690
|
+
size: "sm",
|
|
2691
|
+
onClick: () => void run(),
|
|
2692
|
+
disabled: !name || !parsed.ok || running,
|
|
2693
|
+
"data-testid": "run-query-send",
|
|
2694
|
+
children: [
|
|
2695
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:play", className: "mr-1", width: 16 }),
|
|
2696
|
+
running ? tr("fluidgrids.runControl.query.running", "Asking\u2026") : tr("fluidgrids.runControl.query.run", "Run query")
|
|
2697
|
+
]
|
|
2698
|
+
}
|
|
2699
|
+
) }),
|
|
2700
|
+
/* @__PURE__ */ jsx(FieldError, { message: error, testId: "run-query-error" }),
|
|
2701
|
+
answer && /* @__PURE__ */ jsx("div", { className: "space-y-2", "data-testid": "run-query-answer", children: answer.rejected ? /* @__PURE__ */ jsxs(StatusPill, { tone: "warning", testId: "run-query-rejected", children: [
|
|
2702
|
+
tr("fluidgrids.runControl.query.rejected", "Refused"),
|
|
2703
|
+
answer.error ? `: ${answer.error}` : ""
|
|
2704
|
+
] }) : answer.error ? /* @__PURE__ */ jsx(FieldError, { message: answer.error }) : /* @__PURE__ */ jsx(JsonBlock, { value: answer.value, testId: "run-query-value" }) })
|
|
2705
|
+
]
|
|
2706
|
+
}
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
function entriesOf(value) {
|
|
2710
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2711
|
+
return [];
|
|
2712
|
+
return Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
|
|
2713
|
+
}
|
|
2714
|
+
function display(value) {
|
|
2715
|
+
if (Array.isArray(value)) return value.map(String).join(", ");
|
|
2716
|
+
if (typeof value === "string") return value;
|
|
2717
|
+
return JSON.stringify(value);
|
|
2718
|
+
}
|
|
2719
|
+
function RunSearchAttributesCard({
|
|
2720
|
+
runId,
|
|
2721
|
+
attributes,
|
|
2722
|
+
canEdit,
|
|
2723
|
+
onChanged
|
|
2724
|
+
}) {
|
|
2725
|
+
const tr = useTr();
|
|
2726
|
+
const entries = entriesOf(attributes);
|
|
2727
|
+
const [editing, setEditing] = useState(false);
|
|
2728
|
+
const [text2, setText] = useState("");
|
|
2729
|
+
const [busy, setBusy] = useState(false);
|
|
2730
|
+
const [error, setError] = useState(null);
|
|
2731
|
+
const parsed = parseJsonObjectInput(text2);
|
|
2732
|
+
if (entries.length === 0 && !canEdit) return null;
|
|
2733
|
+
const save = async () => {
|
|
2734
|
+
if (!parsed.ok) return;
|
|
2735
|
+
setBusy(true);
|
|
2736
|
+
setError(null);
|
|
2737
|
+
try {
|
|
2738
|
+
await upsertRunSearchAttributes(runId, parsed.value);
|
|
2739
|
+
toast.success(
|
|
2740
|
+
tr(
|
|
2741
|
+
"fluidgrids.runControl.searchAttributes.saved",
|
|
2742
|
+
"Search attributes saved"
|
|
2743
|
+
)
|
|
2744
|
+
);
|
|
2745
|
+
setEditing(false);
|
|
2746
|
+
onChanged();
|
|
2747
|
+
} catch (err) {
|
|
2748
|
+
setError(describeOperationError(err));
|
|
2749
|
+
} finally {
|
|
2750
|
+
setBusy(false);
|
|
2751
|
+
}
|
|
2752
|
+
};
|
|
2753
|
+
return /* @__PURE__ */ jsxs(
|
|
2754
|
+
"section",
|
|
2755
|
+
{
|
|
2756
|
+
className: "mb-6 rounded-lg border border-border-secondary bg-background-secondary p-4",
|
|
2757
|
+
"data-testid": "run-search-attributes",
|
|
2758
|
+
children: [
|
|
2759
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
2760
|
+
/* @__PURE__ */ jsx(
|
|
2761
|
+
Icon,
|
|
2762
|
+
{
|
|
2763
|
+
icon: "mdi:tag-search-outline",
|
|
2764
|
+
width: 18,
|
|
2765
|
+
className: "text-text-secondary"
|
|
2766
|
+
}
|
|
2767
|
+
),
|
|
2768
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr(
|
|
2769
|
+
"fluidgrids.runControl.searchAttributes.title",
|
|
2770
|
+
"Search attributes"
|
|
2771
|
+
) }),
|
|
2772
|
+
canEdit && !editing && /* @__PURE__ */ jsx(
|
|
2773
|
+
Button,
|
|
2774
|
+
{
|
|
2775
|
+
size: "sm",
|
|
2776
|
+
variant: "outline",
|
|
2777
|
+
className: "ml-auto",
|
|
2778
|
+
onClick: () => {
|
|
2779
|
+
setText("");
|
|
2780
|
+
setEditing(true);
|
|
2781
|
+
},
|
|
2782
|
+
"data-testid": "run-search-attributes-edit",
|
|
2783
|
+
children: tr(
|
|
2784
|
+
"fluidgrids.runControl.searchAttributes.edit",
|
|
2785
|
+
"Set attributes"
|
|
2786
|
+
)
|
|
2787
|
+
}
|
|
2788
|
+
)
|
|
2789
|
+
] }),
|
|
2790
|
+
entries.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: tr(
|
|
2791
|
+
"fluidgrids.runControl.searchAttributes.none",
|
|
2792
|
+
"This run has no search attributes."
|
|
2793
|
+
) }) : /* @__PURE__ */ jsx("dl", { className: "grid gap-x-4 gap-y-1 text-sm sm:grid-cols-[max-content_1fr]", children: entries.map(([name, value]) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
|
|
2794
|
+
/* @__PURE__ */ jsx("dt", { className: "font-mono text-xs text-text-secondary", children: name }),
|
|
2795
|
+
/* @__PURE__ */ jsx(
|
|
2796
|
+
"dd",
|
|
2797
|
+
{
|
|
2798
|
+
className: "break-words text-text-primary",
|
|
2799
|
+
"data-testid": `run-search-attribute-${name}`,
|
|
2800
|
+
children: display(value)
|
|
2801
|
+
}
|
|
2802
|
+
)
|
|
2803
|
+
] }, name)) }),
|
|
2804
|
+
editing && /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-2", children: [
|
|
2805
|
+
/* @__PURE__ */ jsx(
|
|
2806
|
+
JsonField,
|
|
2807
|
+
{
|
|
2808
|
+
id: "run-search-attributes-input",
|
|
2809
|
+
label: tr(
|
|
2810
|
+
"fluidgrids.runControl.searchAttributes.input",
|
|
2811
|
+
"Attributes to set (JSON; null removes one)"
|
|
2812
|
+
),
|
|
2813
|
+
value: text2,
|
|
2814
|
+
onChange: setText,
|
|
2815
|
+
placeholder: '{"customerId": "c-42", "priorityTier": null}',
|
|
2816
|
+
error: text2.trim() && !parsed.ok ? parsed.error : null,
|
|
2817
|
+
testId: "run-search-attributes-input"
|
|
2818
|
+
}
|
|
2819
|
+
),
|
|
2820
|
+
/* @__PURE__ */ jsx(FieldError, { message: error, testId: "run-search-attributes-error" }),
|
|
2821
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
2822
|
+
/* @__PURE__ */ jsx(
|
|
2823
|
+
Button,
|
|
2824
|
+
{
|
|
2825
|
+
size: "sm",
|
|
2826
|
+
variant: "outline",
|
|
2827
|
+
onClick: () => setEditing(false),
|
|
2828
|
+
disabled: busy,
|
|
2829
|
+
children: tr("fluidgrids.runControl.common.cancel", "Cancel")
|
|
2830
|
+
}
|
|
2831
|
+
),
|
|
2832
|
+
/* @__PURE__ */ jsx(
|
|
2833
|
+
Button,
|
|
2834
|
+
{
|
|
2835
|
+
size: "sm",
|
|
2836
|
+
onClick: () => void save(),
|
|
2837
|
+
disabled: busy || !text2.trim() || !parsed.ok,
|
|
2838
|
+
"data-testid": "run-search-attributes-save",
|
|
2839
|
+
children: tr("fluidgrids.runControl.common.save", "Save")
|
|
2840
|
+
}
|
|
2841
|
+
)
|
|
2842
|
+
] })
|
|
2843
|
+
] })
|
|
2844
|
+
]
|
|
2845
|
+
}
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
var UPDATE_POLL_INTERVAL_MS = 1e3;
|
|
2849
|
+
var UPDATE_POLL_LIMIT = 30;
|
|
2850
|
+
var CUSTOM2 = "__custom__";
|
|
2851
|
+
function statusTone(status) {
|
|
2852
|
+
if (status === "COMPLETED") return "success";
|
|
2853
|
+
if (status === "REJECTED") return "error";
|
|
2854
|
+
return "info";
|
|
2855
|
+
}
|
|
2856
|
+
function RunUpdateDialog(props) {
|
|
2857
|
+
return /* @__PURE__ */ jsx(Dialog, { open: props.open, onOpenChange: props.onOpenChange, children: props.open && /* @__PURE__ */ jsx(RunUpdateForm, { ...props }) });
|
|
2858
|
+
}
|
|
2859
|
+
function RunUpdateForm({
|
|
2860
|
+
runId,
|
|
2861
|
+
handlers,
|
|
2862
|
+
open,
|
|
2863
|
+
onOpenChange,
|
|
2864
|
+
onDone
|
|
2865
|
+
}) {
|
|
2866
|
+
const tr = useTr();
|
|
2867
|
+
const [choice, setChoice] = useState(handlers.names[0] ?? CUSTOM2);
|
|
2868
|
+
const [customName, setCustomName] = useState("");
|
|
2869
|
+
const [args, setArgs] = useState("");
|
|
2870
|
+
const [waitFor, setWaitFor] = useState(
|
|
2871
|
+
"COMPLETED" /* Completed */
|
|
2872
|
+
);
|
|
2873
|
+
const [updateId] = useState(() => newRunControlId("ui-update"));
|
|
2874
|
+
const [sending, setSending] = useState(false);
|
|
2875
|
+
const [error, setError] = useState(null);
|
|
2876
|
+
const [result, setResult] = useState(null);
|
|
2877
|
+
const polls = useRef(0);
|
|
2878
|
+
useEffect(() => {
|
|
2879
|
+
if (!open || !result || isFinalUpdateStatus(result.status)) return;
|
|
2880
|
+
if (polls.current >= UPDATE_POLL_LIMIT) return;
|
|
2881
|
+
const timer = setTimeout(() => {
|
|
2882
|
+
polls.current += 1;
|
|
2883
|
+
fetchRunUpdate(runId, result.updateId).then((next) => {
|
|
2884
|
+
setResult(next);
|
|
2885
|
+
if (isFinalUpdateStatus(next.status)) onDone?.(next);
|
|
2886
|
+
}).catch((err) => setError(describeOperationError(err)));
|
|
2887
|
+
}, UPDATE_POLL_INTERVAL_MS);
|
|
2888
|
+
return () => clearTimeout(timer);
|
|
2889
|
+
}, [open, result, runId, onDone]);
|
|
2890
|
+
const name = choice === CUSTOM2 ? customName.trim() : choice;
|
|
2891
|
+
const parsed = parseJsonInput(args);
|
|
2892
|
+
const canSend = !!name && parsed.ok && !sending;
|
|
2893
|
+
const send = async () => {
|
|
2894
|
+
if (!canSend || !parsed.ok) return;
|
|
2895
|
+
setSending(true);
|
|
2896
|
+
setError(null);
|
|
2897
|
+
try {
|
|
2898
|
+
const answer = await sendRunUpdate({
|
|
2899
|
+
runId,
|
|
2900
|
+
name,
|
|
2901
|
+
args: parsed.value,
|
|
2902
|
+
updateId,
|
|
2903
|
+
waitFor
|
|
2904
|
+
});
|
|
2905
|
+
polls.current = 0;
|
|
2906
|
+
setResult(answer);
|
|
2907
|
+
if (updateReached(answer.status, waitFor)) onDone?.(answer);
|
|
2908
|
+
} catch (err) {
|
|
2909
|
+
setError(describeOperationError(err));
|
|
2910
|
+
} finally {
|
|
2911
|
+
setSending(false);
|
|
2912
|
+
}
|
|
2913
|
+
};
|
|
2914
|
+
return /* @__PURE__ */ jsxs(
|
|
2915
|
+
DialogContent,
|
|
2916
|
+
{
|
|
2917
|
+
style: RUN_CONTROL_DIALOG_STYLE,
|
|
2918
|
+
className: "max-w-lg",
|
|
2919
|
+
"data-testid": "run-update-dialog",
|
|
2920
|
+
children: [
|
|
2921
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
2922
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runControl.update.title", "Send an update") }),
|
|
2923
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: tr(
|
|
2924
|
+
"fluidgrids.runControl.update.description",
|
|
2925
|
+
"The run's update handler validates and applies it. A rejected update leaves no history."
|
|
2926
|
+
) })
|
|
2927
|
+
] }),
|
|
2928
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2929
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2930
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "run-update-name", children: tr("fluidgrids.runControl.update.name", "Update") }),
|
|
2931
|
+
handlers.names.length > 0 && /* @__PURE__ */ jsxs(
|
|
2932
|
+
NativeSelect,
|
|
2933
|
+
{
|
|
2934
|
+
id: "run-update-name",
|
|
2935
|
+
value: choice,
|
|
2936
|
+
onChange: setChoice,
|
|
2937
|
+
testId: "run-update-name",
|
|
2938
|
+
children: [
|
|
2939
|
+
handlers.names.map((handler) => /* @__PURE__ */ jsx("option", { value: handler, children: handler }, handler)),
|
|
2940
|
+
handlers.openNames && /* @__PURE__ */ jsx("option", { value: CUSTOM2, children: tr("fluidgrids.runControl.update.other", "Another name\u2026") })
|
|
2941
|
+
]
|
|
2942
|
+
}
|
|
2943
|
+
),
|
|
2944
|
+
(choice === CUSTOM2 || handlers.names.length === 0) && /* @__PURE__ */ jsx(
|
|
2945
|
+
Input,
|
|
2946
|
+
{
|
|
2947
|
+
id: handlers.names.length === 0 ? "run-update-name" : void 0,
|
|
2948
|
+
value: customName,
|
|
2949
|
+
onChange: (event) => setCustomName(event.target.value),
|
|
2950
|
+
placeholder: tr(
|
|
2951
|
+
"fluidgrids.runControl.update.namePlaceholder",
|
|
2952
|
+
"update-name"
|
|
2953
|
+
),
|
|
2954
|
+
"data-testid": "run-update-custom-name"
|
|
2955
|
+
}
|
|
2956
|
+
)
|
|
2957
|
+
] }),
|
|
2958
|
+
/* @__PURE__ */ jsx(
|
|
2959
|
+
JsonField,
|
|
2960
|
+
{
|
|
2961
|
+
id: "run-update-args",
|
|
2962
|
+
label: tr("fluidgrids.runControl.update.args", "Arguments (JSON)"),
|
|
2963
|
+
value: args,
|
|
2964
|
+
onChange: setArgs,
|
|
2965
|
+
placeholder: '{"quantity": 2}',
|
|
2966
|
+
error: parsed.ok ? null : parsed.error,
|
|
2967
|
+
testId: "run-update-args"
|
|
2968
|
+
}
|
|
2969
|
+
),
|
|
2970
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2971
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "run-update-wait", children: tr("fluidgrids.runControl.update.waitFor", "Wait until") }),
|
|
2972
|
+
/* @__PURE__ */ jsxs(
|
|
2973
|
+
NativeSelect,
|
|
2974
|
+
{
|
|
2975
|
+
id: "run-update-wait",
|
|
2976
|
+
value: waitFor,
|
|
2977
|
+
onChange: (value) => setWaitFor(
|
|
2978
|
+
value === "ACCEPTED" /* Accepted */ ? "ACCEPTED" /* Accepted */ : "COMPLETED" /* Completed */
|
|
2979
|
+
),
|
|
2980
|
+
testId: "run-update-wait",
|
|
2981
|
+
children: [
|
|
2982
|
+
/* @__PURE__ */ jsx("option", { value: "COMPLETED" /* Completed */, children: tr("fluidgrids.runControl.update.waitCompleted", "it completes") }),
|
|
2983
|
+
/* @__PURE__ */ jsx("option", { value: "ACCEPTED" /* Accepted */, children: tr(
|
|
2984
|
+
"fluidgrids.runControl.update.waitAccepted",
|
|
2985
|
+
"it is accepted"
|
|
2986
|
+
) })
|
|
2987
|
+
]
|
|
2988
|
+
}
|
|
2989
|
+
)
|
|
2990
|
+
] }),
|
|
2991
|
+
/* @__PURE__ */ jsx(FieldError, { message: error, testId: "run-update-error" }),
|
|
2992
|
+
result && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "run-update-result", children: [
|
|
2993
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
|
|
2994
|
+
/* @__PURE__ */ jsx(
|
|
2995
|
+
StatusPill,
|
|
2996
|
+
{
|
|
2997
|
+
tone: statusTone(result.status),
|
|
2998
|
+
testId: "run-update-status",
|
|
2999
|
+
children: result.status
|
|
3000
|
+
}
|
|
3001
|
+
),
|
|
3002
|
+
/* @__PURE__ */ jsx("code", { className: "truncate font-mono text-xs text-text-secondary", children: result.updateId })
|
|
3003
|
+
] }),
|
|
3004
|
+
result.status === "COMPLETED" && result.result !== void 0 && /* @__PURE__ */ jsx(JsonBlock, { value: result.result, testId: "run-update-value" }),
|
|
3005
|
+
result.failure && /* @__PURE__ */ jsx(JsonBlock, { value: result.failure, testId: "run-update-failure" })
|
|
3006
|
+
] })
|
|
3007
|
+
] }),
|
|
3008
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
3009
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: tr("fluidgrids.runControl.common.close", "Close") }),
|
|
3010
|
+
/* @__PURE__ */ jsx(
|
|
3011
|
+
Button,
|
|
3012
|
+
{
|
|
3013
|
+
onClick: () => void send(),
|
|
3014
|
+
disabled: !canSend,
|
|
3015
|
+
"data-testid": "run-update-send",
|
|
3016
|
+
children: sending ? tr("fluidgrids.runControl.update.sending", "Sending\u2026") : tr("fluidgrids.runControl.update.send", "Send update")
|
|
3017
|
+
}
|
|
3018
|
+
)
|
|
3019
|
+
] })
|
|
3020
|
+
]
|
|
3021
|
+
}
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
function ResetRunDialog(props) {
|
|
3025
|
+
return /* @__PURE__ */ jsx(AlertDialog, { open: props.point !== null, onOpenChange: props.onOpenChange, children: props.point && /* @__PURE__ */ jsx(ResetRunForm, { ...props, point: props.point }) });
|
|
3026
|
+
}
|
|
3027
|
+
function ResetRunForm({
|
|
3028
|
+
runId,
|
|
3029
|
+
point,
|
|
3030
|
+
onOpenChange,
|
|
3031
|
+
onReset
|
|
3032
|
+
}) {
|
|
3033
|
+
const tr = useTr();
|
|
3034
|
+
const [reason, setReason] = useState("");
|
|
3035
|
+
const [reapplySignals, setReapplySignals] = useState(true);
|
|
3036
|
+
const [reapplyUpdates, setReapplyUpdates] = useState(true);
|
|
3037
|
+
const [busy, setBusy] = useState(false);
|
|
3038
|
+
const [error, setError] = useState(null);
|
|
3039
|
+
const confirm = async () => {
|
|
3040
|
+
setBusy(true);
|
|
3041
|
+
setError(null);
|
|
3042
|
+
try {
|
|
3043
|
+
const fork = point.kind === "lastClean" ? await resetRunToLastCleanTask({ runId, reason }) : await resetRun({
|
|
3044
|
+
runId,
|
|
3045
|
+
to: { seq: point.seq },
|
|
3046
|
+
reason,
|
|
3047
|
+
reapplySignals,
|
|
3048
|
+
reapplyUpdates
|
|
3049
|
+
});
|
|
3050
|
+
onOpenChange(false);
|
|
3051
|
+
onReset(fork.id);
|
|
3052
|
+
} catch (err) {
|
|
3053
|
+
setError(describeOperationError(err));
|
|
3054
|
+
} finally {
|
|
3055
|
+
setBusy(false);
|
|
3056
|
+
}
|
|
3057
|
+
};
|
|
3058
|
+
return /* @__PURE__ */ jsxs(
|
|
3059
|
+
AlertDialogContent,
|
|
3060
|
+
{
|
|
3061
|
+
style: RUN_CONTROL_DIALOG_STYLE,
|
|
3062
|
+
"data-testid": "run-reset-dialog",
|
|
3063
|
+
children: [
|
|
3064
|
+
/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
|
|
3065
|
+
/* @__PURE__ */ jsx(AlertDialogTitle, { children: point.kind === "lastClean" ? tr(
|
|
3066
|
+
"fluidgrids.runControl.reset.lastCleanTitle",
|
|
3067
|
+
"Reset to the last clean task?"
|
|
3068
|
+
) : tr(
|
|
3069
|
+
"fluidgrids.runControl.reset.hereTitle",
|
|
3070
|
+
"Reset at event #{{seq}}?",
|
|
3071
|
+
{
|
|
3072
|
+
seq: point.seq
|
|
3073
|
+
}
|
|
3074
|
+
) }),
|
|
3075
|
+
/* @__PURE__ */ jsx(AlertDialogDescription, { children: tr(
|
|
3076
|
+
"fluidgrids.runControl.reset.description",
|
|
3077
|
+
"A new run with the same workflow id replays this run's history up to that point and continues from there. This run is terminated with the reason RESET."
|
|
3078
|
+
) })
|
|
3079
|
+
] }),
|
|
3080
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3081
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
3082
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "run-reset-reason", children: tr("fluidgrids.runControl.reset.reason", "Reason (optional)") }),
|
|
3083
|
+
/* @__PURE__ */ jsx(
|
|
3084
|
+
Input,
|
|
3085
|
+
{
|
|
3086
|
+
id: "run-reset-reason",
|
|
3087
|
+
value: reason,
|
|
3088
|
+
maxLength: 1e3,
|
|
3089
|
+
onChange: (event) => setReason(event.target.value),
|
|
3090
|
+
"data-testid": "run-reset-reason"
|
|
3091
|
+
}
|
|
3092
|
+
)
|
|
3093
|
+
] }),
|
|
3094
|
+
point.kind === "seq" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3095
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm text-text-primary", children: [
|
|
3096
|
+
tr(
|
|
3097
|
+
"fluidgrids.runControl.reset.reapplySignals",
|
|
3098
|
+
"Re-apply the signals received after it"
|
|
3099
|
+
),
|
|
3100
|
+
/* @__PURE__ */ jsx(
|
|
3101
|
+
Switch,
|
|
3102
|
+
{
|
|
3103
|
+
checked: reapplySignals,
|
|
3104
|
+
onCheckedChange: setReapplySignals,
|
|
3105
|
+
"data-testid": "run-reset-reapply-signals"
|
|
3106
|
+
}
|
|
3107
|
+
)
|
|
3108
|
+
] }),
|
|
3109
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm text-text-primary", children: [
|
|
3110
|
+
tr(
|
|
3111
|
+
"fluidgrids.runControl.reset.reapplyUpdates",
|
|
3112
|
+
"Re-apply the updates received after it"
|
|
3113
|
+
),
|
|
3114
|
+
/* @__PURE__ */ jsx(
|
|
3115
|
+
Switch,
|
|
3116
|
+
{
|
|
3117
|
+
checked: reapplyUpdates,
|
|
3118
|
+
onCheckedChange: setReapplyUpdates,
|
|
3119
|
+
"data-testid": "run-reset-reapply-updates"
|
|
3120
|
+
}
|
|
3121
|
+
)
|
|
3122
|
+
] })
|
|
3123
|
+
] }),
|
|
3124
|
+
/* @__PURE__ */ jsx(FieldError, { message: error, testId: "run-reset-error" })
|
|
3125
|
+
] }),
|
|
3126
|
+
/* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
|
|
3127
|
+
/* @__PURE__ */ jsx(AlertDialogCancel, { disabled: busy, children: tr("fluidgrids.runControl.common.cancel", "Cancel") }),
|
|
3128
|
+
/* @__PURE__ */ jsx(
|
|
3129
|
+
AlertDialogAction,
|
|
3130
|
+
{
|
|
3131
|
+
onClick: (event) => {
|
|
3132
|
+
event.preventDefault();
|
|
3133
|
+
void confirm();
|
|
3134
|
+
},
|
|
3135
|
+
disabled: busy,
|
|
3136
|
+
className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
3137
|
+
"data-testid": "run-reset-confirm",
|
|
3138
|
+
children: busy ? tr("fluidgrids.runControl.reset.resetting", "Resetting\u2026") : tr("fluidgrids.runControl.reset.confirm", "Reset run")
|
|
3139
|
+
}
|
|
3140
|
+
)
|
|
3141
|
+
] })
|
|
3142
|
+
]
|
|
3143
|
+
}
|
|
3144
|
+
);
|
|
3145
|
+
}
|
|
3146
|
+
var SIGNAL_TOKEN_TTLS = [
|
|
3147
|
+
{ seconds: 3600, key: "hour" },
|
|
3148
|
+
{ seconds: 86400, key: "day" },
|
|
3149
|
+
{ seconds: 604800, key: "week" },
|
|
3150
|
+
{ seconds: 2592e3, key: "month" },
|
|
3151
|
+
{ seconds: 7776e3, key: "quarter" }
|
|
3152
|
+
];
|
|
3153
|
+
function signalTokenTtlLabel(tr, key) {
|
|
3154
|
+
switch (key) {
|
|
3155
|
+
case "hour":
|
|
3156
|
+
return tr("fluidgrids.runControl.tokens.ttlOption.hour", "1 hour");
|
|
3157
|
+
case "day":
|
|
3158
|
+
return tr("fluidgrids.runControl.tokens.ttlOption.day", "1 day");
|
|
3159
|
+
case "week":
|
|
3160
|
+
return tr("fluidgrids.runControl.tokens.ttlOption.week", "7 days");
|
|
3161
|
+
case "month":
|
|
3162
|
+
return tr("fluidgrids.runControl.tokens.ttlOption.month", "30 days");
|
|
3163
|
+
case "quarter":
|
|
3164
|
+
return tr("fluidgrids.runControl.tokens.ttlOption.quarter", "90 days");
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
function signalTokenStateLabel(tr, state) {
|
|
3168
|
+
switch (state) {
|
|
3169
|
+
case "active":
|
|
3170
|
+
return tr("fluidgrids.runControl.tokens.state.active", "Active");
|
|
3171
|
+
case "spent":
|
|
3172
|
+
return tr("fluidgrids.runControl.tokens.state.spent", "Spent");
|
|
3173
|
+
case "expired":
|
|
3174
|
+
return tr("fluidgrids.runControl.tokens.state.expired", "Expired");
|
|
3175
|
+
case "revoked":
|
|
3176
|
+
return tr("fluidgrids.runControl.tokens.state.revoked", "Revoked");
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
function parseIpAllowlist(text2) {
|
|
3180
|
+
const values = text2.split(/[\s,]+/).map((item) => item.trim()).filter(Boolean);
|
|
3181
|
+
const pattern = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\d{1,2}))?$/;
|
|
3182
|
+
for (const value of values) {
|
|
3183
|
+
const match = pattern.exec(value);
|
|
3184
|
+
const octets = match ? match.slice(1, 5).map(Number) : [];
|
|
3185
|
+
const prefix = match?.[6] === void 0 ? 32 : Number(match[6]);
|
|
3186
|
+
if (!match || octets.some((octet) => octet > 255) || prefix > 32)
|
|
3187
|
+
return { ok: false, bad: value };
|
|
3188
|
+
}
|
|
3189
|
+
return { ok: true, values: values.slice(0, 50) };
|
|
3190
|
+
}
|
|
3191
|
+
function tokenState(token) {
|
|
3192
|
+
if (token.revokedAt) return "revoked";
|
|
3193
|
+
if (!token.reusable && token.usedAt) return "spent";
|
|
3194
|
+
if (new Date(token.expiresAt).getTime() <= Date.now()) return "expired";
|
|
3195
|
+
return "active";
|
|
3196
|
+
}
|
|
3197
|
+
function SignalTokensPanel({
|
|
3198
|
+
runId,
|
|
3199
|
+
signalNames,
|
|
3200
|
+
canCreate
|
|
3201
|
+
}) {
|
|
3202
|
+
const tr = useTr();
|
|
3203
|
+
const tokens = useResource(
|
|
3204
|
+
`signal-tokens:${runId}`,
|
|
3205
|
+
true,
|
|
3206
|
+
() => fetchSignalTokens(runId)
|
|
3207
|
+
);
|
|
3208
|
+
const [creating, setCreating] = useState(false);
|
|
3209
|
+
const [created, setCreated] = useState(
|
|
3210
|
+
null
|
|
3211
|
+
);
|
|
3212
|
+
const revoke = async (id) => {
|
|
3213
|
+
try {
|
|
3214
|
+
await revokeSignalToken(id);
|
|
3215
|
+
toast.success(
|
|
3216
|
+
tr("fluidgrids.runControl.tokens.revoked", "Token revoked")
|
|
3217
|
+
);
|
|
3218
|
+
await tokens.refetch();
|
|
3219
|
+
} catch (err) {
|
|
3220
|
+
toast.error(describeOperationError(err));
|
|
3221
|
+
}
|
|
3222
|
+
};
|
|
3223
|
+
return /* @__PURE__ */ jsxs(
|
|
3224
|
+
"section",
|
|
3225
|
+
{
|
|
3226
|
+
className: "space-y-3 rounded-lg border border-border-secondary bg-background-secondary p-4",
|
|
3227
|
+
"data-testid": "signal-tokens-panel",
|
|
3228
|
+
children: [
|
|
3229
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3230
|
+
/* @__PURE__ */ jsx(
|
|
3231
|
+
Icon,
|
|
3232
|
+
{
|
|
3233
|
+
icon: "mdi:key-chain-variant",
|
|
3234
|
+
width: 18,
|
|
3235
|
+
className: "text-text-secondary"
|
|
3236
|
+
}
|
|
3237
|
+
),
|
|
3238
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-text-primary", children: tr("fluidgrids.runControl.tokens.title", "Signal tokens") }),
|
|
3239
|
+
canCreate && /* @__PURE__ */ jsxs(
|
|
3240
|
+
Button,
|
|
3241
|
+
{
|
|
3242
|
+
size: "sm",
|
|
3243
|
+
variant: "outline",
|
|
3244
|
+
className: "ml-auto",
|
|
3245
|
+
onClick: () => setCreating(true),
|
|
3246
|
+
"data-testid": "signal-token-create",
|
|
3247
|
+
children: [
|
|
3248
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:plus", className: "mr-1", width: 14 }),
|
|
3249
|
+
tr("fluidgrids.runControl.tokens.create", "New token")
|
|
3250
|
+
]
|
|
3251
|
+
}
|
|
3252
|
+
)
|
|
3253
|
+
] }),
|
|
3254
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: tr(
|
|
3255
|
+
"fluidgrids.runControl.tokens.description",
|
|
3256
|
+
"A token lets another system send a signal to this run with a plain POST, without a FluidGrids credential."
|
|
3257
|
+
) }),
|
|
3258
|
+
/* @__PURE__ */ jsx(FieldError, { message: tokens.error }),
|
|
3259
|
+
tokens.data && tokens.data.length === 0 && /* @__PURE__ */ jsx(
|
|
3260
|
+
"p",
|
|
3261
|
+
{
|
|
3262
|
+
className: "text-sm text-text-secondary",
|
|
3263
|
+
"data-testid": "signal-tokens-empty",
|
|
3264
|
+
children: tr("fluidgrids.runControl.tokens.empty", "No tokens yet.")
|
|
3265
|
+
}
|
|
3266
|
+
),
|
|
3267
|
+
tokens.data && tokens.data.length > 0 && /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border-secondary rounded-md border border-border-secondary", children: tokens.data.map((token) => {
|
|
3268
|
+
const state = tokenState(token);
|
|
3269
|
+
return /* @__PURE__ */ jsxs(
|
|
3270
|
+
"li",
|
|
3271
|
+
{
|
|
3272
|
+
className: "flex flex-wrap items-center gap-3 px-3 py-2 text-sm",
|
|
3273
|
+
"data-testid": `signal-token-${token.id}`,
|
|
3274
|
+
children: [
|
|
3275
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs text-text-primary", children: token.signalName }),
|
|
3276
|
+
/* @__PURE__ */ jsx(StatusPill, { tone: state === "active" ? "success" : "neutral", children: signalTokenStateLabel(tr, state) }),
|
|
3277
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-text-secondary", children: [
|
|
3278
|
+
token.reusable ? tr("fluidgrids.runControl.tokens.reusable", "Reusable") : tr(
|
|
3279
|
+
"fluidgrids.runControl.tokens.singleUse",
|
|
3280
|
+
"Single use"
|
|
3281
|
+
),
|
|
3282
|
+
" \xB7 ",
|
|
3283
|
+
tr(
|
|
3284
|
+
"fluidgrids.runControl.tokens.expires",
|
|
3285
|
+
"expires {{date}}",
|
|
3286
|
+
{
|
|
3287
|
+
date: new Date(token.expiresAt).toLocaleString()
|
|
3288
|
+
}
|
|
3289
|
+
)
|
|
3290
|
+
] }),
|
|
3291
|
+
state === "active" && canCreate && /* @__PURE__ */ jsx(
|
|
3292
|
+
Button,
|
|
3293
|
+
{
|
|
3294
|
+
size: "sm",
|
|
3295
|
+
variant: "ghost",
|
|
3296
|
+
className: "ml-auto text-[var(--color-status-error-text)]",
|
|
3297
|
+
onClick: () => void revoke(token.id),
|
|
3298
|
+
"data-testid": `signal-token-revoke-${token.id}`,
|
|
3299
|
+
children: tr("fluidgrids.runControl.tokens.revoke", "Revoke")
|
|
3300
|
+
}
|
|
3301
|
+
)
|
|
3302
|
+
]
|
|
3303
|
+
},
|
|
3304
|
+
token.id
|
|
3305
|
+
);
|
|
3306
|
+
}) }),
|
|
3307
|
+
creating && /* @__PURE__ */ jsx(
|
|
3308
|
+
CreateSignalTokenDialog,
|
|
3309
|
+
{
|
|
3310
|
+
runId,
|
|
3311
|
+
signalNames,
|
|
3312
|
+
onClose: () => setCreating(false),
|
|
3313
|
+
onCreated: (next) => {
|
|
3314
|
+
setCreating(false);
|
|
3315
|
+
setCreated(next);
|
|
3316
|
+
void tokens.refetch();
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
),
|
|
3320
|
+
created && /* @__PURE__ */ jsx(
|
|
3321
|
+
SignalTokenCreatedDialog,
|
|
3322
|
+
{
|
|
3323
|
+
created,
|
|
3324
|
+
onClose: () => setCreated(null)
|
|
3325
|
+
}
|
|
3326
|
+
)
|
|
3327
|
+
]
|
|
3328
|
+
}
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
function CreateSignalTokenDialog({
|
|
3332
|
+
runId,
|
|
3333
|
+
signalNames,
|
|
3334
|
+
onClose,
|
|
3335
|
+
onCreated
|
|
3336
|
+
}) {
|
|
3337
|
+
const tr = useTr();
|
|
3338
|
+
const [signalName, setSignalName] = useState(signalNames[0] ?? "");
|
|
3339
|
+
const [ttl, setTtl] = useState(String(2592e3));
|
|
3340
|
+
const [reusable, setReusable] = useState(false);
|
|
3341
|
+
const [ips, setIps] = useState("");
|
|
3342
|
+
const [busy, setBusy] = useState(false);
|
|
3343
|
+
const [error, setError] = useState(null);
|
|
3344
|
+
const allowlist = parseIpAllowlist(ips);
|
|
3345
|
+
const submit = async () => {
|
|
3346
|
+
if (!signalName.trim() || !allowlist.ok) return;
|
|
3347
|
+
setBusy(true);
|
|
3348
|
+
setError(null);
|
|
3349
|
+
try {
|
|
3350
|
+
onCreated(
|
|
3351
|
+
await createSignalToken({
|
|
3352
|
+
runId,
|
|
3353
|
+
signalName: signalName.trim(),
|
|
3354
|
+
ttlSeconds: Number(ttl),
|
|
3355
|
+
reusable,
|
|
3356
|
+
ipAllowlist: allowlist.values
|
|
3357
|
+
})
|
|
3358
|
+
);
|
|
3359
|
+
} catch (err) {
|
|
3360
|
+
setError(describeOperationError(err));
|
|
3361
|
+
} finally {
|
|
3362
|
+
setBusy(false);
|
|
3363
|
+
}
|
|
3364
|
+
};
|
|
3365
|
+
return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(
|
|
3366
|
+
DialogContent,
|
|
3367
|
+
{
|
|
3368
|
+
style: RUN_CONTROL_DIALOG_STYLE,
|
|
3369
|
+
"data-testid": "signal-token-dialog",
|
|
3370
|
+
children: [
|
|
3371
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
3372
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runControl.tokens.createTitle", "New signal token") }),
|
|
3373
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: tr(
|
|
3374
|
+
"fluidgrids.runControl.tokens.createDescription",
|
|
3375
|
+
"The token is shown once. Store it where the sending system can read it."
|
|
3376
|
+
) })
|
|
3377
|
+
] }),
|
|
3378
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3379
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
3380
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "signal-token-name", children: tr("fluidgrids.runControl.tokens.signal", "Signal") }),
|
|
3381
|
+
signalNames.length > 0 ? /* @__PURE__ */ jsx(
|
|
3382
|
+
NativeSelect,
|
|
3383
|
+
{
|
|
3384
|
+
id: "signal-token-name",
|
|
3385
|
+
value: signalName,
|
|
3386
|
+
onChange: setSignalName,
|
|
3387
|
+
testId: "signal-token-name",
|
|
3388
|
+
children: signalNames.map((name) => /* @__PURE__ */ jsx("option", { value: name, children: name }, name))
|
|
3389
|
+
}
|
|
3390
|
+
) : /* @__PURE__ */ jsx(
|
|
3391
|
+
Input,
|
|
3392
|
+
{
|
|
3393
|
+
id: "signal-token-name",
|
|
3394
|
+
value: signalName,
|
|
3395
|
+
onChange: (event) => setSignalName(event.target.value),
|
|
3396
|
+
"data-testid": "signal-token-name"
|
|
3397
|
+
}
|
|
3398
|
+
)
|
|
3399
|
+
] }),
|
|
3400
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
3401
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "signal-token-ttl", children: tr("fluidgrids.runControl.tokens.ttl", "Lifetime") }),
|
|
3402
|
+
/* @__PURE__ */ jsx(
|
|
3403
|
+
NativeSelect,
|
|
3404
|
+
{
|
|
3405
|
+
id: "signal-token-ttl",
|
|
3406
|
+
value: ttl,
|
|
3407
|
+
onChange: setTtl,
|
|
3408
|
+
testId: "signal-token-ttl",
|
|
3409
|
+
children: SIGNAL_TOKEN_TTLS.map((option) => /* @__PURE__ */ jsx("option", { value: String(option.seconds), children: signalTokenTtlLabel(tr, option.key) }, option.seconds))
|
|
3410
|
+
}
|
|
3411
|
+
)
|
|
3412
|
+
] }),
|
|
3413
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
3414
|
+
tr(
|
|
3415
|
+
"fluidgrids.runControl.tokens.reusableToggle",
|
|
3416
|
+
"Reusable until it expires"
|
|
3417
|
+
),
|
|
3418
|
+
/* @__PURE__ */ jsx(
|
|
3419
|
+
Switch,
|
|
3420
|
+
{
|
|
3421
|
+
checked: reusable,
|
|
3422
|
+
onCheckedChange: setReusable,
|
|
3423
|
+
"data-testid": "signal-token-reusable"
|
|
3424
|
+
}
|
|
3425
|
+
)
|
|
3426
|
+
] }),
|
|
3427
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
3428
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "signal-token-ips", children: tr(
|
|
3429
|
+
"fluidgrids.runControl.tokens.ips",
|
|
3430
|
+
"Allowed IPv4 addresses or ranges (optional)"
|
|
3431
|
+
) }),
|
|
3432
|
+
/* @__PURE__ */ jsx(
|
|
3433
|
+
Textarea,
|
|
3434
|
+
{
|
|
3435
|
+
id: "signal-token-ips",
|
|
3436
|
+
rows: 2,
|
|
3437
|
+
value: ips,
|
|
3438
|
+
onChange: (event) => setIps(event.target.value),
|
|
3439
|
+
placeholder: "203.0.113.10, 198.51.100.0/24",
|
|
3440
|
+
className: "font-mono text-xs",
|
|
3441
|
+
"data-testid": "signal-token-ips"
|
|
3442
|
+
}
|
|
3443
|
+
),
|
|
3444
|
+
!allowlist.ok && /* @__PURE__ */ jsx(
|
|
3445
|
+
FieldError,
|
|
3446
|
+
{
|
|
3447
|
+
message: tr(
|
|
3448
|
+
"fluidgrids.runControl.tokens.badIp",
|
|
3449
|
+
"Not an IPv4 address or range: {{value}}",
|
|
3450
|
+
{ value: allowlist.bad }
|
|
3451
|
+
)
|
|
3452
|
+
}
|
|
3453
|
+
)
|
|
3454
|
+
] }),
|
|
3455
|
+
/* @__PURE__ */ jsx(FieldError, { message: error, testId: "signal-token-error" })
|
|
3456
|
+
] }),
|
|
3457
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
3458
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: busy, children: tr("fluidgrids.runControl.common.cancel", "Cancel") }),
|
|
3459
|
+
/* @__PURE__ */ jsx(
|
|
3460
|
+
Button,
|
|
3461
|
+
{
|
|
3462
|
+
onClick: () => void submit(),
|
|
3463
|
+
disabled: busy || !signalName.trim() || !allowlist.ok,
|
|
3464
|
+
"data-testid": "signal-token-submit",
|
|
3465
|
+
children: tr("fluidgrids.runControl.tokens.createButton", "Create token")
|
|
3466
|
+
}
|
|
3467
|
+
)
|
|
3468
|
+
] })
|
|
3469
|
+
]
|
|
3470
|
+
}
|
|
3471
|
+
) });
|
|
3472
|
+
}
|
|
3473
|
+
function SignalTokenCreatedDialog({
|
|
3474
|
+
created,
|
|
3475
|
+
onClose
|
|
3476
|
+
}) {
|
|
3477
|
+
const tr = useTr();
|
|
3478
|
+
const copy = async (text2) => {
|
|
3479
|
+
try {
|
|
3480
|
+
await navigator.clipboard.writeText(text2);
|
|
3481
|
+
toast.success(tr("fluidgrids.runControl.common.copied", "Copied"));
|
|
3482
|
+
} catch {
|
|
3483
|
+
toast.error(
|
|
3484
|
+
tr(
|
|
3485
|
+
"fluidgrids.runControl.common.copyFailed",
|
|
3486
|
+
"Could not copy; select the text instead."
|
|
3487
|
+
)
|
|
3488
|
+
);
|
|
3489
|
+
}
|
|
3490
|
+
};
|
|
3491
|
+
return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(
|
|
3492
|
+
DialogContent,
|
|
3493
|
+
{
|
|
3494
|
+
style: RUN_CONTROL_DIALOG_STYLE,
|
|
3495
|
+
"data-testid": "signal-token-created",
|
|
3496
|
+
children: [
|
|
3497
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
3498
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runControl.tokens.createdTitle", "Token created") }),
|
|
3499
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: tr(
|
|
3500
|
+
"fluidgrids.runControl.tokens.createdDescription",
|
|
3501
|
+
"Copy it now: it cannot be shown again. POST the signal's payload as JSON to the URL."
|
|
3502
|
+
) })
|
|
3503
|
+
] }),
|
|
3504
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3505
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
3506
|
+
/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.runControl.tokens.url", "Signal URL") }),
|
|
3507
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3508
|
+
/* @__PURE__ */ jsx(
|
|
3509
|
+
"code",
|
|
3510
|
+
{
|
|
3511
|
+
className: "min-w-0 flex-1 break-all rounded-md bg-background-primary p-2 font-mono text-xs text-text-primary",
|
|
3512
|
+
"data-testid": "signal-token-url",
|
|
3513
|
+
children: created.url
|
|
3514
|
+
}
|
|
3515
|
+
),
|
|
3516
|
+
/* @__PURE__ */ jsx(
|
|
3517
|
+
Button,
|
|
3518
|
+
{
|
|
3519
|
+
size: "sm",
|
|
3520
|
+
variant: "outline",
|
|
3521
|
+
onClick: () => void copy(created.url),
|
|
3522
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:content-copy", width: 14 })
|
|
3523
|
+
}
|
|
3524
|
+
)
|
|
3525
|
+
] })
|
|
3526
|
+
] }),
|
|
3527
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
3528
|
+
/* @__PURE__ */ jsx(Label, { children: tr("fluidgrids.runControl.tokens.token", "Token") }),
|
|
3529
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3530
|
+
/* @__PURE__ */ jsx(
|
|
3531
|
+
"code",
|
|
3532
|
+
{
|
|
3533
|
+
className: "min-w-0 flex-1 break-all rounded-md bg-background-primary p-2 font-mono text-xs text-text-primary",
|
|
3534
|
+
"data-testid": "signal-token-value",
|
|
3535
|
+
children: created.token
|
|
3536
|
+
}
|
|
3537
|
+
),
|
|
3538
|
+
/* @__PURE__ */ jsx(
|
|
3539
|
+
Button,
|
|
3540
|
+
{
|
|
3541
|
+
size: "sm",
|
|
3542
|
+
variant: "outline",
|
|
3543
|
+
onClick: () => void copy(created.token),
|
|
3544
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:content-copy", width: 14 })
|
|
3545
|
+
}
|
|
3546
|
+
)
|
|
3547
|
+
] })
|
|
3548
|
+
] })
|
|
3549
|
+
] }),
|
|
3550
|
+
/* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { onClick: onClose, "data-testid": "signal-token-created-done", children: tr("fluidgrids.runControl.common.done", "Done") }) })
|
|
3551
|
+
]
|
|
3552
|
+
}
|
|
3553
|
+
) });
|
|
3554
|
+
}
|
|
3555
|
+
function shouldShowWaitingForWorker(props) {
|
|
3556
|
+
return !!props.taskQueue && props.taskQueue !== "managed" && props.waiting && props.pollers === 0;
|
|
3557
|
+
}
|
|
3558
|
+
function WaitingForWorkerBanner(props) {
|
|
3559
|
+
const tr = useTr();
|
|
3560
|
+
if (!shouldShowWaitingForWorker(props)) return null;
|
|
3561
|
+
return /* @__PURE__ */ jsxs(
|
|
3562
|
+
"div",
|
|
3563
|
+
{
|
|
3564
|
+
role: "status",
|
|
3565
|
+
className: "mb-6 flex items-start gap-2 rounded-lg border border-[var(--color-status-warning-border)] bg-[var(--color-status-warning-bg-subtle)] p-3 text-sm text-[var(--color-status-warning-text)]",
|
|
3566
|
+
"data-testid": "waiting-for-worker",
|
|
3567
|
+
children: [
|
|
3568
|
+
/* @__PURE__ */ jsx(
|
|
3569
|
+
Icon,
|
|
3570
|
+
{
|
|
3571
|
+
icon: "mdi:server-network-off",
|
|
3572
|
+
width: 18,
|
|
3573
|
+
className: "mt-0.5 shrink-0"
|
|
3574
|
+
}
|
|
3575
|
+
),
|
|
3576
|
+
/* @__PURE__ */ jsx("span", { children: tr(
|
|
3577
|
+
"fluidgrids.runControl.worker.waiting",
|
|
3578
|
+
"Waiting for a worker on queue {{queue}}. Start a worker that serves this queue to continue the run.",
|
|
3579
|
+
{ queue: props.taskQueue ?? "" }
|
|
3580
|
+
) })
|
|
3581
|
+
]
|
|
3582
|
+
}
|
|
3583
|
+
);
|
|
3584
|
+
}
|
|
3585
|
+
var QUEUE_STATS_INTERVAL_MS = 6e4;
|
|
3586
|
+
function isRunOpen(status) {
|
|
3587
|
+
return status === "RUNNING" || status === "PENDING" || status === "WAITING";
|
|
3588
|
+
}
|
|
3589
|
+
function RunControlSection({
|
|
3590
|
+
run,
|
|
3591
|
+
flags,
|
|
3592
|
+
canControl,
|
|
3593
|
+
activities,
|
|
3594
|
+
workflowTaskWaiting,
|
|
3595
|
+
historyEvents,
|
|
3596
|
+
signalNames,
|
|
3597
|
+
onChanged,
|
|
3598
|
+
onOpenRun,
|
|
3599
|
+
resetPoint,
|
|
3600
|
+
onResetPointChange
|
|
3601
|
+
}) {
|
|
3602
|
+
const tr = useTr();
|
|
3603
|
+
const durable = run.executionKind === "DURABLE_FN";
|
|
3604
|
+
const open = isRunOpen(run.status);
|
|
3605
|
+
const runControl = durable ? flags.runControlDurable : flags.runControl;
|
|
3606
|
+
const [updateOpen, setUpdateOpen] = useState(false);
|
|
3607
|
+
const [panel, setPanel] = useState(null);
|
|
3608
|
+
const fieldsEnabled = flags.searchAttributes || flags.selfHostedWorkers;
|
|
3609
|
+
const fields = useResource(
|
|
3610
|
+
`run-control-fields:${run.id}`,
|
|
3611
|
+
fieldsEnabled,
|
|
3612
|
+
() => fetchRunControlFields(run.id)
|
|
3613
|
+
);
|
|
3614
|
+
const activitiesShown = durable && open && flags.runControlDurable && activities.length > 0;
|
|
3615
|
+
const rules = useResource(
|
|
3616
|
+
"pause-rules",
|
|
3617
|
+
activitiesShown,
|
|
3618
|
+
fetchPauseRules
|
|
3619
|
+
);
|
|
3620
|
+
const queue = run.taskQueue ?? null;
|
|
3621
|
+
const waiting = workflowTaskWaiting || activities.some((activity) => activity.state === "SCHEDULED");
|
|
3622
|
+
const statsEnabled = flags.selfHostedWorkers && durable && open && !!queue && queue !== "managed" && waiting;
|
|
3623
|
+
const stats = useResource(
|
|
3624
|
+
`queue-stats:${queue ?? ""}`,
|
|
3625
|
+
statsEnabled,
|
|
3626
|
+
() => fetchTaskQueueStats(queue ?? "managed"),
|
|
3627
|
+
{ live: true, intervalMs: QUEUE_STATS_INTERVAL_MS }
|
|
3628
|
+
);
|
|
3629
|
+
const updates = declaredHandlers(
|
|
3630
|
+
run.definitionJson,
|
|
3631
|
+
run.workflowFn,
|
|
3632
|
+
"updates"
|
|
3633
|
+
);
|
|
3634
|
+
const declaredQueries = declaredHandlers(
|
|
3635
|
+
run.definitionJson,
|
|
3636
|
+
run.workflowFn,
|
|
3637
|
+
"queries"
|
|
3638
|
+
);
|
|
3639
|
+
const queries = durable ? declaredQueries : { names: [...GRAPH_RUN_QUERIES], openNames: false };
|
|
3640
|
+
const pauseStates = activityPauseStates(historyEvents);
|
|
3641
|
+
const showUpdate = runControl && durable && open && canControl;
|
|
3642
|
+
const showReset = runControl && durable && canControl;
|
|
3643
|
+
const showQuery = runControl;
|
|
3644
|
+
const showTokens = runControl && durable && open;
|
|
3645
|
+
return /* @__PURE__ */ jsxs("div", { "data-testid": "run-control-section", children: [
|
|
3646
|
+
(showUpdate || showReset || showQuery || showTokens) && /* @__PURE__ */ jsxs(
|
|
3647
|
+
"div",
|
|
3648
|
+
{
|
|
3649
|
+
className: "mb-4 flex flex-wrap items-center gap-2",
|
|
3650
|
+
"data-testid": "run-control-actions",
|
|
3651
|
+
children: [
|
|
3652
|
+
showUpdate && /* @__PURE__ */ jsxs(
|
|
3653
|
+
Button,
|
|
3654
|
+
{
|
|
3655
|
+
variant: "outline",
|
|
3656
|
+
size: "sm",
|
|
3657
|
+
onClick: () => setUpdateOpen(true),
|
|
3658
|
+
"data-testid": "run-update-button",
|
|
3659
|
+
children: [
|
|
3660
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:send-check-outline", className: "mr-1", width: 16 }),
|
|
3661
|
+
tr("fluidgrids.runControl.actions.update", "Update")
|
|
3662
|
+
]
|
|
3663
|
+
}
|
|
3664
|
+
),
|
|
3665
|
+
showQuery && /* @__PURE__ */ jsxs(
|
|
3666
|
+
Button,
|
|
3667
|
+
{
|
|
3668
|
+
variant: panel === "query" ? "default" : "outline",
|
|
3669
|
+
size: "sm",
|
|
3670
|
+
"aria-pressed": panel === "query",
|
|
3671
|
+
onClick: () => setPanel(panel === "query" ? null : "query"),
|
|
3672
|
+
"data-testid": "run-query-button",
|
|
3673
|
+
children: [
|
|
3674
|
+
/* @__PURE__ */ jsx(
|
|
3675
|
+
Icon,
|
|
3676
|
+
{
|
|
3677
|
+
icon: "mdi:help-circle-outline",
|
|
3678
|
+
className: "mr-1",
|
|
3679
|
+
width: 16
|
|
3680
|
+
}
|
|
3681
|
+
),
|
|
3682
|
+
tr("fluidgrids.runControl.actions.query", "Query")
|
|
3683
|
+
]
|
|
3684
|
+
}
|
|
3685
|
+
),
|
|
3686
|
+
showTokens && /* @__PURE__ */ jsxs(
|
|
3687
|
+
Button,
|
|
3688
|
+
{
|
|
3689
|
+
variant: panel === "tokens" ? "default" : "outline",
|
|
3690
|
+
size: "sm",
|
|
3691
|
+
"aria-pressed": panel === "tokens",
|
|
3692
|
+
onClick: () => setPanel(panel === "tokens" ? null : "tokens"),
|
|
3693
|
+
"data-testid": "run-tokens-button",
|
|
3694
|
+
children: [
|
|
3695
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:key-chain-variant", className: "mr-1", width: 16 }),
|
|
3696
|
+
tr("fluidgrids.runControl.actions.tokens", "Signal tokens")
|
|
3697
|
+
]
|
|
3698
|
+
}
|
|
3699
|
+
),
|
|
3700
|
+
showReset && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
3701
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
3702
|
+
Button,
|
|
3703
|
+
{
|
|
3704
|
+
variant: "outline",
|
|
3705
|
+
size: "sm",
|
|
3706
|
+
"data-testid": "run-reset-menu",
|
|
3707
|
+
children: [
|
|
3708
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:restore", className: "mr-1", width: 16 }),
|
|
3709
|
+
tr("fluidgrids.runControl.actions.reset", "Reset"),
|
|
3710
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:chevron-down", className: "ml-1", width: 14 })
|
|
3711
|
+
]
|
|
3712
|
+
}
|
|
3713
|
+
) }),
|
|
3714
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { style: RUN_CONTROL_DIALOG_STYLE, children: /* @__PURE__ */ jsx(
|
|
3715
|
+
DropdownMenuItem,
|
|
3716
|
+
{
|
|
3717
|
+
onSelect: () => onResetPointChange({ kind: "lastClean" }),
|
|
3718
|
+
"data-testid": "run-reset-last-clean",
|
|
3719
|
+
children: tr(
|
|
3720
|
+
"fluidgrids.runControl.actions.resetLastClean",
|
|
3721
|
+
"Reset to last clean task"
|
|
3722
|
+
)
|
|
3723
|
+
}
|
|
3724
|
+
) })
|
|
3725
|
+
] }),
|
|
3726
|
+
showReset && /* @__PURE__ */ jsx("span", { className: "text-xs text-text-secondary", children: tr(
|
|
3727
|
+
"fluidgrids.runControl.actions.resetHint",
|
|
3728
|
+
"Or use Reset here on an event in History."
|
|
3729
|
+
) })
|
|
3730
|
+
]
|
|
3731
|
+
}
|
|
3732
|
+
),
|
|
3733
|
+
statsEnabled && /* @__PURE__ */ jsx(
|
|
3734
|
+
WaitingForWorkerBanner,
|
|
3735
|
+
{
|
|
3736
|
+
taskQueue: queue,
|
|
3737
|
+
waiting,
|
|
3738
|
+
pollers: stats.data?.pollers ?? null
|
|
3739
|
+
}
|
|
3740
|
+
),
|
|
3741
|
+
panel === "query" && showQuery && /* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsx(RunQueryPanel, { runId: run.id, handlers: queries }) }),
|
|
3742
|
+
panel === "tokens" && showTokens && /* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsx(
|
|
3743
|
+
SignalTokensPanel,
|
|
3744
|
+
{
|
|
3745
|
+
runId: run.id,
|
|
3746
|
+
signalNames,
|
|
3747
|
+
canCreate: canControl
|
|
3748
|
+
}
|
|
3749
|
+
) }),
|
|
3750
|
+
flags.searchAttributes && fields.data && /* @__PURE__ */ jsx(
|
|
3751
|
+
RunSearchAttributesCard,
|
|
3752
|
+
{
|
|
3753
|
+
runId: run.id,
|
|
3754
|
+
attributes: fields.data.searchAttributes,
|
|
3755
|
+
canEdit: canControl,
|
|
3756
|
+
onChanged: () => void fields.refetch()
|
|
3757
|
+
}
|
|
3758
|
+
),
|
|
3759
|
+
activitiesShown && /* @__PURE__ */ jsx(
|
|
3760
|
+
RunActivitiesCard,
|
|
3761
|
+
{
|
|
3762
|
+
runId: run.id,
|
|
3763
|
+
activities,
|
|
3764
|
+
pauseStates,
|
|
3765
|
+
pauseRules: rules.data ?? [],
|
|
3766
|
+
canControl,
|
|
3767
|
+
onChanged
|
|
3768
|
+
}
|
|
3769
|
+
),
|
|
3770
|
+
showUpdate && /* @__PURE__ */ jsx(
|
|
3771
|
+
RunUpdateDialog,
|
|
3772
|
+
{
|
|
3773
|
+
runId: run.id,
|
|
3774
|
+
handlers: updates,
|
|
3775
|
+
open: updateOpen,
|
|
3776
|
+
onOpenChange: setUpdateOpen,
|
|
3777
|
+
onDone: onChanged
|
|
3778
|
+
}
|
|
3779
|
+
),
|
|
3780
|
+
showReset && /* @__PURE__ */ jsx(
|
|
3781
|
+
ResetRunDialog,
|
|
3782
|
+
{
|
|
3783
|
+
runId: run.id,
|
|
3784
|
+
point: resetPoint,
|
|
3785
|
+
onOpenChange: (next) => !next && onResetPointChange(null),
|
|
3786
|
+
onReset: onOpenRun
|
|
3787
|
+
}
|
|
3788
|
+
)
|
|
3789
|
+
] });
|
|
3790
|
+
}
|
|
3791
|
+
function describeCodecError(tr, err) {
|
|
3792
|
+
if (!(err instanceof CodecServerError))
|
|
3793
|
+
return tr(
|
|
3794
|
+
"fluidgrids.runControl.codec.errors.failed",
|
|
3795
|
+
"The payload could not be decoded."
|
|
3796
|
+
);
|
|
3797
|
+
switch (err.code) {
|
|
3798
|
+
case "invalidUrl":
|
|
3799
|
+
return tr(
|
|
3800
|
+
"fluidgrids.runControl.codec.errors.invalidUrl",
|
|
3801
|
+
"The codec server URL must be https (or http to localhost)."
|
|
3802
|
+
);
|
|
3803
|
+
case "unreachable":
|
|
3804
|
+
return tr(
|
|
3805
|
+
"fluidgrids.runControl.codec.errors.unreachable",
|
|
3806
|
+
"The codec server could not be reached: {{detail}}",
|
|
3807
|
+
{ detail: err.params.detail ?? "" }
|
|
3808
|
+
);
|
|
3809
|
+
case "http":
|
|
3810
|
+
return tr(
|
|
3811
|
+
"fluidgrids.runControl.codec.errors.http",
|
|
3812
|
+
"The codec server answered HTTP {{status}}.",
|
|
3813
|
+
{ status: err.params.status ?? 0 }
|
|
3814
|
+
);
|
|
3815
|
+
case "notPayloadList":
|
|
3816
|
+
return tr(
|
|
3817
|
+
"fluidgrids.runControl.codec.errors.notPayloadList",
|
|
3818
|
+
"The codec server's answer is not a list of payloads."
|
|
3819
|
+
);
|
|
3820
|
+
case "notJson":
|
|
3821
|
+
return tr(
|
|
3822
|
+
"fluidgrids.runControl.codec.errors.notJson",
|
|
3823
|
+
"The codec server returned a payload that is not JSON."
|
|
3824
|
+
);
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
var MAX_DEPTH = 32;
|
|
3828
|
+
function formatBytes(bytes) {
|
|
3829
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3830
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
3831
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
3832
|
+
}
|
|
3833
|
+
function PayloadView({
|
|
3834
|
+
value,
|
|
3835
|
+
codecServer,
|
|
3836
|
+
fetchImpl
|
|
3837
|
+
}) {
|
|
3838
|
+
if (!containsEncodedPayload(value)) {
|
|
3839
|
+
return /* @__PURE__ */ jsx("pre", { className: "mt-1 max-h-48 overflow-auto whitespace-pre-wrap break-words rounded bg-background-secondary p-2 font-mono text-text-primary", children: JSON.stringify(value, null, 2) });
|
|
3840
|
+
}
|
|
3841
|
+
return /* @__PURE__ */ jsx(
|
|
3842
|
+
"div",
|
|
3843
|
+
{
|
|
3844
|
+
className: "mt-1 max-h-64 overflow-auto rounded bg-background-secondary p-2 font-mono text-text-primary",
|
|
3845
|
+
"data-testid": "payload-view",
|
|
3846
|
+
children: /* @__PURE__ */ jsx(
|
|
3847
|
+
PayloadNode,
|
|
3848
|
+
{
|
|
3849
|
+
value,
|
|
3850
|
+
codecServer,
|
|
3851
|
+
fetchImpl,
|
|
3852
|
+
depth: 0
|
|
3853
|
+
}
|
|
3854
|
+
)
|
|
3855
|
+
}
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
function PayloadNode({
|
|
3859
|
+
value,
|
|
3860
|
+
codecServer,
|
|
3861
|
+
fetchImpl,
|
|
3862
|
+
depth
|
|
3863
|
+
}) {
|
|
3864
|
+
if (isEncodedPayload(value))
|
|
3865
|
+
return /* @__PURE__ */ jsx(
|
|
3866
|
+
EncodedPayloadChip,
|
|
3867
|
+
{
|
|
3868
|
+
payload: value,
|
|
3869
|
+
codecServer,
|
|
3870
|
+
fetchImpl
|
|
3871
|
+
}
|
|
3872
|
+
);
|
|
3873
|
+
if (depth >= MAX_DEPTH || !containsEncodedPayload(value))
|
|
3874
|
+
return /* @__PURE__ */ jsx("span", { className: "whitespace-pre-wrap break-words", children: JSON.stringify(value) });
|
|
3875
|
+
const entries = Array.isArray(value) ? value.map((item, index) => [String(index), item]) : typeof value === "object" && value !== null ? Object.entries(value) : [];
|
|
3876
|
+
const open = Array.isArray(value) ? "[" : "{";
|
|
3877
|
+
const close = Array.isArray(value) ? "]" : "}";
|
|
3878
|
+
return /* @__PURE__ */ jsxs("span", { children: [
|
|
3879
|
+
open,
|
|
3880
|
+
/* @__PURE__ */ jsx("span", { className: "block pl-4", children: entries.map(([key, item]) => /* @__PURE__ */ jsxs("span", { className: "block", children: [
|
|
3881
|
+
!Array.isArray(value) && /* @__PURE__ */ jsxs("span", { className: "text-text-secondary", children: [
|
|
3882
|
+
JSON.stringify(key),
|
|
3883
|
+
":",
|
|
3884
|
+
" "
|
|
3885
|
+
] }),
|
|
3886
|
+
/* @__PURE__ */ jsx(
|
|
3887
|
+
PayloadNode,
|
|
3888
|
+
{
|
|
3889
|
+
value: item,
|
|
3890
|
+
codecServer,
|
|
3891
|
+
fetchImpl,
|
|
3892
|
+
depth: depth + 1
|
|
3893
|
+
}
|
|
3894
|
+
)
|
|
3895
|
+
] }, key)) }),
|
|
3896
|
+
close
|
|
3897
|
+
] });
|
|
3898
|
+
}
|
|
3899
|
+
function EncodedPayloadChip({
|
|
3900
|
+
payload,
|
|
3901
|
+
codecServer,
|
|
3902
|
+
fetchImpl
|
|
3903
|
+
}) {
|
|
3904
|
+
const tr = useTr();
|
|
3905
|
+
const [decoded, setDecoded] = useState(null);
|
|
3906
|
+
const [busy, setBusy] = useState(false);
|
|
3907
|
+
const [error, setError] = useState(null);
|
|
3908
|
+
if (decoded && !isEncodedPayload(decoded.value)) {
|
|
3909
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-block align-top", "data-testid": "payload-decoded", children: [
|
|
3910
|
+
/* @__PURE__ */ jsx("span", { className: "mr-1 rounded border border-border-secondary px-1 text-[10px] uppercase text-text-secondary", children: tr("fluidgrids.runControl.payload.decoded", "decoded") }),
|
|
3911
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-pre-wrap break-words", children: JSON.stringify(decoded.value, null, 2) })
|
|
3912
|
+
] });
|
|
3913
|
+
}
|
|
3914
|
+
const decode = async () => {
|
|
3915
|
+
if (!codecServer) return;
|
|
3916
|
+
setBusy(true);
|
|
3917
|
+
setError(null);
|
|
3918
|
+
try {
|
|
3919
|
+
const [value] = await decodePayloads(codecServer, [payload], fetchImpl);
|
|
3920
|
+
setDecoded({ value });
|
|
3921
|
+
if (isEncodedPayload(value))
|
|
3922
|
+
setError(
|
|
3923
|
+
tr(
|
|
3924
|
+
"fluidgrids.runControl.payload.stillEncoded",
|
|
3925
|
+
"The codec server could not decode this payload."
|
|
3926
|
+
)
|
|
3927
|
+
);
|
|
3928
|
+
} catch (err) {
|
|
3929
|
+
setError(describeCodecError(tr, err));
|
|
3930
|
+
} finally {
|
|
3931
|
+
setBusy(false);
|
|
3932
|
+
}
|
|
3933
|
+
};
|
|
3934
|
+
return /* @__PURE__ */ jsxs(
|
|
3935
|
+
"span",
|
|
3936
|
+
{
|
|
3937
|
+
className: "inline-flex flex-wrap items-center gap-2 align-middle",
|
|
3938
|
+
"data-testid": "payload-encoded",
|
|
3939
|
+
children: [
|
|
3940
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded border border-border-secondary bg-background-tertiary px-1.5 py-0.5 text-xs text-text-secondary", children: [
|
|
3941
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:lock-outline", width: 12 }),
|
|
3942
|
+
tr("fluidgrids.runControl.payload.encoded", "Encoded payload"),
|
|
3943
|
+
" \xB7",
|
|
3944
|
+
" ",
|
|
3945
|
+
payload.encoding,
|
|
3946
|
+
" \xB7 ",
|
|
3947
|
+
formatBytes(encodedPayloadBytes(payload))
|
|
3948
|
+
] }),
|
|
3949
|
+
codecServer && /* @__PURE__ */ jsx(
|
|
3950
|
+
Button,
|
|
3951
|
+
{
|
|
3952
|
+
size: "sm",
|
|
3953
|
+
variant: "outline",
|
|
3954
|
+
className: "h-6 px-2 text-xs",
|
|
3955
|
+
onClick: () => void decode(),
|
|
3956
|
+
disabled: busy,
|
|
3957
|
+
"data-testid": "payload-decode",
|
|
3958
|
+
children: busy ? tr("fluidgrids.runControl.payload.decoding", "Decoding\u2026") : tr("fluidgrids.runControl.payload.decode", "Decode")
|
|
3959
|
+
}
|
|
3960
|
+
),
|
|
3961
|
+
/* @__PURE__ */ jsx(FieldError, { message: error, testId: "payload-decode-error" })
|
|
3962
|
+
]
|
|
3963
|
+
}
|
|
3964
|
+
);
|
|
3965
|
+
}
|
|
3966
|
+
function RunArchivedNotice({
|
|
3967
|
+
runId,
|
|
3968
|
+
enabled
|
|
3969
|
+
}) {
|
|
3970
|
+
const tr = useTr();
|
|
3971
|
+
const archive = useResource(
|
|
3972
|
+
`run-archive:${runId}`,
|
|
3973
|
+
enabled,
|
|
3974
|
+
() => fetchRunHistoryArchive(runId)
|
|
3975
|
+
);
|
|
3976
|
+
if (!archive.data?.archived) return null;
|
|
3977
|
+
return /* @__PURE__ */ jsxs(
|
|
3978
|
+
"div",
|
|
3979
|
+
{
|
|
3980
|
+
role: "status",
|
|
3981
|
+
className: "flex items-start gap-2 rounded-md border border-border-secondary bg-background-tertiary p-3 text-sm text-text-primary",
|
|
3982
|
+
"data-testid": "run-archived",
|
|
3983
|
+
children: [
|
|
3984
|
+
/* @__PURE__ */ jsx(
|
|
3985
|
+
Icon,
|
|
3986
|
+
{
|
|
3987
|
+
icon: "mdi:archive-outline",
|
|
3988
|
+
width: 18,
|
|
3989
|
+
className: "mt-0.5 shrink-0 text-text-secondary"
|
|
3990
|
+
}
|
|
3991
|
+
),
|
|
3992
|
+
/* @__PURE__ */ jsx("span", { children: tr(
|
|
3993
|
+
"fluidgrids.runControl.archive.archived",
|
|
3994
|
+
"This run's history was archived and then purged by retention. It is in {{ref}}.",
|
|
3995
|
+
{
|
|
3996
|
+
ref: archive.data.archiveRef ?? tr(
|
|
3997
|
+
"fluidgrids.runControl.archive.unknownRef",
|
|
3998
|
+
"the workspace's archive bucket"
|
|
3999
|
+
)
|
|
4000
|
+
}
|
|
4001
|
+
) })
|
|
4002
|
+
]
|
|
4003
|
+
}
|
|
4004
|
+
);
|
|
4005
|
+
}
|
|
4006
|
+
function useCodecServer(enabled) {
|
|
4007
|
+
const settings = useResource(
|
|
4008
|
+
"codec-server",
|
|
4009
|
+
enabled,
|
|
4010
|
+
fetchCodecServerSettings
|
|
4011
|
+
);
|
|
4012
|
+
return useMemo(() => {
|
|
4013
|
+
const url = settings.data?.codecServerUrl;
|
|
4014
|
+
if (!url || !checkCodecServerUrl(url).ok) return null;
|
|
4015
|
+
return {
|
|
4016
|
+
url,
|
|
4017
|
+
includeCredentials: settings.data?.codecServerIncludeCredentials === true
|
|
4018
|
+
};
|
|
4019
|
+
}, [settings.data]);
|
|
4020
|
+
}
|
|
1984
4021
|
function formatTime(value) {
|
|
1985
4022
|
if (!value) return null;
|
|
1986
4023
|
const date = new Date(value);
|
|
@@ -2904,6 +4941,9 @@ function ExecutionDetailPage() {
|
|
|
2904
4941
|
);
|
|
2905
4942
|
const durableEnabled = useDurableFunctionNodesEnabled();
|
|
2906
4943
|
const parityEnabled = useDurableParityEnabled();
|
|
4944
|
+
const phaseC = usePhaseCFlags();
|
|
4945
|
+
const phaseCEnabled = phaseC.runControl || phaseC.searchAttributes || phaseC.selfHostedWorkers;
|
|
4946
|
+
const [resetPoint, setResetPoint] = useState(null);
|
|
2907
4947
|
const [showSignalDialog, setShowSignalDialog] = useState(false);
|
|
2908
4948
|
const [showOverrideDialog, setShowOverrideDialog] = useState(false);
|
|
2909
4949
|
const [minLogLevel, setMinLogLevel] = useState(null);
|
|
@@ -3018,10 +5058,20 @@ function ExecutionDetailPage() {
|
|
|
3018
5058
|
),
|
|
3019
5059
|
[execution?.nodeStates, execution?.triggerNodeId]
|
|
3020
5060
|
);
|
|
5061
|
+
const activityControls = isDurableRun && phaseC.runControlDurable;
|
|
5062
|
+
const runPending = useRunPending(runId, {
|
|
5063
|
+
enabled: (parityEnabled || activityControls) && runCanChange,
|
|
5064
|
+
live: isActive
|
|
5065
|
+
});
|
|
5066
|
+
const pendingActivities = runPending.pending?.activities ?? [];
|
|
5067
|
+
const activityControlsShown = activityControls && runCanChange && pendingActivities.length > 0;
|
|
3021
5068
|
const runHistory = useRunHistory(runId, {
|
|
3022
|
-
enabled: isDurableRun && activeTab === "history",
|
|
5069
|
+
enabled: isDurableRun && (activeTab === "history" || activityControlsShown),
|
|
3023
5070
|
live: isActive
|
|
3024
5071
|
});
|
|
5072
|
+
const codecServer = useCodecServer(
|
|
5073
|
+
isDurableRun && phaseC.selfHostedWorkers && activeTab === "history"
|
|
5074
|
+
);
|
|
3025
5075
|
const determinism = useDeterminismStatus(runId, {
|
|
3026
5076
|
enabled: isDurableRun && runCanChange,
|
|
3027
5077
|
live: isActive
|
|
@@ -3035,10 +5085,6 @@ function ExecutionDetailPage() {
|
|
|
3035
5085
|
error: runMetadata.error,
|
|
3036
5086
|
refetch: runMetadata.refetch
|
|
3037
5087
|
} : stackTrace;
|
|
3038
|
-
const runPending = useRunPending(runId, {
|
|
3039
|
-
enabled: parityEnabled && runCanChange,
|
|
3040
|
-
live: isActive
|
|
3041
|
-
});
|
|
3042
5088
|
const parityRun = parityEnabled && execution && parityFields.fields?.id === execution.id ? parityFields.fields : null;
|
|
3043
5089
|
const signalTargets = useMemo(
|
|
3044
5090
|
() => durableEnabled && execution ? runSignalTargets(execution) : { targets: [], openNames: false },
|
|
@@ -3716,6 +5762,32 @@ function ExecutionDetailPage() {
|
|
|
3716
5762
|
onViewChildRun: handleViewChildRun
|
|
3717
5763
|
}
|
|
3718
5764
|
),
|
|
5765
|
+
phaseCEnabled && /* @__PURE__ */ jsx(
|
|
5766
|
+
RunControlSection,
|
|
5767
|
+
{
|
|
5768
|
+
run: {
|
|
5769
|
+
id: execution.id,
|
|
5770
|
+
status: execution.status,
|
|
5771
|
+
executionKind: isDurableRun ? "DURABLE_FN" : "GRAPH",
|
|
5772
|
+
workflowFn: execution.workflowFn,
|
|
5773
|
+
taskQueue: execution.taskQueue,
|
|
5774
|
+
definitionJson: execution.workflow?.definitionJson
|
|
5775
|
+
},
|
|
5776
|
+
flags: phaseC,
|
|
5777
|
+
canControl: canControlRuns,
|
|
5778
|
+
activities: activityControlsShown ? pendingActivities : [],
|
|
5779
|
+
workflowTaskWaiting: runPending.pending?.workflowTask?.state === "SCHEDULED",
|
|
5780
|
+
historyEvents: activityControlsShown ? runHistory.events : [],
|
|
5781
|
+
signalNames: signalTargets.targets.map((target) => target.name),
|
|
5782
|
+
onChanged: () => {
|
|
5783
|
+
void refetch();
|
|
5784
|
+
void runPending.refetch();
|
|
5785
|
+
},
|
|
5786
|
+
onOpenRun: (newRunId) => navigate(`../runs/${newRunId}`),
|
|
5787
|
+
resetPoint,
|
|
5788
|
+
onResetPointChange: setResetPoint
|
|
5789
|
+
}
|
|
5790
|
+
),
|
|
3719
5791
|
/* @__PURE__ */ jsx("div", { className: "border-b border-border-secondary mb-6", children: /* @__PURE__ */ jsx("nav", { className: "flex gap-4 -mb-px overflow-x-auto", children: tabs.map((tab) => /* @__PURE__ */ jsxs(
|
|
3720
5792
|
"button",
|
|
3721
5793
|
{
|
|
@@ -3756,6 +5828,7 @@ function ExecutionDetailPage() {
|
|
|
3756
5828
|
}
|
|
3757
5829
|
) }),
|
|
3758
5830
|
activeTab === "history" && isDurableRun && /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4 sm:p-6", children: [
|
|
5831
|
+
phaseC.runArchival && /* @__PURE__ */ jsx(RunArchivedNotice, { runId: execution.id, enabled: true }),
|
|
3759
5832
|
runCanChange && /* @__PURE__ */ jsx(
|
|
3760
5833
|
WaitingAtPanel,
|
|
3761
5834
|
{
|
|
@@ -3778,7 +5851,24 @@ function ExecutionDetailPage() {
|
|
|
3778
5851
|
historyLength: runHistory.historyLength,
|
|
3779
5852
|
onRefresh: () => void runHistory.refetch(),
|
|
3780
5853
|
onViewChildRun: handleViewChildRun,
|
|
3781
|
-
showBenign: parityEnabled
|
|
5854
|
+
showBenign: parityEnabled,
|
|
5855
|
+
renderPayload: phaseC.selfHostedWorkers ? (event) => /* @__PURE__ */ jsx(
|
|
5856
|
+
PayloadView,
|
|
5857
|
+
{
|
|
5858
|
+
value: event.payload,
|
|
5859
|
+
codecServer
|
|
5860
|
+
}
|
|
5861
|
+
) : void 0,
|
|
5862
|
+
eventActions: phaseC.runControlDurable && canControlRuns ? (event) => /* @__PURE__ */ jsx(
|
|
5863
|
+
"button",
|
|
5864
|
+
{
|
|
5865
|
+
type: "button",
|
|
5866
|
+
className: "text-action-primary-text hover:underline",
|
|
5867
|
+
onClick: () => setResetPoint({ kind: "seq", seq: event.seq }),
|
|
5868
|
+
"data-testid": `history-reset-here-${event.seq}`,
|
|
5869
|
+
children: tr("fluidgrids.runControl.reset.here", "Reset here")
|
|
5870
|
+
}
|
|
5871
|
+
) : void 0
|
|
3782
5872
|
}
|
|
3783
5873
|
)
|
|
3784
5874
|
] }),
|
|
@@ -4009,4 +6099,4 @@ function ExecutionDetailPage() {
|
|
|
4009
6099
|
);
|
|
4010
6100
|
}
|
|
4011
6101
|
|
|
4012
|
-
export { ExecutionDetailPage, HumanRequestCard, RunHumanRequests, usePendingHumanRequests, useRespondToHumanRequest, useRunHumanRequests };
|
|
6102
|
+
export { ExecutionDetailPage, HumanRequestCard, RunHumanRequests, createTaskQueue, createWorkerToken, deleteTaskQueue, deleteWorkerToken, fetchTaskQueueStats, fetchTaskQueues, fetchWorkerTokens, fetchWorkflowWorkers, rotateWorkerToken, setWorkerTokenConnectionAllowlist, updateTaskQueue, usePendingHumanRequests, useRespondToHumanRequest, useRunHumanRequests };
|