@burdenoff/fluidgrids-fe-sdk 2026.1111.16 → 2026.1111.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +41 -0
  2. package/dist/AiStudioPage-kJHSsIcj.js +774 -0
  3. package/dist/ApprovalInboxPage-dbBDSJ-f.js +558 -0
  4. package/dist/DataSinkDashboardPage-1h2QsMzH.js +270 -0
  5. package/dist/DataTablesPage-BZy2z0LB.js +1690 -0
  6. package/dist/EntityTagPicker-BqRNsLCQ.js +135 -0
  7. package/dist/{ExecutionDetailPage-D-0_POAf.js → ExecutionDetailPage-BdcZmLT4.js} +799 -4420
  8. package/dist/ExecutionDetailPage-DvQ667OE.d.ts +10 -0
  9. package/dist/ExecutionsListPage-BCUJoAIE.js +2742 -0
  10. package/dist/{FailureTriagePanel-BHiduL7A.js → FailureTriagePanel-B9hDQymi.js} +4 -97
  11. package/dist/{FluidGridsProvider-DmvFADFa.js → FluidGridsProvider-dvZSNtSW.js} +10 -350
  12. package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
  13. package/dist/{HistoryPage-BbUgxx1I.js → HistoryPage-U0tqupBN.js} +6 -4
  14. package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
  15. package/dist/{InsightsPage-s6f5bU-8.js → InsightsPage-CALo16eB.js} +17 -328
  16. package/dist/ManagedAiPolicyAuthority-BxwJ3ptd.js +40 -0
  17. package/dist/{ManagedAiPolicyAuthority-CGux_DaV.d.ts → ManagedAiPolicyAuthority-DM3t3wNr.d.ts} +1 -5
  18. package/dist/ManagedAiPolicyAuthorityContext-CNkxIOZ1.js +10 -0
  19. package/dist/{MissingNodesInstallModal-B-9gHy2K.js → MissingNodesInstallModal-CMybsI8h.js} +3 -2
  20. package/dist/{NodesPage-ClvhUfbh.js → NodesPage-Bf5PhfBK.js} +7 -3
  21. package/dist/NodesPage-DEbnz9iU.d.ts +6 -0
  22. package/dist/{PublishCustomNodeAction-D2F7oN0S.js → PublishCustomNodeAction-5XXnSA3H.js} +3 -45
  23. package/dist/QuotaExhaustedDialog-7IFSaLyi.js +54 -0
  24. package/dist/RunnerStatusPill-Ctg4TJcP.js +187 -0
  25. package/dist/RunnersPage-CLCOCclc.js +467 -0
  26. package/dist/{ManagedAiPolicyAuthority-DsSaz-vg.js → SettingsPage-CgkRlCM5.js} +19 -50
  27. package/dist/SettingsPage-DMHQFj5-.d.ts +9 -0
  28. package/dist/TagFilterSelect-DCBOH6Ou.js +41 -0
  29. package/dist/TaskQueuesPage-9VgSnLdL.js +951 -0
  30. package/dist/TemplateCard-DsC0KNH1.js +1416 -0
  31. package/dist/{TemplateConfigurationModal-D_Mvgxqa.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
  32. package/dist/{TemplateDetailPanel-DfFfUcOe.js → TemplateDetailPanel-D4cUVr-m.js} +3 -3
  33. package/dist/TemplatePreview-GthzCOSo.js +231 -0
  34. package/dist/TemplatesPage-BG3H-6v6.d.ts +10 -0
  35. package/dist/{TemplatesPage-Cku13bav.js → TemplatesPage-CsDNT54N.js} +11 -228
  36. package/dist/VariablesPage-67WR_lVE.js +949 -0
  37. package/dist/{VersionDiffDialog-Dq2xlPFI.js → VersionDiffDialog-DSN0mU0f.js} +2 -1
  38. package/dist/WorkflowBuilder-DEi_9YOb.js +555 -0
  39. package/dist/{WorkflowSearchRedirect-CaoOOpEl.js → WorkflowBuilder-iPqXgbYr.js} +19 -585
  40. package/dist/{WorkflowProposalCard-8Vr56kQa.js → WorkflowProposalCard-qrEeNyYG.js} +4 -405
  41. package/dist/WorkflowSearchRedirect-DwDNZ_q5.js +36 -0
  42. package/dist/{WorkflowShareDialog-6RRDe-_v.js → WorkflowShareDialog-B_gn968c.js} +6 -52
  43. package/dist/{WorkflowsList-B2j1Pehb.js → WorkflowsList-CA20dVIl.js} +11 -6
  44. package/dist/WorkflowsList-CcNDQge5.d.ts +6 -0
  45. package/dist/agent-form-Bfvm32j1.js +83 -0
  46. package/dist/{agentTools-DJ5o2ekg.js → agentTools-CkRJB7r5.js} +3 -2
  47. package/dist/{ai-usage-D5p7ZeRR.js → ai-usage-glCwHyQz.js} +1 -1
  48. package/dist/api-BrCHB5oA.js +224 -0
  49. package/dist/api-C8e0iV-Q.js +55 -0
  50. package/dist/button-DG7y_FVJ.js +48 -0
  51. package/dist/{canvasNoteTools-D3zUdEeg.js → canvasNoteTools-CQIc7Oxg.js} +2 -2
  52. package/dist/client-C9XSzSXr.js +1147 -0
  53. package/dist/{codec-client-DstqyzTN.js → codec-client-CJ95Rmqo.js} +1 -1
  54. package/dist/constants-B2lhWNoN.js +202 -0
  55. package/dist/constants-XdvUxeWK.d.ts +162 -0
  56. package/dist/dialog-Cjb4lB4D.js +105 -0
  57. package/dist/executions.d.ts +1 -1
  58. package/dist/executions.js +2 -1
  59. package/dist/feature-flags-DM5myWaD.js +296 -0
  60. package/dist/flags.d.ts +3 -0
  61. package/dist/flags.js +4 -0
  62. package/dist/{flow-builder-DEzT8TQu.js → flow-builder-y2Y1iG4Y.js} +70 -1731
  63. package/dist/{node-endpoint-configs-DHFGA5MQ.js → graphql-operation-types-kX-MMjgW.js} +1 -1194
  64. package/dist/{graphql-types-XcDLtFwU.js → graphql-types-CDKVWH2R.js} +2 -202
  65. package/dist/history.d.ts +1 -1
  66. package/dist/history.js +1 -1
  67. package/dist/index-72XFgvbn.d.ts +132 -0
  68. package/dist/index-BcdtKBM-.d.ts +56 -0
  69. package/dist/{index-B3BWqafN.d.ts → index-DjiFgEQl.d.ts} +5 -131
  70. package/dist/{index-DRf25-D3.d.ts → index-W2o__Nm6.d.ts} +32 -214
  71. package/dist/index.d.ts +13 -9
  72. package/dist/index.js +50 -5300
  73. package/dist/{node-detail-view-LNqeIqN-.js → node-detail-view-B91kXzIy.js} +11 -8
  74. package/dist/node-endpoint-configs-B5bjNMDW.js +37 -0
  75. package/dist/node-registry-refresh-Dy0uRg9f.js +84 -0
  76. package/dist/nodes.d.ts +1 -1
  77. package/dist/nodes.js +1 -1
  78. package/dist/observability.d.ts +1 -1
  79. package/dist/observability.js +2 -1
  80. package/dist/{quick-config-panel-D3ogsaFL.js → quick-config-panel-DggPl1vY.js} +2 -2
  81. package/dist/rolldown-runtime-hDbaFkMy.js +18 -0
  82. package/dist/run-display-BycKYSU0.js +46 -0
  83. package/dist/run-list-links-ByYC0OlV.js +98 -0
  84. package/dist/settings.d.ts +2 -1
  85. package/dist/settings.js +2 -1
  86. package/dist/shared-CMfn30xT.js +196 -0
  87. package/dist/templates.d.ts +1 -1
  88. package/dist/templates.js +2 -1
  89. package/dist/theme-provider-B3mIuz2d.js +11 -0
  90. package/dist/{useAgentDefinitions-NFtLytDR.js → useAgentDefinitions-Dpz0HDTq.js} +2 -1
  91. package/dist/useDurableParity-CzUQMFJ3.js +415 -0
  92. package/dist/useEntityTags-DhnsZcTl.js +236 -0
  93. package/dist/{useExecutions-DAzeWHS6.js → useExecutions-9aLuOt1c.js} +4 -3
  94. package/dist/{useObservability-COCZ0hUS.js → useObservability-CfEw3ZFD.js} +2 -2
  95. package/dist/{useRunHistory-CsYUbouO.js → useRunHistory-B1xJDwl1.js} +4 -3
  96. package/dist/{useWorkflows-w4Dx2fF2.js → useWorkflows-BlVVu8NM.js} +3 -2
  97. package/dist/utils-uaL5UVxb.js +65 -0
  98. package/dist/workflow-builder.d.ts +1 -1
  99. package/dist/workflow-builder.js +4 -2
  100. package/dist/workflow-host.d.ts +6 -0
  101. package/dist/workflow-host.js +9 -0
  102. package/dist/workflow-pages.d.ts +55 -0
  103. package/dist/workflow-pages.js +113 -0
  104. package/dist/workflows-list.d.ts +1 -1
  105. package/dist/workflows-list.js +1 -1
  106. package/package.json +13 -1
  107. package/dist/ExecutionDetailPage-MvsnOnE1.d.ts +0 -4
  108. package/dist/HistoryPage-BqUizaFv.d.ts +0 -3
  109. package/dist/NodesPage-C97-9K2Y.d.ts +0 -3
  110. package/dist/TemplatesPage-D0qnXzHh.d.ts +0 -4
  111. package/dist/WorkflowsList-0d5GSKdh.d.ts +0 -3
  112. package/dist/agent-form-BvMWhQ42.js +0 -496
@@ -0,0 +1,187 @@
1
+ "use client";
2
+ import { _ as throwIfResultError, n as getFluidGridsClient } from "./client-C9XSzSXr.js";
3
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
+ import { cn } from "@burdenoff/fe-libs/utils";
6
+ import { jsx } from "react/jsx-runtime";
7
+ import { gql } from "@apollo/client";
8
+
9
+ // src/modules/runners/hooks/useRunners.ts
10
+ /**
11
+ * Self-hosted runners (wspace-sandbox-svc): list, add, rotate token, remove.
12
+ *
13
+ * A runner is a program the customer installs on their own machine; workflow
14
+ * nodes whose "Run on" names it execute there, in a local sandbox. Mirrors the
15
+ * connections/variables hook pattern (shared Apollo client, no generated hooks).
16
+ */
17
+ const RUNNER_FIELDS = gql`
18
+ fragment SandboxRunnerFields on SandboxRunner {
19
+ id
20
+ name
21
+ status
22
+ hostname
23
+ os
24
+ arch
25
+ driver
26
+ version
27
+ capacity
28
+ privateNetwork {
29
+ target
30
+ ports
31
+ nodes
32
+ }
33
+ tokenPrefix
34
+ lastSeenAt
35
+ connectedAt
36
+ createdBy
37
+ createdAt
38
+ }
39
+ `;
40
+ const LIST_RUNNERS = gql`
41
+ ${RUNNER_FIELDS}
42
+ query SandboxRunners {
43
+ sandboxRunners(context: {}) {
44
+ ...SandboxRunnerFields
45
+ }
46
+ }
47
+ `;
48
+ const CREATE_RUNNER = gql`
49
+ ${RUNNER_FIELDS}
50
+ mutation CreateSandboxRunner($name: String!) {
51
+ createSandboxRunner(input: { name: $name, context: {} }) {
52
+ token
53
+ bridgeUrl
54
+ runner {
55
+ ...SandboxRunnerFields
56
+ }
57
+ }
58
+ }
59
+ `;
60
+ const ROTATE_RUNNER_TOKEN = gql`
61
+ ${RUNNER_FIELDS}
62
+ mutation RotateSandboxRunnerToken($id: ID!) {
63
+ rotateSandboxRunnerToken(id: $id, context: {}) {
64
+ token
65
+ bridgeUrl
66
+ runner {
67
+ ...SandboxRunnerFields
68
+ }
69
+ }
70
+ }
71
+ `;
72
+ const DELETE_RUNNER = gql`
73
+ mutation DeleteSandboxRunner($id: ID!) {
74
+ deleteSandboxRunner(id: $id, context: {})
75
+ }
76
+ `;
77
+ /** Where the installer is published (fluidgrids-runner release workflow). */
78
+ const RUNNER_INSTALL_SCRIPT_URL = "https://www.fluidgrids.ai/downloads/runner/install.sh";
79
+ /**
80
+ * The one-line install command shown after creating a runner. It carries NO
81
+ * token: the installer asks for it (input hidden), so the token never lands in
82
+ * shell history or the machine's process list.
83
+ */
84
+ function runnerInstallCommand(bridgeUrl) {
85
+ const base = `curl -fsSL ${RUNNER_INSTALL_SCRIPT_URL} | sh`;
86
+ return bridgeUrl && bridgeUrl !== "wss://graphqlworkspaces.burdenoff.com/runner-bridge" ? `${base} -s -- --url ${bridgeUrl}` : base;
87
+ }
88
+ /** The runner's built-in default; omitted from the command when it matches. */
89
+ const DEFAULT_BRIDGE_URL = "wss://graphqlworkspaces.burdenoff.com/runner-bridge";
90
+ /**
91
+ * Runners in the current workspace. Polls while mounted so ONLINE/OFFLINE
92
+ * stays current (a runner heartbeats every 20 s).
93
+ */
94
+ function useRunners(options) {
95
+ const [runners, setRunners] = useState([]);
96
+ const [loading, setLoading] = useState(!options?.skip);
97
+ const [error, setError] = useState(null);
98
+ const mounted = useRef(true);
99
+ const fetchRunners = useCallback(async () => {
100
+ if (options?.skip) return;
101
+ try {
102
+ const result = await getFluidGridsClient().query({
103
+ query: LIST_RUNNERS,
104
+ fetchPolicy: "network-only"
105
+ });
106
+ if (!mounted.current) return;
107
+ setRunners(result.data?.sandboxRunners ?? []);
108
+ setError(null);
109
+ } catch (err) {
110
+ if (mounted.current) setError(err);
111
+ } finally {
112
+ if (mounted.current) setLoading(false);
113
+ }
114
+ }, [options?.skip]);
115
+ useEffect(() => {
116
+ mounted.current = true;
117
+ fetchRunners();
118
+ const pollMs = options?.pollMs ?? 1e4;
119
+ const timer = pollMs > 0 ? setInterval(fetchRunners, pollMs) : null;
120
+ return () => {
121
+ mounted.current = false;
122
+ if (timer) clearInterval(timer);
123
+ };
124
+ }, [fetchRunners, options?.pollMs]);
125
+ return {
126
+ runners,
127
+ loading,
128
+ error,
129
+ refetch: fetchRunners
130
+ };
131
+ }
132
+ function useRunnerActions() {
133
+ const [busy, setBusy] = useState(false);
134
+ const run = useCallback(async (fn) => {
135
+ setBusy(true);
136
+ try {
137
+ return await fn();
138
+ } finally {
139
+ setBusy(false);
140
+ }
141
+ }, []);
142
+ return {
143
+ createRunner: useCallback((name) => run(async () => {
144
+ const result = throwIfResultError(await getFluidGridsClient().mutate({
145
+ mutation: CREATE_RUNNER,
146
+ variables: { name }
147
+ }));
148
+ if (!result.data?.createSandboxRunner) throw new Error("No runner returned");
149
+ return result.data.createSandboxRunner;
150
+ }), [run]),
151
+ rotateToken: useCallback((id) => run(async () => {
152
+ const result = throwIfResultError(await getFluidGridsClient().mutate({
153
+ mutation: ROTATE_RUNNER_TOKEN,
154
+ variables: { id }
155
+ }));
156
+ if (!result.data?.rotateSandboxRunnerToken) throw new Error("No runner returned");
157
+ return result.data.rotateSandboxRunnerToken;
158
+ }), [run]),
159
+ deleteRunner: useCallback((id) => run(async () => {
160
+ return throwIfResultError(await getFluidGridsClient().mutate({
161
+ mutation: DELETE_RUNNER,
162
+ variables: { id }
163
+ })).data?.deleteSandboxRunner === true;
164
+ }), [run]),
165
+ busy
166
+ };
167
+ }
168
+
169
+ // src/modules/runners/components/RunnerStatusPill.tsx
170
+ const STYLES = {
171
+ ONLINE: "border-status-success-border bg-status-success-bg-subtle text-status-success-text",
172
+ OFFLINE: "border-border-default bg-bg-sunken text-text-muted",
173
+ NEVER_CONNECTED: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
174
+ };
175
+ /** Online / Offline / Not connected yet. */
176
+ function RunnerStatusPill({ status, className }) {
177
+ const tr = useTr();
178
+ const label = status === "ONLINE" ? tr("fluidgrids.runners.online", "Online") : status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet");
179
+ return /* @__PURE__ */ jsx("span", {
180
+ className: cn("inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium", STYLES[status], className),
181
+ "data-testid": "runner-status",
182
+ "data-status": status,
183
+ children: label
184
+ });
185
+ }
186
+
187
+ export { useRunners as a, useRunnerActions as i, RUNNER_INSTALL_SCRIPT_URL as n, runnerInstallCommand as r, RunnerStatusPill as t };
@@ -0,0 +1,467 @@
1
+ "use client";
2
+ import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
3
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
4
+ import { i as SdkPermissionButton, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
5
+ import { a as useRunners, i as useRunnerActions, r as runnerInstallCommand, t as RunnerStatusPill } from "./RunnerStatusPill-Ctg4TJcP.js";
6
+ import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
7
+ import { useCallback, useEffect, useState } from "react";
8
+ import { Icon } from "@iconify/react";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ import { toast } from "sonner";
11
+ import { Badge, Card, CardHeader, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@burdenoff/fe-libs/ui";
12
+ import { Button as Button$1 } from "@burdenoff/fe-libs/button";
13
+
14
+ // src/modules/runners/pages/RunnersPage.tsx
15
+ /**
16
+ * Runners Page — self-hosted runners.
17
+ *
18
+ * A runner is a small program a customer installs on their own VM or laptop.
19
+ * It connects out to FluidGrids, and any workflow node whose Settings →
20
+ * "Run on" names it executes on that machine, inside a local sandbox.
21
+ *
22
+ * Adding a runner issues a token that is shown ONCE, inside a ready-to-paste
23
+ * install command; the dialog then waits for the runner to come online.
24
+ */
25
+ var RunnersPage_exports = /* @__PURE__ */ __exportAll({
26
+ RunnersPage: () => RunnersPage,
27
+ describePrivateRule: () => describePrivateRule
28
+ });
29
+ function formatSeen(iso) {
30
+ if (!iso) return "—";
31
+ const seconds = Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 1e3));
32
+ if (seconds < 60) return `${seconds}s ago`;
33
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m ago`;
34
+ if (seconds < 86400) return `${Math.round(seconds / 3600)}h ago`;
35
+ return new Date(iso).toLocaleDateString();
36
+ }
37
+ /** Rules shown inline before folding the rest behind "+N more". */
38
+ const PRIVATE_NETWORK_INLINE = 3;
39
+ /** "10.0.5.12:5432" / "db.corp (all ports)" for one rule. */
40
+ function describePrivateRule(rule, allPorts) {
41
+ return rule.ports.length > 0 ? `${rule.target}:${rule.ports.join(",")}` : `${rule.target} (${allPorts})`;
42
+ }
43
+ /**
44
+ * What a runner's nodes may reach besides the public internet. Read-only: the
45
+ * list is set on the runner machine (`fluidgrids-runner network allow`), never
46
+ * here, so a FluidGrids account cannot open a path into the customer's network.
47
+ */
48
+ function PrivateNetworkCell({ rules }) {
49
+ const tr = useTr();
50
+ const [expanded, setExpanded] = useState(false);
51
+ if (rules.length === 0) return /* @__PURE__ */ jsx("span", {
52
+ "data-testid": "runner-private-network-none",
53
+ children: tr("fluidgrids.runners.publicOnly", "Public internet only")
54
+ });
55
+ const allPorts = tr("fluidgrids.runners.allPorts", "all ports");
56
+ const everyNode = tr("fluidgrids.runners.everyNode", "every node");
57
+ const hidden = rules.length - PRIVATE_NETWORK_INLINE;
58
+ const shown = expanded || hidden <= 0 ? rules : rules.slice(0, PRIVATE_NETWORK_INLINE);
59
+ return /* @__PURE__ */ jsxs("div", {
60
+ "data-testid": "runner-private-network",
61
+ children: [/* @__PURE__ */ jsx("ul", {
62
+ className: "space-y-0.5",
63
+ children: shown.map((rule, index) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
64
+ className: "font-mono text-xs text-text-primary",
65
+ children: describePrivateRule(rule, allPorts)
66
+ }), /* @__PURE__ */ jsx("span", {
67
+ className: "ml-1 text-xs",
68
+ children: rule.nodes.length > 0 ? rule.nodes.join(", ") : everyNode
69
+ })] }, `${rule.target}-${index}`))
70
+ }), hidden > 0 && /* @__PURE__ */ jsx(Button$1, {
71
+ variant: "link",
72
+ size: "sm",
73
+ className: "h-auto p-0 text-xs",
74
+ "aria-expanded": expanded,
75
+ onClick: () => setExpanded((open) => !open),
76
+ "data-testid": "runner-private-network-toggle",
77
+ children: expanded ? tr("fluidgrids.runners.showFewer", "Show fewer") : tr("fluidgrids.runners.moreRules", "+{{count}} more", { count: hidden })
78
+ })]
79
+ });
80
+ }
81
+ /** Install command + copy button, with the "shown once" warning. */
82
+ function CopyableValue({ value, copied, testId, copyTestId }) {
83
+ const tr = useTr();
84
+ const copy = useCallback(async () => {
85
+ try {
86
+ await navigator.clipboard.writeText(value);
87
+ toast.success(copied);
88
+ } catch {
89
+ toast.error(tr("fluidgrids.runners.copyFailed", "Could not copy — select the text and copy it manually"));
90
+ }
91
+ }, [
92
+ value,
93
+ copied,
94
+ tr
95
+ ]);
96
+ return /* @__PURE__ */ jsxs("div", {
97
+ className: "flex items-start gap-2",
98
+ children: [/* @__PURE__ */ jsx("pre", {
99
+ className: "flex-1 overflow-x-auto whitespace-pre-wrap break-all rounded-md border border-border bg-muted p-3 font-mono text-xs",
100
+ "data-testid": testId,
101
+ children: value
102
+ }), /* @__PURE__ */ jsxs(Button$1, {
103
+ variant: "outline",
104
+ size: "sm",
105
+ onClick: copy,
106
+ "data-testid": copyTestId,
107
+ children: [/* @__PURE__ */ jsx(Icon, {
108
+ icon: "lucide:copy",
109
+ className: "mr-1 h-4 w-4"
110
+ }), tr("fluidgrids.runners.copy", "Copy")]
111
+ })]
112
+ });
113
+ }
114
+ /**
115
+ * Install command + token, kept apart: the command carries no token, and the
116
+ * installer asks for it with hidden input — so the token never lands in shell
117
+ * history or the machine's process list.
118
+ */
119
+ function InstallCommand({ issued }) {
120
+ const tr = useTr();
121
+ const command = runnerInstallCommand(issued.bridgeUrl);
122
+ return /* @__PURE__ */ jsxs("div", {
123
+ className: "space-y-3",
124
+ children: [
125
+ /* @__PURE__ */ jsxs("div", {
126
+ className: "space-y-2",
127
+ children: [/* @__PURE__ */ jsx("p", {
128
+ className: "text-sm text-text-secondary",
129
+ children: tr("fluidgrids.runners.installHint", "1. Run this on the machine that should run your nodes (Linux, or a Mac with Apple silicon). It installs the runner and its sandbox, and starts it in the background.")
130
+ }), /* @__PURE__ */ jsx(CopyableValue, {
131
+ value: command,
132
+ copied: tr("fluidgrids.runners.copied", "Install command copied"),
133
+ testId: "runner-install-command",
134
+ copyTestId: "runner-copy-command"
135
+ })]
136
+ }),
137
+ /* @__PURE__ */ jsxs("div", {
138
+ className: "space-y-2",
139
+ children: [/* @__PURE__ */ jsx("p", {
140
+ className: "text-sm text-text-secondary",
141
+ children: tr("fluidgrids.runners.tokenHint", "2. Paste this token when the installer asks for it. It is not part of the command, so it never ends up in your shell history.")
142
+ }), /* @__PURE__ */ jsx(CopyableValue, {
143
+ value: issued.token,
144
+ copied: tr("fluidgrids.runners.tokenCopied", "Token copied"),
145
+ testId: "runner-token",
146
+ copyTestId: "runner-copy-token"
147
+ })]
148
+ }),
149
+ /* @__PURE__ */ jsx("p", {
150
+ className: "text-xs text-status-warning-text",
151
+ children: tr("fluidgrids.runners.tokenOnce", "The token is shown only once. Rotate it from the runner list if it is lost.")
152
+ }),
153
+ /* @__PURE__ */ jsx("p", {
154
+ className: "text-xs text-text-secondary",
155
+ children: tr("fluidgrids.runners.tokenAutomation", "Installing without a terminal (cloud-init, scripts)? Save the token to a file only you can read and add --token-file <path> to the command.")
156
+ })
157
+ ]
158
+ });
159
+ }
160
+ /** Add-runner dialog: name → command → waits for the runner to connect. */
161
+ function AddRunnerDialog({ open, onOpenChange, runners, onCreated }) {
162
+ const tr = useTr();
163
+ const { createRunner, busy } = useRunnerActions();
164
+ const [name, setName] = useState("");
165
+ const [issued, setIssued] = useState(null);
166
+ useEffect(() => {
167
+ if (!open) {
168
+ setName("");
169
+ setIssued(null);
170
+ }
171
+ }, [open]);
172
+ const live = issued ? runners.find((r) => r.id === issued.runner.id) : void 0;
173
+ const submit = useCallback(async () => {
174
+ try {
175
+ setIssued(await createRunner(name.trim()));
176
+ onCreated();
177
+ } catch (err) {
178
+ toast.error(err instanceof Error ? err.message : tr("fluidgrids.runners.createFailed", "Could not add the runner"));
179
+ }
180
+ }, [
181
+ createRunner,
182
+ name,
183
+ onCreated,
184
+ tr
185
+ ]);
186
+ return /* @__PURE__ */ jsx(Dialog, {
187
+ open,
188
+ onOpenChange,
189
+ children: /* @__PURE__ */ jsxs(DialogContent, {
190
+ className: "sm:max-w-2xl",
191
+ "data-testid": "add-runner-dialog",
192
+ children: [
193
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runners.addTitle", "Add a runner") }) }),
194
+ !issued ? /* @__PURE__ */ jsxs("div", {
195
+ className: "space-y-2",
196
+ children: [/* @__PURE__ */ jsx(Label, {
197
+ htmlFor: "runner-name",
198
+ children: tr("fluidgrids.runners.name", "Name")
199
+ }), /* @__PURE__ */ jsx(Input, {
200
+ id: "runner-name",
201
+ value: name,
202
+ maxLength: 100,
203
+ placeholder: tr("fluidgrids.runners.namePlaceholder", "e.g. build-server or my-laptop"),
204
+ onChange: (e) => setName(e.target.value),
205
+ onKeyDown: (e) => {
206
+ if (e.key === "Enter" && name.trim()) submit();
207
+ },
208
+ "data-testid": "runner-name-input"
209
+ })]
210
+ }) : /* @__PURE__ */ jsxs("div", {
211
+ className: "space-y-4",
212
+ children: [/* @__PURE__ */ jsx(InstallCommand, { issued }), /* @__PURE__ */ jsx("div", {
213
+ className: "flex items-center gap-2 text-sm",
214
+ "data-testid": "runner-connect-status",
215
+ children: live?.status === "ONLINE" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
216
+ icon: "lucide:check-circle-2",
217
+ className: "h-4 w-4 text-status-success-text"
218
+ }), tr("fluidgrids.runners.connected", "Connected. You can now pick this runner in a node's Settings → Run on.")] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
219
+ icon: "lucide:loader-2",
220
+ className: "h-4 w-4 animate-spin text-text-secondary"
221
+ }), tr("fluidgrids.runners.waiting", "Waiting for the runner to connect…")] })
222
+ })]
223
+ }),
224
+ /* @__PURE__ */ jsx(DialogFooter, { children: !issued ? /* @__PURE__ */ jsx(Button$1, {
225
+ onClick: submit,
226
+ disabled: busy || !name.trim(),
227
+ "data-testid": "runner-create-submit",
228
+ children: tr("fluidgrids.runners.create", "Create runner")
229
+ }) : /* @__PURE__ */ jsx(Button$1, {
230
+ variant: "outline",
231
+ onClick: () => onOpenChange(false),
232
+ "data-testid": "runner-dialog-done",
233
+ children: tr("fluidgrids.runners.done", "Done")
234
+ }) })
235
+ ]
236
+ })
237
+ });
238
+ }
239
+ function RunnersPage() {
240
+ const tr = useTr();
241
+ const { runners, loading, error, refetch } = useRunners({ pollMs: 5e3 });
242
+ const { rotateToken, deleteRunner, busy } = useRunnerActions();
243
+ const [adding, setAdding] = useState(false);
244
+ const [rotated, setRotated] = useState(null);
245
+ const [removing, setRemoving] = useState(null);
246
+ const handleRotate = useCallback(async (runner) => {
247
+ try {
248
+ setRotated(await rotateToken(runner.id));
249
+ refetch();
250
+ } catch (err) {
251
+ toast.error(err instanceof Error ? err.message : tr("fluidgrids.runners.rotateFailed", "Could not rotate the token"));
252
+ }
253
+ }, [
254
+ refetch,
255
+ rotateToken,
256
+ tr
257
+ ]);
258
+ const handleRemove = useCallback(async () => {
259
+ if (!removing) return;
260
+ try {
261
+ await deleteRunner(removing.id);
262
+ toast.success(tr("fluidgrids.runners.removed", "Runner removed"));
263
+ setRemoving(null);
264
+ refetch();
265
+ } catch (err) {
266
+ toast.error(err instanceof Error ? err.message : tr("fluidgrids.runners.removeFailed", "Could not remove the runner"));
267
+ }
268
+ }, [
269
+ deleteRunner,
270
+ refetch,
271
+ removing,
272
+ tr
273
+ ]);
274
+ return /* @__PURE__ */ jsxs("div", {
275
+ className: "space-y-6 p-6",
276
+ "data-testid": "runners-page",
277
+ children: [
278
+ /* @__PURE__ */ jsxs("div", {
279
+ className: "flex items-start justify-between gap-4",
280
+ "data-tour": "fluidgrids-runners-header",
281
+ children: [/* @__PURE__ */ jsxs("div", { children: [
282
+ /* @__PURE__ */ jsx("h1", {
283
+ className: "text-2xl font-semibold text-text-primary",
284
+ children: tr("fluidgrids.runners.title", "Runners")
285
+ }),
286
+ /* @__PURE__ */ jsx("p", {
287
+ className: "mt-1 max-w-2xl text-sm text-text-secondary",
288
+ children: tr("fluidgrids.runners.subtitle", "Run workflow nodes on your own machines. A node set to Run on a runner executes there, inside a local sandbox; the rest of the workflow keeps running in FluidGrids.")
289
+ }),
290
+ /* @__PURE__ */ jsxs("p", {
291
+ className: "mt-1 max-w-2xl text-sm text-text-secondary",
292
+ "data-testid": "runners-private-network-hint",
293
+ children: [
294
+ tr("fluidgrids.runners.privateNetworkHint", "Nodes reach the public internet only. To let them reach something inside your network, open it on the runner machine:"),
295
+ " ",
296
+ /* @__PURE__ */ jsx("code", {
297
+ className: "rounded bg-muted px-1 font-mono text-xs text-text-primary",
298
+ children: "fluidgrids-runner network allow 10.0.5.12 --port 5432"
299
+ })
300
+ ]
301
+ }),
302
+ /* @__PURE__ */ jsx(FluidGridsDocsLink, {
303
+ topic: "runners",
304
+ className: "mt-1"
305
+ })
306
+ ] }), /* @__PURE__ */ jsx(SdkPermissionButton, {
307
+ permission: FLUIDGRIDS_PERMISSIONS.SANDBOX_CREATE,
308
+ deniedTooltip: tr("fluidgrids.runners.addDeniedTooltip", "You don't have permission to add runners"),
309
+ children: /* @__PURE__ */ jsxs(Button$1, {
310
+ onClick: () => setAdding(true),
311
+ "data-testid": "runner-add",
312
+ "data-tour": "fluidgrids-runners-add",
313
+ children: [/* @__PURE__ */ jsx(Icon, {
314
+ icon: "lucide:plus",
315
+ className: "mr-1 h-4 w-4"
316
+ }), tr("fluidgrids.runners.add", "Add runner")]
317
+ })
318
+ })]
319
+ }),
320
+ error && /* @__PURE__ */ jsx("div", {
321
+ className: "rounded-md border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text",
322
+ children: error.message
323
+ }),
324
+ /* @__PURE__ */ jsx(Card, {
325
+ "data-tour": "fluidgrids-runners-table",
326
+ children: /* @__PURE__ */ jsx(CardHeader, {
327
+ className: "p-0",
328
+ children: /* @__PURE__ */ jsxs(Table, { children: [/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
329
+ /* @__PURE__ */ jsx(TableHead, { children: tr("fluidgrids.runners.name", "Name") }),
330
+ /* @__PURE__ */ jsx(TableHead, { children: tr("fluidgrids.runners.status", "Status") }),
331
+ /* @__PURE__ */ jsx(TableHead, { children: tr("fluidgrids.runners.machine", "Machine") }),
332
+ /* @__PURE__ */ jsx(TableHead, { children: tr("fluidgrids.runners.sandbox", "Sandbox") }),
333
+ /* @__PURE__ */ jsx(TableHead, { children: tr("fluidgrids.runners.privateNetwork", "Private network") }),
334
+ /* @__PURE__ */ jsx(TableHead, { children: tr("fluidgrids.runners.lastSeen", "Last seen") }),
335
+ /* @__PURE__ */ jsx(TableHead, {
336
+ className: "text-right",
337
+ children: tr("fluidgrids.runners.actions", "Actions")
338
+ })
339
+ ] }) }), /* @__PURE__ */ jsxs(TableBody, { children: [runners.map((runner) => /* @__PURE__ */ jsxs(TableRow, {
340
+ "data-testid": "runner-row",
341
+ children: [
342
+ /* @__PURE__ */ jsxs(TableCell, { children: [/* @__PURE__ */ jsx("div", {
343
+ className: "font-medium text-text-primary",
344
+ children: runner.name
345
+ }), /* @__PURE__ */ jsxs("div", {
346
+ className: "font-mono text-xs text-text-secondary",
347
+ children: [runner.tokenPrefix, "…"]
348
+ })] }),
349
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(RunnerStatusPill, { status: runner.status }) }),
350
+ /* @__PURE__ */ jsx(TableCell, {
351
+ className: "text-sm text-text-secondary",
352
+ children: runner.hostname ? `${runner.hostname} · ${runner.os ?? "?"}/${runner.arch ?? "?"}` : "—"
353
+ }),
354
+ /* @__PURE__ */ jsxs(TableCell, {
355
+ className: "text-sm text-text-secondary",
356
+ children: [runner.driver ? /* @__PURE__ */ jsx(Badge, {
357
+ variant: "outline",
358
+ children: runner.driver
359
+ }) : "—", runner.version && /* @__PURE__ */ jsxs("span", {
360
+ className: "ml-2 text-xs",
361
+ children: ["v", runner.version]
362
+ })]
363
+ }),
364
+ /* @__PURE__ */ jsx(TableCell, {
365
+ className: "text-sm text-text-secondary",
366
+ children: /* @__PURE__ */ jsx(PrivateNetworkCell, { rules: runner.privateNetwork ?? [] })
367
+ }),
368
+ /* @__PURE__ */ jsx(TableCell, {
369
+ className: "text-sm text-text-secondary",
370
+ children: formatSeen(runner.lastSeenAt)
371
+ }),
372
+ /* @__PURE__ */ jsxs(TableCell, {
373
+ className: "text-right",
374
+ children: [/* @__PURE__ */ jsx(SdkPermissionButton, {
375
+ permission: FLUIDGRIDS_PERMISSIONS.SANDBOX_UPDATE,
376
+ deniedTooltip: tr("fluidgrids.runners.rotateDeniedTooltip", "You don't have permission to rotate runner tokens"),
377
+ children: /* @__PURE__ */ jsx(Button$1, {
378
+ "data-agent": "confirm",
379
+ variant: "ghost",
380
+ size: "sm",
381
+ title: tr("fluidgrids.runners.rotate", "Rotate token"),
382
+ onClick: () => handleRotate(runner),
383
+ disabled: busy,
384
+ "data-testid": "runner-rotate",
385
+ children: /* @__PURE__ */ jsx(Icon, {
386
+ icon: "lucide:key-round",
387
+ className: "h-4 w-4"
388
+ })
389
+ })
390
+ }), /* @__PURE__ */ jsx(SdkPermissionButton, {
391
+ permission: FLUIDGRIDS_PERMISSIONS.SANDBOX_DELETE,
392
+ deniedTooltip: tr("fluidgrids.runners.removeDeniedTooltip", "You don't have permission to remove runners"),
393
+ children: /* @__PURE__ */ jsx(Button$1, {
394
+ variant: "ghost",
395
+ size: "sm",
396
+ title: tr("fluidgrids.runners.remove", "Remove"),
397
+ onClick: () => setRemoving(runner),
398
+ disabled: busy,
399
+ "data-testid": "runner-remove",
400
+ children: /* @__PURE__ */ jsx(Icon, {
401
+ icon: "lucide:trash-2",
402
+ className: "h-4 w-4 text-destructive"
403
+ })
404
+ })
405
+ })]
406
+ })
407
+ ]
408
+ }, runner.id)), !loading && runners.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
409
+ colSpan: 7,
410
+ className: "py-10 text-center text-sm text-text-secondary",
411
+ children: tr("fluidgrids.runners.empty", "No runners yet. Add one to run nodes on your own machine.")
412
+ }) })] })] })
413
+ })
414
+ }),
415
+ /* @__PURE__ */ jsx(AddRunnerDialog, {
416
+ open: adding,
417
+ onOpenChange: setAdding,
418
+ runners,
419
+ onCreated: () => void refetch()
420
+ }),
421
+ /* @__PURE__ */ jsx(Dialog, {
422
+ open: rotated !== null,
423
+ onOpenChange: (open) => !open && setRotated(null),
424
+ children: /* @__PURE__ */ jsxs(DialogContent, {
425
+ className: "sm:max-w-2xl",
426
+ children: [
427
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runners.rotatedTitle", "New token issued") }) }),
428
+ /* @__PURE__ */ jsx("p", {
429
+ className: "text-sm text-text-secondary",
430
+ children: tr("fluidgrids.runners.rotatedHint", "The old token no longer works and the runner was disconnected. Re-run the command below on that machine and paste the new token when it asks.")
431
+ }),
432
+ rotated && /* @__PURE__ */ jsx(InstallCommand, { issued: rotated }),
433
+ /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button$1, {
434
+ variant: "outline",
435
+ onClick: () => setRotated(null),
436
+ children: tr("fluidgrids.runners.done", "Done")
437
+ }) })
438
+ ]
439
+ })
440
+ }),
441
+ /* @__PURE__ */ jsx(Dialog, {
442
+ open: removing !== null,
443
+ onOpenChange: (open) => !open && setRemoving(null),
444
+ children: /* @__PURE__ */ jsxs(DialogContent, { children: [
445
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.runners.removeTitle", "Remove this runner?") }) }),
446
+ /* @__PURE__ */ jsx("p", {
447
+ className: "text-sm text-text-secondary",
448
+ children: tr("fluidgrids.runners.removeHint", "It is disconnected and its token stops working. Nodes set to run on it will fail until you pick another runner or FluidGrids Cloud.")
449
+ }),
450
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button$1, {
451
+ variant: "outline",
452
+ onClick: () => setRemoving(null),
453
+ children: tr("fluidgrids.runners.cancel", "Cancel")
454
+ }), /* @__PURE__ */ jsx(Button$1, {
455
+ variant: "destructive",
456
+ onClick: handleRemove,
457
+ disabled: busy,
458
+ "data-testid": "runner-remove-confirm",
459
+ children: tr("fluidgrids.runners.remove", "Remove")
460
+ })] })
461
+ ] })
462
+ })
463
+ ]
464
+ });
465
+ }
466
+
467
+ export { RunnersPage_exports as n, RunnersPage as t };