@burdenoff/microfe-bigconsole 2026.812.2 → 2026.827.1
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/bigconsole/components/builder/inspector/LayoutSection.js +71 -0
- package/dist/bigconsole/components/builder/inspector/LayoutSection.js.map +1 -0
- package/dist/bigconsole/components/builder/inspector/index.js +1 -0
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js +137 -121
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +204 -93
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +259 -133
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +87 -39
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkCard.js +88 -68
- package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +213 -199
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowCard.js +46 -27
- package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +44 -47
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
- package/dist/bigconsole/customWidget/proposalSource.js +31 -24
- package/dist/bigconsole/customWidget/proposalSource.js.map +1 -1
- package/dist/bigconsole/hooks/useDashboardOperations.js +74 -64
- package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
- package/dist/bigconsole/hooks/useDataSinkOperations.js +2 -2
- package/dist/bigconsole/hooks/useDataSinkOperations.js.map +1 -1
- package/dist/bigconsole/pages/DashboardBuilderPage.js +1 -1
- package/dist/bigconsole/pages/DashboardsPage.js +386 -316
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js +251 -206
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/InsightsPage.js +646 -378
- package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowsPage.js +153 -108
- package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
- package/dist/bigconsole/utils/format.js +18 -0
- package/dist/bigconsole/utils/format.js.map +1 -0
- package/dist/bigconsole/utils/widgetPositioning.js +1 -1
- package/dist/generated/wspace-operations.js +1 -0
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,232 +10,277 @@ import { BIGCONSOLE_PERMISSIONS as s } from "../../constants/permissions.js";
|
|
|
10
10
|
import { isNative as c, nativeNotify as l } from "../../utils/nativeBridge.js";
|
|
11
11
|
import u from "../components/workflow/WorkflowDeleteDialog.js";
|
|
12
12
|
import d from "../components/workflow/WorkflowCard.js";
|
|
13
|
-
import { memo as f, useCallback as p, useMemo as
|
|
14
|
-
import { AccessDenied as
|
|
15
|
-
import { usePermissions as
|
|
16
|
-
import { AlertCircle as
|
|
17
|
-
import { useI18n as
|
|
18
|
-
import { AuroraBackground as
|
|
19
|
-
import { Fragment as
|
|
13
|
+
import { memo as f, useCallback as p, useMemo as ee, useState as m } from "react";
|
|
14
|
+
import { AccessDenied as h, PermissionButton as g } from "@burdenoff/fe-libs/shared/components";
|
|
15
|
+
import { usePermissions as _ } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
16
|
+
import { AlertCircle as v, Plus as y, Search as b, Workflow as x } from "lucide-react";
|
|
17
|
+
import { useI18n as te } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
18
|
+
import { AuroraBackground as S, Button as C, GradientText as w, IllustratedEmptyState as T, Input as E, PagePurpose as ne, Spinner as re } from "@burdenoff/fe-libs/ui";
|
|
19
|
+
import { Fragment as ie, jsx as D, jsxs as O } from "react/jsx-runtime";
|
|
20
20
|
//#region src/bigconsole/pages/WorkflowsPage.tsx
|
|
21
|
-
var
|
|
22
|
-
let f = t(), { t:
|
|
23
|
-
let e = [...
|
|
24
|
-
if (
|
|
25
|
-
let t =
|
|
21
|
+
var k = f(function() {
|
|
22
|
+
let f = t(), { t: k } = te(), { hasPermission: A, isLoading: j } = _(), M = A(s.WORKFLOW_READ), N = A(s.WORKFLOW_UPDATE), P = A(s.WORKFLOW_DELETE), F = A(s.WORKFLOW_EXECUTE), [I, L] = m("all"), [R, z] = m(null), B = e((e) => e.searchQuery), V = e((e) => e.setSearchQuery), H = e((e) => e.deleteDialogOpen), U = e((e) => e.currentWorkflow), W = e((e) => e.openDeleteDialog), G = e((e) => e.closeDeleteDialog), { loading: K, error: q, workflows: J, deleteWorkflow: Y, refetch: ae } = a(), { executeWorkflow: X } = a({ skipFetch: !0 }), oe = e((e) => e.workflowUIStates), Z = ee(() => {
|
|
23
|
+
let e = [...J];
|
|
24
|
+
if (I !== "all" && (e = e.filter((e) => e.status === I)), B.trim()) {
|
|
25
|
+
let t = B.toLowerCase();
|
|
26
26
|
e = e.filter((e) => e.name.toLowerCase().includes(t) || e.key.toLowerCase().includes(t) || e.description?.toLowerCase().includes(t));
|
|
27
27
|
}
|
|
28
28
|
return e.sort((e, t) => new Date(t.updatedAt).getTime() - new Date(e.updatedAt).getTime());
|
|
29
29
|
}, [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
]),
|
|
30
|
+
J,
|
|
31
|
+
I,
|
|
32
|
+
B
|
|
33
|
+
]), Q = p(() => {
|
|
34
34
|
f(o());
|
|
35
|
-
}, [f]),
|
|
35
|
+
}, [f]), se = p((e) => {
|
|
36
36
|
f(`/workflows/${e}`);
|
|
37
|
-
}, [f]),
|
|
37
|
+
}, [f]), ce = p((e) => {
|
|
38
38
|
f(`/workflows/${e}/edit`);
|
|
39
|
-
}, [f]),
|
|
40
|
-
let t =
|
|
41
|
-
t &&
|
|
42
|
-
}, [
|
|
43
|
-
|
|
39
|
+
}, [f]), le = p((e) => {
|
|
40
|
+
let t = J.find((t) => t.id === e);
|
|
41
|
+
t && W(t);
|
|
42
|
+
}, [J, W]), $ = p(async (e) => {
|
|
43
|
+
let t = J.find((t) => t.id === e);
|
|
44
|
+
if (t) {
|
|
45
|
+
if (z(null), await X(e)) {
|
|
46
|
+
c() && l("success"), z({
|
|
47
|
+
workflowName: t.name,
|
|
48
|
+
ok: !0,
|
|
49
|
+
message: k("bigconsole.workflows.run.triggered", "Run triggered. The execution count and last-run time update as the run progresses.")
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
c() && l("error"), z({
|
|
54
|
+
workflowName: t.name,
|
|
55
|
+
ok: !1,
|
|
56
|
+
message: k("bigconsole.workflows.run.failed", "The workflow could not be started. Nothing has been changed.")
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}, [
|
|
60
|
+
J,
|
|
61
|
+
X,
|
|
62
|
+
k
|
|
63
|
+
]), ue = p(async () => {
|
|
64
|
+
if (U) {
|
|
44
65
|
try {
|
|
45
|
-
await
|
|
66
|
+
await Y(U.id), c() && l("success");
|
|
46
67
|
} catch (e) {
|
|
47
68
|
throw c() && l("error"), e;
|
|
48
69
|
}
|
|
49
|
-
|
|
70
|
+
G();
|
|
50
71
|
}
|
|
51
72
|
}, [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
]),
|
|
73
|
+
U,
|
|
74
|
+
Y,
|
|
75
|
+
G
|
|
76
|
+
]), de = [
|
|
56
77
|
{
|
|
57
78
|
value: "all",
|
|
58
|
-
label:
|
|
79
|
+
label: k("bigconsole.workflows.filters.all", "All")
|
|
59
80
|
},
|
|
60
81
|
{
|
|
61
82
|
value: "draft",
|
|
62
|
-
label:
|
|
83
|
+
label: k("bigconsole.workflows.status.draft", "Draft")
|
|
63
84
|
},
|
|
64
85
|
{
|
|
65
86
|
value: "active",
|
|
66
|
-
label:
|
|
87
|
+
label: k("bigconsole.workflows.status.active", "Active")
|
|
67
88
|
},
|
|
68
89
|
{
|
|
69
90
|
value: "paused",
|
|
70
|
-
label:
|
|
91
|
+
label: k("bigconsole.workflows.status.paused", "Paused")
|
|
71
92
|
},
|
|
72
93
|
{
|
|
73
94
|
value: "error",
|
|
74
|
-
label:
|
|
95
|
+
label: k("bigconsole.workflows.status.error", "Error")
|
|
75
96
|
},
|
|
76
97
|
{
|
|
77
98
|
value: "archived",
|
|
78
|
-
label:
|
|
99
|
+
label: k("bigconsole.workflows.status.archived", "Archived")
|
|
79
100
|
}
|
|
80
101
|
];
|
|
81
|
-
if (!
|
|
82
|
-
title:
|
|
83
|
-
description:
|
|
102
|
+
if (!j && !M) return /* @__PURE__ */ D(h, {
|
|
103
|
+
title: k("bigconsole.workflows.errors.accessDeniedTitle", "Access Denied"),
|
|
104
|
+
description: k("bigconsole.workflows.errors.accessDeniedListDescription", "You don't have permission to view workflows.")
|
|
84
105
|
});
|
|
85
|
-
let
|
|
86
|
-
return /* @__PURE__ */
|
|
106
|
+
let fe = !K && J.length === 0, pe = !K && Z.length === 0 && J.length > 0;
|
|
107
|
+
return /* @__PURE__ */ O("div", {
|
|
87
108
|
className: "min-h-dvh bg-bg-canvas",
|
|
88
109
|
children: [
|
|
89
|
-
/* @__PURE__ */
|
|
110
|
+
/* @__PURE__ */ O("header", {
|
|
90
111
|
"data-section-emphasis": "bare",
|
|
91
112
|
className: "relative min-w-0 overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]",
|
|
92
|
-
children: [/* @__PURE__ */
|
|
113
|
+
children: [/* @__PURE__ */ D(S, { intensity: "subtle" }), /* @__PURE__ */ O("div", {
|
|
93
114
|
className: "relative flex flex-col gap-form-gap p-[var(--space-pagePadding)] sm:flex-row sm:items-center sm:justify-between",
|
|
94
|
-
children: [/* @__PURE__ */
|
|
115
|
+
children: [/* @__PURE__ */ O("div", {
|
|
95
116
|
className: "flex min-w-0 items-center gap-form-gap",
|
|
96
|
-
children: [/* @__PURE__ */
|
|
117
|
+
children: [/* @__PURE__ */ D("span", {
|
|
97
118
|
"aria-hidden": "true",
|
|
98
119
|
className: "flex h-control-lg w-[var(--size-controlHeight-lg)] shrink-0 items-center justify-center rounded-card bg-action-primary-bg",
|
|
99
|
-
children: /* @__PURE__ */
|
|
100
|
-
}), /* @__PURE__ */
|
|
120
|
+
children: /* @__PURE__ */ D(x, { className: "h-icon-lg w-icon-lg text-action-primary-text" })
|
|
121
|
+
}), /* @__PURE__ */ O("div", {
|
|
101
122
|
className: "min-w-0",
|
|
102
|
-
children: [/* @__PURE__ */
|
|
123
|
+
children: [/* @__PURE__ */ D("h1", {
|
|
103
124
|
className: "text-2xl font-bold text-text-primary",
|
|
104
|
-
children: /* @__PURE__ */
|
|
105
|
-
}), /* @__PURE__ */
|
|
125
|
+
children: /* @__PURE__ */ D(w, { children: k("bigconsole.workflows.list.title", "Workflows") })
|
|
126
|
+
}), /* @__PURE__ */ D("p", {
|
|
106
127
|
className: "text-sm text-text-secondary",
|
|
107
|
-
children:
|
|
128
|
+
children: k("bigconsole.workflows.list.subtitle", "Design ETL workflows with FluidGrids and link them to DataSinks")
|
|
108
129
|
})]
|
|
109
130
|
})]
|
|
110
|
-
}), /* @__PURE__ */
|
|
131
|
+
}), /* @__PURE__ */ D("div", {
|
|
111
132
|
className: "flex flex-wrap items-center gap-control-gap",
|
|
112
|
-
children: /* @__PURE__ */
|
|
133
|
+
children: /* @__PURE__ */ D(g, {
|
|
113
134
|
permission: s.WORKFLOW_CREATE,
|
|
114
135
|
deniedBehavior: "disable",
|
|
115
|
-
children: /* @__PURE__ */
|
|
116
|
-
onClick:
|
|
117
|
-
children: [/* @__PURE__ */
|
|
136
|
+
children: /* @__PURE__ */ O(C, {
|
|
137
|
+
onClick: Q,
|
|
138
|
+
children: [/* @__PURE__ */ D(y, { className: "h-icon-sm w-icon-sm" }), k("bigconsole.workflows.actions.designWorkflow", "Design Workflow")]
|
|
118
139
|
})
|
|
119
140
|
})
|
|
120
141
|
})]
|
|
121
142
|
})]
|
|
122
143
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
144
|
+
/* @__PURE__ */ D("div", {
|
|
124
145
|
className: "p-[var(--space-pagePadding)]",
|
|
125
|
-
children: /* @__PURE__ */
|
|
146
|
+
children: /* @__PURE__ */ O(i, { children: [/* @__PURE__ */ D(ne, { children: k("bigconsole.workflows.list.purpose", "Workflows are visual ETL jobs built with the FluidGrids builder — they fetch data from external systems, transform it, and load it into your BigConsole DataSinks on a schedule or on demand. Use them to keep dashboards fed with fresh, shaped data without writing glue code.") }), q ? /* @__PURE__ */ D(r, {
|
|
126
147
|
headingLevel: 2,
|
|
127
148
|
tone: "danger",
|
|
128
|
-
title:
|
|
149
|
+
title: k("bigconsole.workflows.errors.loadFailedTitle", "Failed to load Workflows"),
|
|
129
150
|
description: "The workflow service did not respond. Nothing has been changed — retry when you are ready.",
|
|
130
|
-
icon: /* @__PURE__ */
|
|
131
|
-
actions: /* @__PURE__ */
|
|
151
|
+
icon: /* @__PURE__ */ D(v, { className: "h-icon-md w-icon-md" }),
|
|
152
|
+
actions: /* @__PURE__ */ D(C, {
|
|
132
153
|
size: "sm",
|
|
133
|
-
onClick: () =>
|
|
134
|
-
children:
|
|
154
|
+
onClick: () => ae(),
|
|
155
|
+
children: k("bigconsole.workflows.actions.tryAgain", "Try Again")
|
|
135
156
|
}),
|
|
136
157
|
"data-testid": "workflows-error",
|
|
137
|
-
children: /* @__PURE__ */
|
|
158
|
+
children: /* @__PURE__ */ D("p", {
|
|
138
159
|
className: "break-words text-sm text-[var(--color-status-error-text)]",
|
|
139
|
-
children:
|
|
160
|
+
children: q.message
|
|
140
161
|
})
|
|
141
|
-
}) : /* @__PURE__ */
|
|
162
|
+
}) : /* @__PURE__ */ O(ie, { children: [/* @__PURE__ */ D(r, {
|
|
142
163
|
headingLevel: 2,
|
|
143
164
|
title: "Filters",
|
|
144
165
|
description: "Narrow the list by status, or search by name, key or description.",
|
|
145
166
|
padding: "compact",
|
|
146
167
|
"data-testid": "workflows-filters",
|
|
147
|
-
children: /* @__PURE__ */
|
|
168
|
+
children: /* @__PURE__ */ O("div", {
|
|
148
169
|
className: "flex flex-col gap-form-gap lg:flex-row lg:items-center lg:justify-between",
|
|
149
|
-
children: [/* @__PURE__ */
|
|
170
|
+
children: [/* @__PURE__ */ D("div", {
|
|
150
171
|
role: "group",
|
|
151
172
|
"aria-label": "Filter workflows by status",
|
|
152
173
|
className: "flex flex-wrap items-center gap-1 rounded-card bg-bg-sunken p-1",
|
|
153
|
-
children:
|
|
174
|
+
children: de.map((e) => /* @__PURE__ */ D("button", {
|
|
154
175
|
type: "button",
|
|
155
|
-
"aria-pressed":
|
|
156
|
-
onClick: () =>
|
|
157
|
-
className: `min-h-[var(--size-controlHeight-md)] rounded-button px-3 text-sm font-medium motion-safe:transition-colors ${n} ${
|
|
176
|
+
"aria-pressed": I === e.value,
|
|
177
|
+
onClick: () => L(e.value),
|
|
178
|
+
className: `min-h-[var(--size-controlHeight-md)] rounded-button px-3 text-sm font-medium motion-safe:transition-colors ${n} ${I === e.value ? "bg-bg-surface text-text-primary shadow-[var(--shadow-elevation-1)]" : "text-text-secondary hover:text-text-primary"}`,
|
|
158
179
|
children: e.label
|
|
159
180
|
}, e.value))
|
|
160
|
-
}), /* @__PURE__ */
|
|
181
|
+
}), /* @__PURE__ */ O("div", {
|
|
161
182
|
className: "relative w-full lg:max-w-[360px]",
|
|
162
|
-
children: [/* @__PURE__ */
|
|
183
|
+
children: [/* @__PURE__ */ D(b, {
|
|
163
184
|
"aria-hidden": "true",
|
|
164
185
|
className: "pointer-events-none absolute left-3 top-1/2 h-icon-sm w-icon-sm -translate-y-1/2 text-text-muted"
|
|
165
|
-
}), /* @__PURE__ */
|
|
186
|
+
}), /* @__PURE__ */ D(E, {
|
|
166
187
|
type: "text",
|
|
167
|
-
"aria-label":
|
|
168
|
-
placeholder:
|
|
169
|
-
value:
|
|
170
|
-
onChange: (e) =>
|
|
188
|
+
"aria-label": k("bigconsole.workflows.filters.searchPlaceholder", "Search workflows..."),
|
|
189
|
+
placeholder: k("bigconsole.workflows.filters.searchPlaceholder", "Search workflows..."),
|
|
190
|
+
value: B,
|
|
191
|
+
onChange: (e) => V(e.target.value),
|
|
171
192
|
className: "h-control-md pl-10"
|
|
172
193
|
})]
|
|
173
194
|
})]
|
|
174
195
|
})
|
|
175
|
-
}), /* @__PURE__ */
|
|
196
|
+
}), /* @__PURE__ */ O(r, {
|
|
176
197
|
headingLevel: 2,
|
|
177
198
|
emphasis: "primary",
|
|
178
|
-
title:
|
|
199
|
+
title: k("bigconsole.workflows.list.title", "Workflows"),
|
|
179
200
|
description: "Every workflow in this workspace, most recently updated first.",
|
|
180
|
-
meta: /* @__PURE__ */
|
|
201
|
+
meta: /* @__PURE__ */ O("span", {
|
|
181
202
|
className: "rounded-pill bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary",
|
|
182
|
-
children: [
|
|
203
|
+
children: [Z.length, Z.length === J.length ? "" : ` of ${J.length}`]
|
|
183
204
|
}),
|
|
184
205
|
"data-testid": "workflows-results",
|
|
185
206
|
children: [
|
|
186
|
-
|
|
207
|
+
K && J.length === 0 && /* @__PURE__ */ D("div", {
|
|
187
208
|
className: "flex min-h-[240px] items-center justify-center",
|
|
188
|
-
children: /* @__PURE__ */
|
|
209
|
+
children: /* @__PURE__ */ O("div", {
|
|
189
210
|
className: "text-center",
|
|
190
|
-
children: [/* @__PURE__ */
|
|
211
|
+
children: [/* @__PURE__ */ D(re, {
|
|
191
212
|
size: "lg",
|
|
192
213
|
className: "mb-form-gap"
|
|
193
|
-
}), /* @__PURE__ */
|
|
214
|
+
}), /* @__PURE__ */ D("p", {
|
|
194
215
|
className: "text-sm text-text-muted",
|
|
195
|
-
children:
|
|
216
|
+
children: k("bigconsole.workflows.list.loading", "Loading workflows...")
|
|
196
217
|
})]
|
|
197
218
|
})
|
|
198
219
|
}),
|
|
199
|
-
|
|
220
|
+
fe && /* @__PURE__ */ D(T, {
|
|
200
221
|
illustration: "empty-data",
|
|
201
|
-
title:
|
|
202
|
-
description:
|
|
203
|
-
action: /* @__PURE__ */
|
|
222
|
+
title: k("bigconsole.workflows.list.emptyTitle", "No Workflows yet"),
|
|
223
|
+
description: k("bigconsole.workflows.list.emptyDescription", "Design ETL workflows with the FluidGrids visual builder to fetch, transform, and deliver data into BigConsole DataSinks."),
|
|
224
|
+
action: /* @__PURE__ */ D(g, {
|
|
204
225
|
permission: s.WORKFLOW_CREATE,
|
|
205
226
|
deniedBehavior: "disable",
|
|
206
|
-
children: /* @__PURE__ */
|
|
207
|
-
onClick:
|
|
208
|
-
children: [/* @__PURE__ */
|
|
227
|
+
children: /* @__PURE__ */ O(C, {
|
|
228
|
+
onClick: Q,
|
|
229
|
+
children: [/* @__PURE__ */ D(y, { className: "h-icon-sm w-icon-sm" }), k("bigconsole.workflows.actions.designWorkflow", "Design Workflow")]
|
|
209
230
|
})
|
|
210
231
|
})
|
|
211
232
|
}),
|
|
212
|
-
|
|
233
|
+
pe && /* @__PURE__ */ D(T, {
|
|
213
234
|
illustration: "empty-search",
|
|
214
|
-
title:
|
|
215
|
-
description:
|
|
235
|
+
title: k("bigconsole.workflows.list.noResultsTitle", "No matching workflows"),
|
|
236
|
+
description: k("bigconsole.workflows.list.noResultsDescription", "No workflows match your filters")
|
|
237
|
+
}),
|
|
238
|
+
R && /* @__PURE__ */ O("div", {
|
|
239
|
+
role: "status",
|
|
240
|
+
"aria-live": "polite",
|
|
241
|
+
className: `mb-form-gap flex flex-wrap items-center justify-between gap-icon-gap rounded-input border p-3 text-sm ${R.ok ? "border-[var(--color-status-success-border)] bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)]" : "border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] text-[var(--color-status-error-text)]"}`,
|
|
242
|
+
"data-testid": "workflows-run-outcome",
|
|
243
|
+
children: [/* @__PURE__ */ O("span", {
|
|
244
|
+
className: "min-w-0",
|
|
245
|
+
children: [
|
|
246
|
+
/* @__PURE__ */ D("strong", {
|
|
247
|
+
className: "font-semibold",
|
|
248
|
+
children: R.workflowName
|
|
249
|
+
}),
|
|
250
|
+
" — ",
|
|
251
|
+
R.message
|
|
252
|
+
]
|
|
253
|
+
}), /* @__PURE__ */ D("button", {
|
|
254
|
+
type: "button",
|
|
255
|
+
onClick: () => z(null),
|
|
256
|
+
className: "rounded-button px-2 py-1 text-xs font-medium underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
|
|
257
|
+
children: k("bigconsole.workflows.run.dismiss", "Dismiss")
|
|
258
|
+
})]
|
|
216
259
|
}),
|
|
217
|
-
|
|
260
|
+
Z.length > 0 && /* @__PURE__ */ D("div", {
|
|
218
261
|
className: "grid grid-cols-1 gap-form-gap sm:grid-cols-2 xl:grid-cols-3",
|
|
219
|
-
children:
|
|
262
|
+
children: Z.map((e) => /* @__PURE__ */ D(d, {
|
|
220
263
|
workflow: e,
|
|
221
|
-
onView:
|
|
222
|
-
onEdit:
|
|
223
|
-
onDelete:
|
|
264
|
+
onView: se,
|
|
265
|
+
onEdit: N ? ce : void 0,
|
|
266
|
+
onDelete: P ? le : void 0,
|
|
267
|
+
onRun: F ? $ : void 0,
|
|
268
|
+
isRunning: oe.get(e.id)?.isExecuting ?? !1
|
|
224
269
|
}, e.id))
|
|
225
270
|
})
|
|
226
271
|
]
|
|
227
272
|
})] })] })
|
|
228
273
|
}),
|
|
229
|
-
|
|
230
|
-
workflow:
|
|
231
|
-
isOpen:
|
|
232
|
-
onClose:
|
|
233
|
-
onConfirm:
|
|
274
|
+
U && /* @__PURE__ */ D(u, {
|
|
275
|
+
workflow: U,
|
|
276
|
+
isOpen: H,
|
|
277
|
+
onClose: G,
|
|
278
|
+
onConfirm: ue
|
|
234
279
|
})
|
|
235
280
|
]
|
|
236
281
|
});
|
|
237
282
|
});
|
|
238
283
|
//#endregion
|
|
239
|
-
export {
|
|
284
|
+
export { k as WorkflowsPage };
|
|
240
285
|
|
|
241
286
|
//# sourceMappingURL=WorkflowsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkflowsPage.js","names":[],"sources":["../../../src/bigconsole/pages/WorkflowsPage.tsx"],"sourcesContent":["/**\n * Workflows Page\n *\n * List and manage FluidGrids Workflows.\n *\n * Section hierarchy (BOFF-5634):\n * - page header strip .............. `bare` — identity + the one create action\n * - \"Filters\" ...................... `standard` — status tabs + search, one labelled group\n * - \"Workflows\" .................... `primary` — the result set this route exists for;\n * loading / empty / no-results / grid all\n * live inside it so the page keeps its shape\n * - load failure ................... `danger` — in-page status section, not a page swap\n */\n\nimport { memo, useState, useMemo, useCallback } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeNotify } from '../../utils/nativeBridge';\nimport { newWorkflowPath } from '../BigConsoleRoutes';\nimport { useWorkflowOperations } from '../hooks/useWorkflowOperations';\nimport { useWorkflowStore } from '../store/workflowStore';\nimport { WorkflowCard } from '../components/workflow/WorkflowCard';\nimport { WorkflowDeleteDialog } from '../components/workflow/WorkflowDeleteDialog';\nimport { Section, SectionStack, SECTION_FOCUS_RING } from '../components/common';\nimport type { FluidGridsWorkflowStatus } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { AuroraBackground, GradientText, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport { Plus, Search, Workflow, AlertCircle } from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ntype StatusFilter = 'all' | FluidGridsWorkflowStatus;\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const WorkflowsPage = memo(function WorkflowsPage() {\n const navigate = useAppNavigate();\n const { t } = useI18n();\n\n // RBAC permissions\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.WORKFLOW_READ);\n const canUpdate = hasPermission(BIGCONSOLE_PERMISSIONS.WORKFLOW_UPDATE);\n const canDelete = hasPermission(BIGCONSOLE_PERMISSIONS.WORKFLOW_DELETE);\n\n // Local state\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');\n\n // Store state\n const searchQuery = useWorkflowStore((state) => state.searchQuery);\n const setSearchQuery = useWorkflowStore((state) => state.setSearchQuery);\n const deleteDialogOpen = useWorkflowStore((state) => state.deleteDialogOpen);\n const currentWorkflow = useWorkflowStore((state) => state.currentWorkflow);\n const openDeleteDialog = useWorkflowStore((state) => state.openDeleteDialog);\n const closeDeleteDialog = useWorkflowStore((state) => state.closeDeleteDialog);\n\n // Operations\n const { loading, error, workflows, deleteWorkflow, refetch } = useWorkflowOperations();\n\n // Filter workflows\n const filteredWorkflows = useMemo(() => {\n let filtered = [...workflows];\n\n // Apply status filter\n if (statusFilter !== 'all') {\n filtered = filtered.filter((wf) => wf.status === statusFilter);\n }\n\n // Apply search filter\n if (searchQuery.trim()) {\n const query = searchQuery.toLowerCase();\n filtered = filtered.filter(\n (wf) =>\n wf.name.toLowerCase().includes(query) ||\n wf.key.toLowerCase().includes(query) ||\n wf.description?.toLowerCase().includes(query)\n );\n }\n\n // Sort by updatedAt descending\n return filtered.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n }, [workflows, statusFilter, searchQuery]);\n\n // Handlers\n const handleCreateWorkflow = useCallback(() => {\n navigate(newWorkflowPath());\n }, [navigate]);\n\n const handleViewWorkflow = useCallback(\n (id: string) => {\n navigate(`/workflows/${id}`);\n },\n [navigate]\n );\n\n const handleEditWorkflow = useCallback(\n (id: string) => {\n navigate(`/workflows/${id}/edit`);\n },\n [navigate]\n );\n\n const handleDeleteClick = useCallback(\n (id: string) => {\n const workflow = workflows.find((wf) => wf.id === id);\n if (workflow) {\n openDeleteDialog(workflow);\n }\n },\n [workflows, openDeleteDialog]\n );\n\n const handleDeleteConfirm = useCallback(async () => {\n if (currentWorkflow) {\n try {\n await deleteWorkflow(currentWorkflow.id);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n closeDeleteDialog();\n }\n }, [currentWorkflow, deleteWorkflow, closeDeleteDialog]);\n\n // Status filter options\n const statusOptions: { value: StatusFilter; label: string }[] = [\n { value: 'all', label: t('bigconsole.workflows.filters.all', 'All') },\n { value: 'draft', label: t('bigconsole.workflows.status.draft', 'Draft') },\n { value: 'active', label: t('bigconsole.workflows.status.active', 'Active') },\n { value: 'paused', label: t('bigconsole.workflows.status.paused', 'Paused') },\n { value: 'error', label: t('bigconsole.workflows.status.error', 'Error') },\n { value: 'archived', label: t('bigconsole.workflows.status.archived', 'Archived') },\n ];\n\n // Page-level read gate — only after permissions have finished loading so we\n // don't flash Access Denied while the permission set is still being fetched.\n if (!permissionsLoading && !canRead) {\n return (\n <AccessDenied\n title={t('bigconsole.workflows.errors.accessDeniedTitle', 'Access Denied')}\n description={t(\n 'bigconsole.workflows.errors.accessDeniedListDescription',\n \"You don't have permission to view workflows.\"\n )}\n />\n );\n }\n\n const isEmpty = !loading && workflows.length === 0;\n const isNoResults = !loading && filteredWorkflows.length === 0 && workflows.length > 0;\n\n return (\n <div className=\"min-h-dvh bg-bg-canvas\">\n {/* ------------------------------------------------------------------ *\n * Page header strip — `bare` emphasis: structural only. It carries the\n * page identity and the single create action, and must never out-weigh\n * the result set below it. Rendered as <header> (the page banner) so it\n * stays a landmark without competing with the labelled sections below.\n * The band itself was invisible before: `border-border-seam` and\n * `bg-gloss-card` are unregistered utilities that emit no CSS.\n * ------------------------------------------------------------------ */}\n <header\n data-section-emphasis=\"bare\"\n className=\"relative min-w-0 overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]\"\n >\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"relative flex flex-col gap-form-gap p-[var(--space-pagePadding)] sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex min-w-0 items-center gap-form-gap\">\n <span\n aria-hidden=\"true\"\n className=\"flex h-control-lg w-[var(--size-controlHeight-lg)] shrink-0 items-center justify-center rounded-card bg-action-primary-bg\"\n >\n <Workflow className=\"h-icon-lg w-icon-lg text-action-primary-text\" />\n </span>\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{t('bigconsole.workflows.list.title', 'Workflows')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'bigconsole.workflows.list.subtitle',\n 'Design ETL workflows with FluidGrids and link them to DataSinks'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-control-gap\">\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.WORKFLOW_CREATE} deniedBehavior=\"disable\">\n <Button onClick={handleCreateWorkflow}>\n <Plus className=\"h-icon-sm w-icon-sm\" />\n {t('bigconsole.workflows.actions.designWorkflow', 'Design Workflow')}\n </Button>\n </PermissionButton>\n </div>\n </div>\n </header>\n\n {/* Main Content */}\n <div className=\"p-[var(--space-pagePadding)]\">\n <SectionStack>\n <PagePurpose>\n {t(\n 'bigconsole.workflows.list.purpose',\n 'Workflows are visual ETL jobs built with the FluidGrids builder — they fetch data from external systems, transform it, and load it into your BigConsole DataSinks on a schedule or on demand. Use them to keep dashboards fed with fresh, shaped data without writing glue code.'\n )}\n </PagePurpose>\n\n {error ? (\n /* Load failure reads as a status section inside the page — the\n header band and the create action stay reachable. */\n <Section\n headingLevel={2}\n tone=\"danger\"\n title={t('bigconsole.workflows.errors.loadFailedTitle', 'Failed to load Workflows')}\n description=\"The workflow service did not respond. Nothing has been changed — retry when you are ready.\"\n icon={<AlertCircle className=\"h-icon-md w-icon-md\" />}\n actions={\n <Button size=\"sm\" onClick={() => refetch()}>\n {t('bigconsole.workflows.actions.tryAgain', 'Try Again')}\n </Button>\n }\n data-testid=\"workflows-error\"\n >\n <p className=\"break-words text-sm text-[var(--color-status-error-text)]\">{error.message}</p>\n </Section>\n ) : (\n <>\n {/* Configuration: the two filter controls read as ONE group. */}\n <Section\n headingLevel={2}\n title=\"Filters\"\n description=\"Narrow the list by status, or search by name, key or description.\"\n padding=\"compact\"\n data-testid=\"workflows-filters\"\n >\n <div className=\"flex flex-col gap-form-gap lg:flex-row lg:items-center lg:justify-between\">\n <div\n role=\"group\"\n aria-label=\"Filter workflows by status\"\n className=\"flex flex-wrap items-center gap-1 rounded-card bg-bg-sunken p-1\"\n >\n {statusOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n aria-pressed={statusFilter === option.value}\n onClick={() => setStatusFilter(option.value)}\n className={`min-h-[var(--size-controlHeight-md)] rounded-button px-3 text-sm font-medium motion-safe:transition-colors ${SECTION_FOCUS_RING} ${\n statusFilter === option.value\n ? 'bg-bg-surface text-text-primary shadow-[var(--shadow-elevation-1)]'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {option.label}\n </button>\n ))}\n </div>\n\n <div className=\"relative w-full lg:max-w-[360px]\">\n <Search\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute left-3 top-1/2 h-icon-sm w-icon-sm -translate-y-1/2 text-text-muted\"\n />\n <Input\n type=\"text\"\n aria-label={t('bigconsole.workflows.filters.searchPlaceholder', 'Search workflows...')}\n placeholder={t('bigconsole.workflows.filters.searchPlaceholder', 'Search workflows...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"h-control-md pl-10\"\n />\n </div>\n </div>\n </Section>\n\n {/* ------------------------------------------------------------ *\n * The result set — the ONE `primary` block on this route.\n * Loading, empty, no-results and the grid all render inside it,\n * so the page keeps the same shape whatever the data does.\n * ------------------------------------------------------------ */}\n <Section\n headingLevel={2}\n emphasis=\"primary\"\n title={t('bigconsole.workflows.list.title', 'Workflows')}\n description=\"Every workflow in this workspace, most recently updated first.\"\n meta={\n <span className=\"rounded-pill bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {filteredWorkflows.length}\n {filteredWorkflows.length !== workflows.length ? ` of ${workflows.length}` : ''}\n </span>\n }\n data-testid=\"workflows-results\"\n >\n {loading && workflows.length === 0 && (\n <div className=\"flex min-h-[240px] items-center justify-center\">\n <div className=\"text-center\">\n <Spinner size=\"lg\" className=\"mb-form-gap\" />\n <p className=\"text-sm text-text-muted\">\n {t('bigconsole.workflows.list.loading', 'Loading workflows...')}\n </p>\n </div>\n </div>\n )}\n\n {isEmpty && (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('bigconsole.workflows.list.emptyTitle', 'No Workflows yet')}\n description={t(\n 'bigconsole.workflows.list.emptyDescription',\n 'Design ETL workflows with the FluidGrids visual builder to fetch, transform, and deliver data into BigConsole DataSinks.'\n )}\n action={\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.WORKFLOW_CREATE} deniedBehavior=\"disable\">\n <Button onClick={handleCreateWorkflow}>\n <Plus className=\"h-icon-sm w-icon-sm\" />\n {t('bigconsole.workflows.actions.designWorkflow', 'Design Workflow')}\n </Button>\n </PermissionButton>\n }\n />\n )}\n\n {isNoResults && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={t('bigconsole.workflows.list.noResultsTitle', 'No matching workflows')}\n description={t('bigconsole.workflows.list.noResultsDescription', 'No workflows match your filters')}\n />\n )}\n\n {filteredWorkflows.length > 0 && (\n <div className=\"grid grid-cols-1 gap-form-gap sm:grid-cols-2 xl:grid-cols-3\">\n {filteredWorkflows.map((workflow) => (\n <WorkflowCard\n key={workflow.id}\n workflow={workflow}\n onView={handleViewWorkflow}\n onEdit={canUpdate ? handleEditWorkflow : undefined}\n onDelete={canDelete ? handleDeleteClick : undefined}\n />\n ))}\n </div>\n )}\n </Section>\n </>\n )}\n </SectionStack>\n </div>\n\n {/* Delete Dialog */}\n {currentWorkflow && (\n <WorkflowDeleteDialog\n workflow={currentWorkflow}\n isOpen={deleteDialogOpen}\n onClose={closeDeleteDialog}\n onConfirm={handleDeleteConfirm}\n />\n )}\n </div>\n );\n});\n\nexport default WorkflowsPage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkflowsPage.js","names":[],"sources":["../../../src/bigconsole/pages/WorkflowsPage.tsx"],"sourcesContent":["/**\n * Workflows Page\n *\n * List and manage FluidGrids Workflows.\n *\n * Section hierarchy (BOFF-5634):\n * - page header strip .............. `bare` — identity + the one create action\n * - \"Filters\" ...................... `standard` — status tabs + search, one labelled group\n * - \"Workflows\" .................... `primary` — the result set this route exists for;\n * loading / empty / no-results / grid all\n * live inside it so the page keeps its shape\n * - load failure ................... `danger` — in-page status section, not a page swap\n */\n\nimport { memo, useState, useMemo, useCallback } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeNotify } from '../../utils/nativeBridge';\nimport { newWorkflowPath } from '../BigConsoleRoutes';\nimport { useWorkflowOperations } from '../hooks/useWorkflowOperations';\nimport { useWorkflowStore } from '../store/workflowStore';\nimport { WorkflowCard } from '../components/workflow/WorkflowCard';\nimport { WorkflowDeleteDialog } from '../components/workflow/WorkflowDeleteDialog';\nimport { Section, SectionStack, SECTION_FOCUS_RING } from '../components/common';\nimport type { FluidGridsWorkflowStatus } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { AuroraBackground, GradientText, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport { Plus, Search, Workflow, AlertCircle } from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ntype StatusFilter = 'all' | FluidGridsWorkflowStatus;\n\n/** Result of the last manual run triggered from a card. */\ninterface RunOutcome {\n workflowName: string;\n ok: boolean;\n message: string;\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const WorkflowsPage = memo(function WorkflowsPage() {\n const navigate = useAppNavigate();\n const { t } = useI18n();\n\n // RBAC permissions\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.WORKFLOW_READ);\n const canUpdate = hasPermission(BIGCONSOLE_PERMISSIONS.WORKFLOW_UPDATE);\n const canDelete = hasPermission(BIGCONSOLE_PERMISSIONS.WORKFLOW_DELETE);\n const canExecute = hasPermission(BIGCONSOLE_PERMISSIONS.WORKFLOW_EXECUTE);\n\n // Local state\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');\n /** Outcome of the most recent run, shown inline above the grid. */\n const [runOutcome, setRunOutcome] = useState<RunOutcome | null>(null);\n\n // Store state\n const searchQuery = useWorkflowStore((state) => state.searchQuery);\n const setSearchQuery = useWorkflowStore((state) => state.setSearchQuery);\n const deleteDialogOpen = useWorkflowStore((state) => state.deleteDialogOpen);\n const currentWorkflow = useWorkflowStore((state) => state.currentWorkflow);\n const openDeleteDialog = useWorkflowStore((state) => state.openDeleteDialog);\n const closeDeleteDialog = useWorkflowStore((state) => state.closeDeleteDialog);\n\n // Operations\n const { loading, error, workflows, deleteWorkflow, refetch } = useWorkflowOperations();\n\n /**\n * Runs go through a SECOND hook instance on purpose. `useWorkflowOperations`\n * keeps one shared `error` for every operation, and this page swaps the whole\n * result set for a \"Failed to load Workflows\" panel whenever that error is\n * set — so a failed run on one card would wipe the list. A separate instance\n * (fetch skipped) keeps run failures local while still writing through to the\n * shared workflow store, so the card's execution count and last-run time\n * update in place.\n */\n const { executeWorkflow } = useWorkflowOperations({ skipFetch: true });\n\n // Per-card execution flags, maintained by the hook's executeWorkflow.\n const workflowUIStates = useWorkflowStore((state) => state.workflowUIStates);\n\n // Filter workflows\n const filteredWorkflows = useMemo(() => {\n let filtered = [...workflows];\n\n // Apply status filter\n if (statusFilter !== 'all') {\n filtered = filtered.filter((wf) => wf.status === statusFilter);\n }\n\n // Apply search filter\n if (searchQuery.trim()) {\n const query = searchQuery.toLowerCase();\n filtered = filtered.filter(\n (wf) =>\n wf.name.toLowerCase().includes(query) ||\n wf.key.toLowerCase().includes(query) ||\n wf.description?.toLowerCase().includes(query)\n );\n }\n\n // Sort by updatedAt descending\n return filtered.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n }, [workflows, statusFilter, searchQuery]);\n\n // Handlers\n const handleCreateWorkflow = useCallback(() => {\n navigate(newWorkflowPath());\n }, [navigate]);\n\n const handleViewWorkflow = useCallback(\n (id: string) => {\n navigate(`/workflows/${id}`);\n },\n [navigate]\n );\n\n const handleEditWorkflow = useCallback(\n (id: string) => {\n navigate(`/workflows/${id}/edit`);\n },\n [navigate]\n );\n\n const handleDeleteClick = useCallback(\n (id: string) => {\n const workflow = workflows.find((wf) => wf.id === id);\n if (workflow) {\n openDeleteDialog(workflow);\n }\n },\n [workflows, openDeleteDialog]\n );\n\n const handleRunWorkflow = useCallback(\n async (id: string) => {\n const workflow = workflows.find((wf) => wf.id === id);\n if (!workflow) return;\n\n setRunOutcome(null);\n // `executeWorkflow` swallows the Apollo error and resolves to null, so a\n // null result IS the failure signal — never treat it as a silent success.\n const result = await executeWorkflow(id);\n\n if (result) {\n if (isNative()) void nativeNotify('success');\n setRunOutcome({\n workflowName: workflow.name,\n ok: true,\n message: t(\n 'bigconsole.workflows.run.triggered',\n 'Run triggered. The execution count and last-run time update as the run progresses.'\n ),\n });\n return;\n }\n\n if (isNative()) void nativeNotify('error');\n setRunOutcome({\n workflowName: workflow.name,\n ok: false,\n message: t('bigconsole.workflows.run.failed', 'The workflow could not be started. Nothing has been changed.'),\n });\n },\n [workflows, executeWorkflow, t]\n );\n\n const handleDeleteConfirm = useCallback(async () => {\n if (currentWorkflow) {\n try {\n await deleteWorkflow(currentWorkflow.id);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n closeDeleteDialog();\n }\n }, [currentWorkflow, deleteWorkflow, closeDeleteDialog]);\n\n // Status filter options\n const statusOptions: { value: StatusFilter; label: string }[] = [\n { value: 'all', label: t('bigconsole.workflows.filters.all', 'All') },\n { value: 'draft', label: t('bigconsole.workflows.status.draft', 'Draft') },\n { value: 'active', label: t('bigconsole.workflows.status.active', 'Active') },\n { value: 'paused', label: t('bigconsole.workflows.status.paused', 'Paused') },\n { value: 'error', label: t('bigconsole.workflows.status.error', 'Error') },\n { value: 'archived', label: t('bigconsole.workflows.status.archived', 'Archived') },\n ];\n\n // Page-level read gate — only after permissions have finished loading so we\n // don't flash Access Denied while the permission set is still being fetched.\n if (!permissionsLoading && !canRead) {\n return (\n <AccessDenied\n title={t('bigconsole.workflows.errors.accessDeniedTitle', 'Access Denied')}\n description={t(\n 'bigconsole.workflows.errors.accessDeniedListDescription',\n \"You don't have permission to view workflows.\"\n )}\n />\n );\n }\n\n const isEmpty = !loading && workflows.length === 0;\n const isNoResults = !loading && filteredWorkflows.length === 0 && workflows.length > 0;\n\n return (\n <div className=\"min-h-dvh bg-bg-canvas\">\n {/* ------------------------------------------------------------------ *\n * Page header strip — `bare` emphasis: structural only. It carries the\n * page identity and the single create action, and must never out-weigh\n * the result set below it. Rendered as <header> (the page banner) so it\n * stays a landmark without competing with the labelled sections below.\n * The band itself was invisible before: `border-border-seam` and\n * `bg-gloss-card` are unregistered utilities that emit no CSS.\n * ------------------------------------------------------------------ */}\n <header\n data-section-emphasis=\"bare\"\n className=\"relative min-w-0 overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]\"\n >\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"relative flex flex-col gap-form-gap p-[var(--space-pagePadding)] sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex min-w-0 items-center gap-form-gap\">\n <span\n aria-hidden=\"true\"\n className=\"flex h-control-lg w-[var(--size-controlHeight-lg)] shrink-0 items-center justify-center rounded-card bg-action-primary-bg\"\n >\n <Workflow className=\"h-icon-lg w-icon-lg text-action-primary-text\" />\n </span>\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{t('bigconsole.workflows.list.title', 'Workflows')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'bigconsole.workflows.list.subtitle',\n 'Design ETL workflows with FluidGrids and link them to DataSinks'\n )}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-control-gap\">\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.WORKFLOW_CREATE} deniedBehavior=\"disable\">\n <Button onClick={handleCreateWorkflow}>\n <Plus className=\"h-icon-sm w-icon-sm\" />\n {t('bigconsole.workflows.actions.designWorkflow', 'Design Workflow')}\n </Button>\n </PermissionButton>\n </div>\n </div>\n </header>\n\n {/* Main Content */}\n <div className=\"p-[var(--space-pagePadding)]\">\n <SectionStack>\n <PagePurpose>\n {t(\n 'bigconsole.workflows.list.purpose',\n 'Workflows are visual ETL jobs built with the FluidGrids builder — they fetch data from external systems, transform it, and load it into your BigConsole DataSinks on a schedule or on demand. Use them to keep dashboards fed with fresh, shaped data without writing glue code.'\n )}\n </PagePurpose>\n\n {error ? (\n /* Load failure reads as a status section inside the page — the\n header band and the create action stay reachable. */\n <Section\n headingLevel={2}\n tone=\"danger\"\n title={t('bigconsole.workflows.errors.loadFailedTitle', 'Failed to load Workflows')}\n description=\"The workflow service did not respond. Nothing has been changed — retry when you are ready.\"\n icon={<AlertCircle className=\"h-icon-md w-icon-md\" />}\n actions={\n <Button size=\"sm\" onClick={() => refetch()}>\n {t('bigconsole.workflows.actions.tryAgain', 'Try Again')}\n </Button>\n }\n data-testid=\"workflows-error\"\n >\n <p className=\"break-words text-sm text-[var(--color-status-error-text)]\">{error.message}</p>\n </Section>\n ) : (\n <>\n {/* Configuration: the two filter controls read as ONE group. */}\n <Section\n headingLevel={2}\n title=\"Filters\"\n description=\"Narrow the list by status, or search by name, key or description.\"\n padding=\"compact\"\n data-testid=\"workflows-filters\"\n >\n <div className=\"flex flex-col gap-form-gap lg:flex-row lg:items-center lg:justify-between\">\n <div\n role=\"group\"\n aria-label=\"Filter workflows by status\"\n className=\"flex flex-wrap items-center gap-1 rounded-card bg-bg-sunken p-1\"\n >\n {statusOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n aria-pressed={statusFilter === option.value}\n onClick={() => setStatusFilter(option.value)}\n className={`min-h-[var(--size-controlHeight-md)] rounded-button px-3 text-sm font-medium motion-safe:transition-colors ${SECTION_FOCUS_RING} ${\n statusFilter === option.value\n ? 'bg-bg-surface text-text-primary shadow-[var(--shadow-elevation-1)]'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {option.label}\n </button>\n ))}\n </div>\n\n <div className=\"relative w-full lg:max-w-[360px]\">\n <Search\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute left-3 top-1/2 h-icon-sm w-icon-sm -translate-y-1/2 text-text-muted\"\n />\n <Input\n type=\"text\"\n aria-label={t('bigconsole.workflows.filters.searchPlaceholder', 'Search workflows...')}\n placeholder={t('bigconsole.workflows.filters.searchPlaceholder', 'Search workflows...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"h-control-md pl-10\"\n />\n </div>\n </div>\n </Section>\n\n {/* ------------------------------------------------------------ *\n * The result set — the ONE `primary` block on this route.\n * Loading, empty, no-results and the grid all render inside it,\n * so the page keeps the same shape whatever the data does.\n * ------------------------------------------------------------ */}\n <Section\n headingLevel={2}\n emphasis=\"primary\"\n title={t('bigconsole.workflows.list.title', 'Workflows')}\n description=\"Every workflow in this workspace, most recently updated first.\"\n meta={\n <span className=\"rounded-pill bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {filteredWorkflows.length}\n {filteredWorkflows.length !== workflows.length ? ` of ${workflows.length}` : ''}\n </span>\n }\n data-testid=\"workflows-results\"\n >\n {loading && workflows.length === 0 && (\n <div className=\"flex min-h-[240px] items-center justify-center\">\n <div className=\"text-center\">\n <Spinner size=\"lg\" className=\"mb-form-gap\" />\n <p className=\"text-sm text-text-muted\">\n {t('bigconsole.workflows.list.loading', 'Loading workflows...')}\n </p>\n </div>\n </div>\n )}\n\n {isEmpty && (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('bigconsole.workflows.list.emptyTitle', 'No Workflows yet')}\n description={t(\n 'bigconsole.workflows.list.emptyDescription',\n 'Design ETL workflows with the FluidGrids visual builder to fetch, transform, and deliver data into BigConsole DataSinks.'\n )}\n action={\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.WORKFLOW_CREATE} deniedBehavior=\"disable\">\n <Button onClick={handleCreateWorkflow}>\n <Plus className=\"h-icon-sm w-icon-sm\" />\n {t('bigconsole.workflows.actions.designWorkflow', 'Design Workflow')}\n </Button>\n </PermissionButton>\n }\n />\n )}\n\n {isNoResults && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={t('bigconsole.workflows.list.noResultsTitle', 'No matching workflows')}\n description={t('bigconsole.workflows.list.noResultsDescription', 'No workflows match your filters')}\n />\n )}\n\n {runOutcome && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={`mb-form-gap flex flex-wrap items-center justify-between gap-icon-gap rounded-input border p-3 text-sm ${\n runOutcome.ok\n ? 'border-[var(--color-status-success-border)] bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)]'\n : 'border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] text-[var(--color-status-error-text)]'\n }`}\n data-testid=\"workflows-run-outcome\"\n >\n <span className=\"min-w-0\">\n <strong className=\"font-semibold\">{runOutcome.workflowName}</strong> — {runOutcome.message}\n </span>\n <button\n type=\"button\"\n onClick={() => setRunOutcome(null)}\n className={`rounded-button px-2 py-1 text-xs font-medium underline-offset-2 hover:underline ${SECTION_FOCUS_RING}`}\n >\n {t('bigconsole.workflows.run.dismiss', 'Dismiss')}\n </button>\n </div>\n )}\n\n {filteredWorkflows.length > 0 && (\n <div className=\"grid grid-cols-1 gap-form-gap sm:grid-cols-2 xl:grid-cols-3\">\n {filteredWorkflows.map((workflow) => (\n <WorkflowCard\n key={workflow.id}\n workflow={workflow}\n onView={handleViewWorkflow}\n onEdit={canUpdate ? handleEditWorkflow : undefined}\n onDelete={canDelete ? handleDeleteClick : undefined}\n onRun={canExecute ? handleRunWorkflow : undefined}\n isRunning={workflowUIStates.get(workflow.id)?.isExecuting ?? false}\n />\n ))}\n </div>\n )}\n </Section>\n </>\n )}\n </SectionStack>\n </div>\n\n {/* Delete Dialog */}\n {currentWorkflow && (\n <WorkflowDeleteDialog\n workflow={currentWorkflow}\n isOpen={deleteDialogOpen}\n onClose={closeDeleteDialog}\n onConfirm={handleDeleteConfirm}\n />\n )}\n </div>\n );\n});\n\nexport default WorkflowsPage;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/bigconsole/utils/format.ts
|
|
2
|
+
var e = [
|
|
3
|
+
"B",
|
|
4
|
+
"KB",
|
|
5
|
+
"MB",
|
|
6
|
+
"GB",
|
|
7
|
+
"TB"
|
|
8
|
+
];
|
|
9
|
+
function t(t) {
|
|
10
|
+
if (t == null || !Number.isFinite(t)) return "—";
|
|
11
|
+
if (t <= 0) return "0 B";
|
|
12
|
+
let n = Math.min(Math.floor(Math.log(t) / Math.log(1024)), e.length - 1), r = t / 1024 ** n, i = n === 0 ? 0 : 1;
|
|
13
|
+
return `${r.toFixed(i)} ${e[n]}`;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { t as formatByteSize };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","names":[],"sources":["../../../src/bigconsole/utils/format.ts"],"sourcesContent":["/**\n * Shared display formatters.\n *\n * Kept here rather than re-declared per page so the same byte value reads the\n * same everywhere it is shown (list table, card, detail panel).\n *\n * @module bigconsole/utils/format\n */\n\nconst BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;\n\n/**\n * Render a byte count as a human-readable size.\n *\n * `null`/`undefined` render as an em dash rather than `0 B`: \"we don't know\"\n * and \"it is empty\" are different facts, and a sink whose size has never been\n * computed must not claim to be empty.\n */\nexport function formatByteSize(bytes: number | null | undefined): string {\n if (bytes === null || bytes === undefined || !Number.isFinite(bytes)) return '—';\n if (bytes <= 0) return '0 B';\n const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), BYTE_UNITS.length - 1);\n const value = bytes / Math.pow(1024, exponent);\n // Whole bytes never need a decimal; larger units keep one for readability.\n const decimals = exponent === 0 ? 0 : 1;\n return `${value.toFixed(decimals)} ${BYTE_UNITS[exponent]}`;\n}\n"],"mappings":";AASA,IAAM,IAAa;CAAC;CAAK;CAAM;CAAM;CAAM;CAAK;AAShD,SAAgB,EAAe,GAA0C;AACvE,KAAI,KAAU,QAA+B,CAAC,OAAO,SAAS,EAAM,CAAE,QAAO;AAC7E,KAAI,KAAS,EAAG,QAAO;CACvB,IAAM,IAAW,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,EAAM,GAAG,KAAK,IAAI,KAAK,CAAC,EAAE,EAAW,SAAS,EAAE,EACxF,IAAQ,IAAiB,QAAM,GAE/B,IAAW,MAAa,IAAI,IAAI;AACtC,QAAO,GAAG,EAAM,QAAQ,EAAS,CAAC,GAAG,EAAW"}
|
|
@@ -39,6 +39,6 @@ function i(t, n, r, i, a, o = e) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
//#endregion
|
|
42
|
-
export { i as calculatePositionFromDrop, r as findNextAvailablePosition };
|
|
42
|
+
export { e as DEFAULT_GRID_CONFIG, i as calculatePositionFromDrop, r as findNextAvailablePosition };
|
|
43
43
|
|
|
44
44
|
//# sourceMappingURL=widgetPositioning.js.map
|