@burdenoff/microfe-bigconsole 2026.828.1 → 2026.829.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/bigconsole/pages/DashboardsPage.js +282 -286
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataParsersPage.js +60 -60
- package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
- package/dist/shared/hooks/useTr.js +14 -0
- package/dist/shared/hooks/useTr.js.map +1 -0
- package/package.json +1 -1
|
@@ -6,20 +6,20 @@ import "../components/common/index.js";
|
|
|
6
6
|
import i from "../hooks/useParserOperations.js";
|
|
7
7
|
import a from "../hooks/useExportParser.js";
|
|
8
8
|
import { BIGCONSOLE_PERMISSIONS as o } from "../../constants/permissions.js";
|
|
9
|
-
import { isNative as s, nativeConfirm as ee, nativeImpact as
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { AuroraBackground as
|
|
9
|
+
import { isNative as s, nativeConfirm as ee, nativeImpact as te, nativeNotify as c } from "../../utils/nativeBridge.js";
|
|
10
|
+
import { useTr as ne } from "../../shared/hooks/useTr.js";
|
|
11
|
+
import re from "../components/parser/ParserCard.js";
|
|
12
|
+
import ie from "../components/parser/ImportParserDialog.js";
|
|
13
|
+
import { memo as l, useCallback as u, useMemo as d, useState as f } from "react";
|
|
14
|
+
import { useParams as ae } from "react-router";
|
|
15
|
+
import { AccessDenied as oe, PermissionButton as se } from "@burdenoff/fe-libs/shared/components";
|
|
16
|
+
import { usePermissions as ce } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
17
|
+
import { AlertCircle as le, FileCode2 as ue, Plus as p, Search as de, Upload as m, X as fe } from "lucide-react";
|
|
18
|
+
import { AuroraBackground as pe, Badge as h, Button as g, CTAOverflowMenu as me, GradientText as he, IllustratedEmptyState as _, Input as ge, PagePurpose as v, Select as y, SelectContent as b, SelectItem as x, SelectTrigger as S, SelectValue as C, Spinner as _e } from "@burdenoff/fe-libs/ui";
|
|
19
19
|
import { jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
20
20
|
//#region src/bigconsole/pages/DataParsersPage.tsx
|
|
21
|
-
var ve = "default", E =
|
|
22
|
-
let
|
|
21
|
+
var ve = "default", E = l(function() {
|
|
22
|
+
let l = t(), { consoleId: E } = ae(), D = ne(), ye = d(() => [
|
|
23
23
|
{
|
|
24
24
|
value: "all",
|
|
25
25
|
label: D("bigconsole.parsers.filters.allTypes", "All Types")
|
|
@@ -32,7 +32,7 @@ var ve = "default", E = u(function() {
|
|
|
32
32
|
value: "jsonata",
|
|
33
33
|
label: D("bigconsole.parsers.filters.jsonata", "JSONata")
|
|
34
34
|
}
|
|
35
|
-
], [D]), be =
|
|
35
|
+
], [D]), be = d(() => [
|
|
36
36
|
{
|
|
37
37
|
value: "all",
|
|
38
38
|
label: D("bigconsole.parsers.filters.allStatus", "All Status")
|
|
@@ -45,10 +45,10 @@ var ve = "default", E = u(function() {
|
|
|
45
45
|
value: "inactive",
|
|
46
46
|
label: D("bigconsole.parsers.filters.inactive", "Inactive")
|
|
47
47
|
}
|
|
48
|
-
], [D]), { hasPermission: O, isLoading: k } =
|
|
48
|
+
], [D]), { hasPermission: O, isLoading: k } = ce(), A = O(o.PARSER_READ), j = O(o.PARSER_CREATE), M = O(o.PARSER_UPDATE), N = O(o.PARSER_DELETE), P = E || ve, F = e((e) => e.typeFilter), I = e((e) => e.statusFilter), L = e((e) => e.searchQuery), R = e((e) => e.setTypeFilter), z = e((e) => e.setStatusFilter), B = e((e) => e.setSearchQuery), [xe, V] = f(!1), [Se, H] = f(null), [Ce, U] = f(!1), { loading: W, error: G, parsers: K, deleteParser: q, refetch: J } = i({
|
|
49
49
|
consoleId: P,
|
|
50
50
|
skipFetch: !1
|
|
51
|
-
}), { downloadParser: Y } = a(), X =
|
|
51
|
+
}), { downloadParser: Y } = a(), X = d(() => K ? K.filter((e) => {
|
|
52
52
|
if (F && e.type !== F || I && e.status !== I) return !1;
|
|
53
53
|
if (L) {
|
|
54
54
|
let t = L.toLowerCase(), n = e.name?.toLowerCase().includes(t), r = e.description?.toLowerCase().includes(t), i = e.inputSinkKey?.toLowerCase().includes(t), a = e.outputKey?.toLowerCase().includes(t);
|
|
@@ -60,19 +60,19 @@ var ve = "default", E = u(function() {
|
|
|
60
60
|
F,
|
|
61
61
|
I,
|
|
62
62
|
L
|
|
63
|
-
]), we =
|
|
63
|
+
]), we = u((e) => {
|
|
64
64
|
R(e === "all" ? null : e);
|
|
65
|
-
}, [R]), Te =
|
|
65
|
+
}, [R]), Te = u((e) => {
|
|
66
66
|
z(e === "all" ? null : e);
|
|
67
|
-
}, [z]), Ee =
|
|
67
|
+
}, [z]), Ee = u((e) => {
|
|
68
68
|
B(e.target.value);
|
|
69
|
-
}, [B]), Z =
|
|
70
|
-
|
|
71
|
-
}, [
|
|
72
|
-
|
|
73
|
-
}, [
|
|
74
|
-
|
|
75
|
-
}, [
|
|
69
|
+
}, [B]), Z = u(() => {
|
|
70
|
+
l(E ? `/dashboards/${E}/parsers/new` : "/parsers/new");
|
|
71
|
+
}, [l, E]), De = u((e) => {
|
|
72
|
+
l(E ? `/dashboards/${E}/parsers/${e}` : `/parsers/${e}`);
|
|
73
|
+
}, [l, E]), Oe = u((e) => {
|
|
74
|
+
l(E ? `/dashboards/${E}/parsers/${e}/edit` : `/parsers/${e}/edit`);
|
|
75
|
+
}, [l, E]), ke = u(async (e) => {
|
|
76
76
|
let t = await ee({
|
|
77
77
|
title: D("bigconsole.parsers.confirmDelete.title", "Delete parser?"),
|
|
78
78
|
message: D("bigconsole.parsers.confirmDelete.message", "Are you sure you want to delete this parser?"),
|
|
@@ -80,27 +80,27 @@ var ve = "default", E = u(function() {
|
|
|
80
80
|
cancelButtonTitle: D("bigconsole.parsers.actions.cancel", "Cancel")
|
|
81
81
|
});
|
|
82
82
|
if (t === null ? window.confirm(D("bigconsole.parsers.confirmDelete.message", "Are you sure you want to delete this parser?")) : t) {
|
|
83
|
-
|
|
83
|
+
te("medium"), V(!0), H(e);
|
|
84
84
|
try {
|
|
85
|
-
await q(e), s() &&
|
|
85
|
+
await q(e), s() && c("success");
|
|
86
86
|
} catch {
|
|
87
|
-
s() &&
|
|
87
|
+
s() && c("error");
|
|
88
88
|
} finally {
|
|
89
89
|
V(!1), H(null);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
}, [q, D]), Q =
|
|
92
|
+
}, [q, D]), Q = u(() => {
|
|
93
93
|
R(null), z(null), B("");
|
|
94
94
|
}, [
|
|
95
95
|
R,
|
|
96
96
|
z,
|
|
97
97
|
B
|
|
98
|
-
]), Ae =
|
|
98
|
+
]), Ae = u(async (e) => {
|
|
99
99
|
let t = K.find((t) => t.id === e);
|
|
100
100
|
t && await Y(e, t.name);
|
|
101
|
-
}, [K, Y]), je =
|
|
101
|
+
}, [K, Y]), je = u(() => {
|
|
102
102
|
U(!0);
|
|
103
|
-
}, []), Me =
|
|
103
|
+
}, []), Me = u((e, t, n) => {
|
|
104
104
|
J();
|
|
105
105
|
}, [J]), $ = F !== null || I !== null || (L?.length ?? 0) > 0;
|
|
106
106
|
return G ? /* @__PURE__ */ w("div", {
|
|
@@ -110,8 +110,8 @@ var ve = "default", E = u(function() {
|
|
|
110
110
|
headingLevel: 2,
|
|
111
111
|
title: D("bigconsole.parsers.errors.loadFailed", "Failed to load Parsers"),
|
|
112
112
|
description: "The parser list could not be fetched. Retry, or check that the BigConsole service is reachable.",
|
|
113
|
-
icon: /* @__PURE__ */ w(
|
|
114
|
-
actions: /* @__PURE__ */ w(
|
|
113
|
+
icon: /* @__PURE__ */ w(le, { className: "h-icon-md w-icon-md text-[var(--color-status-error-text)]" }),
|
|
114
|
+
actions: /* @__PURE__ */ w(g, {
|
|
115
115
|
size: "sm",
|
|
116
116
|
onClick: () => J(),
|
|
117
117
|
children: D("bigconsole.parsers.actions.tryAgain", "Try Again")
|
|
@@ -122,7 +122,7 @@ var ve = "default", E = u(function() {
|
|
|
122
122
|
children: G.message
|
|
123
123
|
})
|
|
124
124
|
})
|
|
125
|
-
}) : !k && !A ? /* @__PURE__ */ w(
|
|
125
|
+
}) : !k && !A ? /* @__PURE__ */ w(oe, {
|
|
126
126
|
title: D("bigconsole.parsers.accessDenied.title", "Access Denied"),
|
|
127
127
|
description: D("bigconsole.parsers.accessDenied.list", "You don't have permission to view parsers.")
|
|
128
128
|
}) : /* @__PURE__ */ T("div", {
|
|
@@ -133,7 +133,7 @@ var ve = "default", E = u(function() {
|
|
|
133
133
|
children: [
|
|
134
134
|
/* @__PURE__ */ T("div", {
|
|
135
135
|
className: "relative overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]",
|
|
136
|
-
children: [/* @__PURE__ */ w(
|
|
136
|
+
children: [/* @__PURE__ */ w(pe, { intensity: "subtle" }), /* @__PURE__ */ w("div", {
|
|
137
137
|
className: "relative",
|
|
138
138
|
style: { padding: "var(--space-pagePadding)" },
|
|
139
139
|
children: /* @__PURE__ */ T("div", {
|
|
@@ -148,7 +148,7 @@ var ve = "default", E = u(function() {
|
|
|
148
148
|
borderRadius: "var(--radius-card)",
|
|
149
149
|
backgroundColor: "var(--color-action-primary-bg)"
|
|
150
150
|
},
|
|
151
|
-
children: /* @__PURE__ */ w(
|
|
151
|
+
children: /* @__PURE__ */ w(ue, { style: {
|
|
152
152
|
height: "var(--size-icon-lg)",
|
|
153
153
|
width: "var(--size-icon-lg)",
|
|
154
154
|
color: "var(--color-action-primary-text)"
|
|
@@ -157,17 +157,17 @@ var ve = "default", E = u(function() {
|
|
|
157
157
|
className: "min-w-0",
|
|
158
158
|
children: [/* @__PURE__ */ w("h1", {
|
|
159
159
|
className: "m-0 text-2xl font-bold text-text-primary sm:text-3xl",
|
|
160
|
-
children: /* @__PURE__ */ w(
|
|
160
|
+
children: /* @__PURE__ */ w(he, { children: D("bigconsole.parsers.list.title", "Data Parsers") })
|
|
161
161
|
}), /* @__PURE__ */ w("p", {
|
|
162
162
|
className: "m-0 text-sm text-text-secondary",
|
|
163
163
|
children: D("bigconsole.parsers.list.subtitle", "Transform and process data with JQ or JSONata expressions")
|
|
164
164
|
})]
|
|
165
165
|
})]
|
|
166
|
-
}), /* @__PURE__ */ w(
|
|
166
|
+
}), /* @__PURE__ */ w(me, {
|
|
167
167
|
primary: j ? {
|
|
168
168
|
label: D("bigconsole.parsers.actions.create", "Create Parser"),
|
|
169
169
|
onSelect: Z,
|
|
170
|
-
icon: /* @__PURE__ */ w(
|
|
170
|
+
icon: /* @__PURE__ */ w(p, { style: {
|
|
171
171
|
marginRight: "var(--space-iconGap)",
|
|
172
172
|
height: "var(--size-icon-sm)",
|
|
173
173
|
width: "var(--size-icon-sm)"
|
|
@@ -176,7 +176,7 @@ var ve = "default", E = u(function() {
|
|
|
176
176
|
actions: j ? [{
|
|
177
177
|
label: D("bigconsole.parsers.actions.import", "Import"),
|
|
178
178
|
onSelect: je,
|
|
179
|
-
icon: /* @__PURE__ */ w(
|
|
179
|
+
icon: /* @__PURE__ */ w(m, { style: {
|
|
180
180
|
height: "var(--size-icon-sm)",
|
|
181
181
|
width: "var(--size-icon-sm)"
|
|
182
182
|
} })
|
|
@@ -188,7 +188,7 @@ var ve = "default", E = u(function() {
|
|
|
188
188
|
/* @__PURE__ */ w("div", {
|
|
189
189
|
style: { padding: "var(--space-pagePadding)" },
|
|
190
190
|
children: /* @__PURE__ */ T(r, { children: [
|
|
191
|
-
/* @__PURE__ */ w(
|
|
191
|
+
/* @__PURE__ */ w(v, {
|
|
192
192
|
className: "max-w-[80ch]",
|
|
193
193
|
children: D("bigconsole.parsers.list.purpose", "Parsers define how raw input — log lines, CSVs, JSON, or arbitrary text — is read and mapped into structured fields. Build a parser once, test it against a sample, then reuse it across pipelines so every source lands in a consistent, queryable shape.")
|
|
194
194
|
}),
|
|
@@ -196,7 +196,7 @@ var ve = "default", E = u(function() {
|
|
|
196
196
|
emphasis: "muted",
|
|
197
197
|
padding: "compact",
|
|
198
198
|
headingLevel: 2,
|
|
199
|
-
title: "Find a parser",
|
|
199
|
+
title: D("bigconsole.parsers.list.findTitle", "Find a parser"),
|
|
200
200
|
headerClassName: "mb-control-gap",
|
|
201
201
|
"data-testid": "parser-filters",
|
|
202
202
|
children: /* @__PURE__ */ T("div", {
|
|
@@ -204,7 +204,7 @@ var ve = "default", E = u(function() {
|
|
|
204
204
|
children: [
|
|
205
205
|
/* @__PURE__ */ T("div", {
|
|
206
206
|
className: "relative w-full min-w-0 flex-1 sm:max-w-[400px]",
|
|
207
|
-
children: [/* @__PURE__ */ w(
|
|
207
|
+
children: [/* @__PURE__ */ w(de, {
|
|
208
208
|
"aria-hidden": "true",
|
|
209
209
|
style: {
|
|
210
210
|
position: "absolute",
|
|
@@ -215,7 +215,7 @@ var ve = "default", E = u(function() {
|
|
|
215
215
|
width: "var(--size-icon-sm)",
|
|
216
216
|
color: "var(--color-text-muted)"
|
|
217
217
|
}
|
|
218
|
-
}), /* @__PURE__ */ w(
|
|
218
|
+
}), /* @__PURE__ */ w(ge, {
|
|
219
219
|
type: "text",
|
|
220
220
|
"aria-label": D("bigconsole.parsers.list.searchPlaceholder", "Search parsers..."),
|
|
221
221
|
placeholder: D("bigconsole.parsers.list.searchPlaceholder", "Search parsers..."),
|
|
@@ -232,7 +232,7 @@ var ve = "default", E = u(function() {
|
|
|
232
232
|
value: F || "all",
|
|
233
233
|
onValueChange: we,
|
|
234
234
|
children: [/* @__PURE__ */ w(S, {
|
|
235
|
-
"aria-label": "Filter by parser type",
|
|
235
|
+
"aria-label": D("bigconsole.parsers.filters.typeAriaLabel", "Filter by parser type"),
|
|
236
236
|
className: "min-h-control-md w-full sm:w-[150px]",
|
|
237
237
|
children: /* @__PURE__ */ w(C, { placeholder: D("bigconsole.parsers.filters.allTypes", "All Types") })
|
|
238
238
|
}), /* @__PURE__ */ w(b, { children: ye.map((e) => /* @__PURE__ */ w(x, {
|
|
@@ -244,7 +244,7 @@ var ve = "default", E = u(function() {
|
|
|
244
244
|
value: I || "all",
|
|
245
245
|
onValueChange: Te,
|
|
246
246
|
children: [/* @__PURE__ */ w(S, {
|
|
247
|
-
"aria-label": "Filter by status",
|
|
247
|
+
"aria-label": D("bigconsole.parsers.filters.statusAriaLabel", "Filter by status"),
|
|
248
248
|
className: "min-h-control-md w-full sm:w-[150px]",
|
|
249
249
|
children: /* @__PURE__ */ w(C, { placeholder: D("bigconsole.parsers.filters.allStatus", "All Status") })
|
|
250
250
|
}), /* @__PURE__ */ w(b, { children: be.map((e) => /* @__PURE__ */ w(x, {
|
|
@@ -252,12 +252,12 @@ var ve = "default", E = u(function() {
|
|
|
252
252
|
children: e.label
|
|
253
253
|
}, e.value)) })]
|
|
254
254
|
}),
|
|
255
|
-
$ && /* @__PURE__ */ T(
|
|
255
|
+
$ && /* @__PURE__ */ T(g, {
|
|
256
256
|
variant: "ghost",
|
|
257
257
|
size: "sm",
|
|
258
258
|
onClick: Q,
|
|
259
259
|
className: "min-h-control-md",
|
|
260
|
-
children: [/* @__PURE__ */ w(
|
|
260
|
+
children: [/* @__PURE__ */ w(fe, { style: {
|
|
261
261
|
marginRight: "var(--space-iconGap)",
|
|
262
262
|
height: "var(--size-icon-sm)",
|
|
263
263
|
width: "var(--size-icon-sm)"
|
|
@@ -270,9 +270,9 @@ var ve = "default", E = u(function() {
|
|
|
270
270
|
emphasis: "primary",
|
|
271
271
|
padding: "compact",
|
|
272
272
|
headingLevel: 2,
|
|
273
|
-
title: "Parsers",
|
|
274
|
-
description: "Open a parser to review its expression, or use the actions on a card to edit, export or delete it.",
|
|
275
|
-
meta: /* @__PURE__ */ w(
|
|
273
|
+
title: D("bigconsole.parsers.list.resultsTitle", "Parsers"),
|
|
274
|
+
description: D("bigconsole.parsers.list.resultsDescription", "Open a parser to review its expression, or use the actions on a card to edit, export or delete it."),
|
|
275
|
+
meta: /* @__PURE__ */ w(h, {
|
|
276
276
|
variant: "secondary",
|
|
277
277
|
children: X.length === 1 ? D("bigconsole.parsers.list.countSingular", "{{count}} Parser", { count: X.length }) : D("bigconsole.parsers.list.countPlural", "{{count}} Parsers", { count: X.length })
|
|
278
278
|
}),
|
|
@@ -292,16 +292,16 @@ var ve = "default", E = u(function() {
|
|
|
292
292
|
})]
|
|
293
293
|
})
|
|
294
294
|
}),
|
|
295
|
-
!W && X.length === 0 && !$ && /* @__PURE__ */ w(
|
|
295
|
+
!W && X.length === 0 && !$ && /* @__PURE__ */ w(_, {
|
|
296
296
|
illustration: "empty-data",
|
|
297
297
|
title: D("bigconsole.parsers.list.emptyTitle", "No Parsers yet"),
|
|
298
298
|
description: D("bigconsole.parsers.list.emptyDescription", "Create your first parser to start transforming data with JQ or JSONata."),
|
|
299
|
-
action: /* @__PURE__ */ w(
|
|
299
|
+
action: /* @__PURE__ */ w(se, {
|
|
300
300
|
permission: o.PARSER_CREATE,
|
|
301
301
|
deniedBehavior: "disable",
|
|
302
|
-
children: /* @__PURE__ */ T(
|
|
302
|
+
children: /* @__PURE__ */ T(g, {
|
|
303
303
|
onClick: Z,
|
|
304
|
-
children: [/* @__PURE__ */ w(
|
|
304
|
+
children: [/* @__PURE__ */ w(p, { style: {
|
|
305
305
|
marginRight: "var(--space-iconGap)",
|
|
306
306
|
height: "var(--size-icon-sm)",
|
|
307
307
|
width: "var(--size-icon-sm)"
|
|
@@ -309,11 +309,11 @@ var ve = "default", E = u(function() {
|
|
|
309
309
|
})
|
|
310
310
|
})
|
|
311
311
|
}),
|
|
312
|
-
!W && X.length === 0 && $ && /* @__PURE__ */ w(
|
|
312
|
+
!W && X.length === 0 && $ && /* @__PURE__ */ w(_, {
|
|
313
313
|
illustration: "empty-search",
|
|
314
314
|
title: D("bigconsole.parsers.list.noMatchTitle", "No matching parsers"),
|
|
315
315
|
description: D("bigconsole.parsers.list.noMatchDescription", "No parsers match your filters"),
|
|
316
|
-
action: /* @__PURE__ */ w(
|
|
316
|
+
action: /* @__PURE__ */ w(g, {
|
|
317
317
|
variant: "ghost",
|
|
318
318
|
size: "sm",
|
|
319
319
|
onClick: Q,
|
|
@@ -324,7 +324,7 @@ var ve = "default", E = u(function() {
|
|
|
324
324
|
className: "grid list-none grid-cols-[repeat(auto-fill,minmax(min(100%,18rem),1fr))] gap-form-gap p-0",
|
|
325
325
|
children: X.map((e) => /* @__PURE__ */ w("li", {
|
|
326
326
|
className: "min-w-0",
|
|
327
|
-
children: /* @__PURE__ */ w(
|
|
327
|
+
children: /* @__PURE__ */ w(re, {
|
|
328
328
|
parser: e,
|
|
329
329
|
onView: De,
|
|
330
330
|
onEdit: M ? Oe : void 0,
|
|
@@ -338,7 +338,7 @@ var ve = "default", E = u(function() {
|
|
|
338
338
|
})
|
|
339
339
|
] })
|
|
340
340
|
}),
|
|
341
|
-
/* @__PURE__ */ w(
|
|
341
|
+
/* @__PURE__ */ w(ie, {
|
|
342
342
|
isOpen: Ce,
|
|
343
343
|
onClose: () => U(!1),
|
|
344
344
|
consoleId: P,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataParsersPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataParsersPage.tsx"],"sourcesContent":["/**\n * Data Parsers Page\n *\n * Page for listing and managing parsers with filtering and search.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { memo, useState, useMemo, useCallback } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { useParserStore } from '../store/parserStore';\nimport { useParserOperations } from '../hooks/useParserOperations';\nimport { useExportParser } from '../hooks/useExportParser';\nimport { ParserCard } from '../components/parser/ParserCard';\nimport { ImportParserDialog } from '../components/parser/ImportParserDialog';\nimport { Section, SectionStack } from '../components/common';\nimport type { Parser, ParserType, ParserStatus } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport {\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport { Plus, Search, AlertCircle, FileCode2, X, Upload } from 'lucide-react';\n\n// RBAC\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\n\n// i18n\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface ParserFilterOption<T> {\n value: T | 'all';\n label: string;\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\n// Default console ID for standalone parser listing\nconst DEFAULT_CONSOLE_ID = 'default';\n\nexport const DataParsersPage = memo(function DataParsersPage() {\n const navigate = useAppNavigate();\n const { consoleId: urlConsoleId } = useParams<{ consoleId?: string }>();\n const { t } = useI18n();\n\n // Filter option labels are resolved via i18n inside the component.\n const TYPE_OPTIONS: ParserFilterOption<ParserType>[] = useMemo(\n () => [\n { value: 'all', label: t('bigconsole.parsers.filters.allTypes', 'All Types') },\n { value: 'jq', label: t('bigconsole.parsers.filters.jq', 'JQ') },\n { value: 'jsonata', label: t('bigconsole.parsers.filters.jsonata', 'JSONata') },\n ],\n [t]\n );\n\n const STATUS_OPTIONS: ParserFilterOption<ParserStatus>[] = useMemo(\n () => [\n { value: 'all', label: t('bigconsole.parsers.filters.allStatus', 'All Status') },\n { value: 'active', label: t('bigconsole.parsers.filters.active', 'Active') },\n { value: 'inactive', label: t('bigconsole.parsers.filters.inactive', 'Inactive') },\n ],\n [t]\n );\n\n // RBAC — gate the page and its actions on the seeded parser permissions.\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_READ);\n const canCreate = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_CREATE);\n const canUpdate = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_UPDATE);\n const canDelete = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_DELETE);\n\n // Use URL consoleId or fall back to default\n const consoleId = urlConsoleId || DEFAULT_CONSOLE_ID;\n\n // Store state\n const typeFilter = useParserStore((state) => state.typeFilter);\n const statusFilter = useParserStore((state) => state.statusFilter);\n const searchQuery = useParserStore((state) => state.searchQuery);\n const setTypeFilter = useParserStore((state) => state.setTypeFilter);\n const setStatusFilter = useParserStore((state) => state.setStatusFilter);\n const setSearchQuery = useParserStore((state) => state.setSearchQuery);\n\n // Local state\n const [isDeleting, setIsDeleting] = useState(false);\n const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null);\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n\n // Operations - use consoleId from URL or default\n const { loading, error, parsers, deleteParser, refetch } = useParserOperations({\n consoleId,\n skipFetch: false,\n });\n\n // Export hook\n const { downloadParser } = useExportParser();\n\n // Filter parsers\n const filteredParsers = useMemo(() => {\n if (!parsers) return [];\n\n return parsers.filter((parser: Parser) => {\n if (typeFilter && parser.type !== typeFilter) {\n return false;\n }\n if (statusFilter && parser.status !== statusFilter) {\n return false;\n }\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const nameMatch = parser.name?.toLowerCase().includes(query);\n const descMatch = parser.description?.toLowerCase().includes(query);\n const inputKeyMatch = parser.inputSinkKey?.toLowerCase().includes(query);\n const outputKeyMatch = parser.outputKey?.toLowerCase().includes(query);\n if (!nameMatch && !descMatch && !inputKeyMatch && !outputKeyMatch) {\n return false;\n }\n }\n return true;\n });\n }, [parsers, typeFilter, statusFilter, searchQuery]);\n\n // Handlers\n const handleTypeFilterChange = useCallback(\n (value: string) => {\n setTypeFilter(value === 'all' ? null : (value as ParserType));\n },\n [setTypeFilter]\n );\n\n const handleStatusFilterChange = useCallback(\n (value: string) => {\n setStatusFilter(value === 'all' ? null : (value as ParserStatus));\n },\n [setStatusFilter]\n );\n\n const handleSearchChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchQuery(e.target.value);\n },\n [setSearchQuery]\n );\n\n const handleCreateParser = useCallback(() => {\n // Navigate to dashboard-scoped parser creation if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/new`);\n } else {\n navigate('/parsers/new');\n }\n }, [navigate, urlConsoleId]);\n\n const handleViewParser = useCallback(\n (id: string) => {\n // Navigate to dashboard-scoped parser view if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/${id}`);\n } else {\n navigate(`/parsers/${id}`);\n }\n },\n [navigate, urlConsoleId]\n );\n\n const handleEditParser = useCallback(\n (id: string) => {\n // Navigate to dashboard-scoped parser edit if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/${id}/edit`);\n } else {\n navigate(`/parsers/${id}/edit`);\n }\n },\n [navigate, urlConsoleId]\n );\n\n const handleDeleteParser = useCallback(\n async (id: string) => {\n const native = await nativeConfirm({\n title: t('bigconsole.parsers.confirmDelete.title', 'Delete parser?'),\n message: t('bigconsole.parsers.confirmDelete.message', 'Are you sure you want to delete this parser?'),\n okButtonTitle: t('bigconsole.parsers.actions.delete', 'Delete'),\n cancelButtonTitle: t('bigconsole.parsers.actions.cancel', 'Cancel'),\n });\n const ok =\n native === null\n ? window.confirm(\n t('bigconsole.parsers.confirmDelete.message', 'Are you sure you want to delete this parser?')\n )\n : native;\n if (!ok) return;\n nativeImpact('medium');\n setIsDeleting(true);\n setDeleteTargetId(id);\n try {\n await deleteParser(id);\n if (isNative()) void nativeNotify('success');\n } catch {\n if (isNative()) void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n setDeleteTargetId(null);\n }\n },\n [deleteParser, t]\n );\n\n const handleClearFilters = useCallback(() => {\n setTypeFilter(null);\n setStatusFilter(null);\n setSearchQuery('');\n }, [setTypeFilter, setStatusFilter, setSearchQuery]);\n\n const handleExportParser = useCallback(\n async (id: string) => {\n const parser = parsers.find((p: Parser) => p.id === id);\n if (parser) {\n await downloadParser(id, parser.name);\n }\n },\n [parsers, downloadParser]\n );\n\n const handleImportParser = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_parserId: string, _parserName: string, _wasUpdate: boolean) => {\n // Refetch parsers to show the newly imported one\n refetch();\n },\n [refetch]\n );\n\n const hasActiveFilters = typeFilter !== null || statusFilter !== null || (searchQuery?.length ?? 0) > 0;\n\n // Error state\n if (error) {\n return (\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Section\n tone=\"danger\"\n headingLevel={2}\n title={t('bigconsole.parsers.errors.loadFailed', 'Failed to load Parsers')}\n description=\"The parser list could not be fetched. Retry, or check that the BigConsole service is reachable.\"\n icon={<AlertCircle className=\"h-icon-md w-icon-md text-[var(--color-status-error-text)]\" />}\n actions={\n <Button size=\"sm\" onClick={() => refetch()}>\n {t('bigconsole.parsers.actions.tryAgain', 'Try Again')}\n </Button>\n }\n data-testid=\"parsers-error\"\n >\n <p className=\"font-mono text-xs break-words text-text-secondary\">{error.message}</p>\n </Section>\n </div>\n );\n }\n\n // Read gate — once permissions have loaded, block users without parser:read.\n if (!permissionsLoading && !canRead) {\n return (\n <AccessDenied\n title={t('bigconsole.parsers.accessDenied.title', 'Access Denied')}\n description={t('bigconsole.parsers.accessDenied.list', \"You don't have permission to view parsers.\")}\n />\n );\n }\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Page header strip — structural only (no surface of its own beyond the gloss band) */}\n <div className=\"relative overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"relative\" style={{ padding: 'var(--space-pagePadding)' }}>\n <div className=\"flex flex-wrap items-center justify-between gap-form-gap\">\n <div className=\"flex min-w-0 items-center gap-form-gap\">\n <div\n className=\"flex shrink-0 items-center justify-center\"\n style={{\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <FileCode2\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n <div className=\"min-w-0\">\n <h1 className=\"m-0 text-2xl font-bold text-text-primary sm:text-3xl\">\n <GradientText>{t('bigconsole.parsers.list.title', 'Data Parsers')}</GradientText>\n </h1>\n <p className=\"m-0 text-sm text-text-secondary\">\n {t('bigconsole.parsers.list.subtitle', 'Transform and process data with JQ or JSONata expressions')}\n </p>\n </div>\n </div>\n\n {/*\n Creating and importing a parser both require parser:create, so the\n entire CTA (primary + Import overflow action) is gated on canCreate.\n */}\n <CTAOverflowMenu\n primary={\n canCreate\n ? {\n label: t('bigconsole.parsers.actions.create', 'Create Parser'),\n onSelect: handleCreateParser,\n icon: (\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n ),\n }\n : undefined\n }\n actions={\n canCreate\n ? [\n {\n label: t('bigconsole.parsers.actions.import', 'Import'),\n onSelect: handleImportParser,\n icon: <Upload style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />,\n },\n ]\n : []\n }\n />\n </div>\n </div>\n </div>\n\n {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <SectionStack>\n <PagePurpose className=\"max-w-[80ch]\">\n {t(\n 'bigconsole.parsers.list.purpose',\n 'Parsers define how raw input — log lines, CSVs, JSON, or arbitrary text — is read and mapped into structured fields. Build a parser once, test it against a sample, then reuse it across pipelines so every source lands in a consistent, queryable shape.'\n )}\n </PagePurpose>\n\n {/* Controls — recessed so the eye separates filters from results */}\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={2}\n title=\"Find a parser\"\n headerClassName=\"mb-control-gap\"\n data-testid=\"parser-filters\"\n >\n <div className=\"flex flex-wrap items-center gap-control-gap\">\n {/* Search Bar */}\n <div className=\"relative w-full min-w-0 flex-1 sm:max-w-[400px]\">\n <Search\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n left: 'var(--space-controlGap)',\n top: '50%',\n transform: 'translateY(-50%)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <Input\n type=\"text\"\n aria-label={t('bigconsole.parsers.list.searchPlaceholder', 'Search parsers...')}\n placeholder={t('bigconsole.parsers.list.searchPlaceholder', 'Search parsers...')}\n value={searchQuery || ''}\n onChange={handleSearchChange}\n className=\"min-h-control-md\"\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Type Filter */}\n <Select value={typeFilter || 'all'} onValueChange={handleTypeFilterChange}>\n <SelectTrigger aria-label=\"Filter by parser type\" className=\"min-h-control-md w-full sm:w-[150px]\">\n <SelectValue placeholder={t('bigconsole.parsers.filters.allTypes', 'All Types')} />\n </SelectTrigger>\n <SelectContent>\n {TYPE_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Status Filter */}\n <Select value={statusFilter || 'all'} onValueChange={handleStatusFilterChange}>\n <SelectTrigger aria-label=\"Filter by status\" className=\"min-h-control-md w-full sm:w-[150px]\">\n <SelectValue placeholder={t('bigconsole.parsers.filters.allStatus', 'All Status')} />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Clear Filters */}\n {hasActiveFilters && (\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters} className=\"min-h-control-md\">\n <X\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {t('bigconsole.parsers.filters.clear', 'Clear Filters')}\n </Button>\n )}\n </div>\n </Section>\n\n {/* Results — the block this route exists for */}\n <Section\n emphasis=\"primary\"\n padding=\"compact\"\n headingLevel={2}\n title=\"Parsers\"\n description=\"Open a parser to review its expression, or use the actions on a card to edit, export or delete it.\"\n meta={\n <Badge variant=\"secondary\">\n {filteredParsers.length === 1\n ? t('bigconsole.parsers.list.countSingular', '{{count}} Parser', {\n count: filteredParsers.length,\n })\n : t('bigconsole.parsers.list.countPlural', '{{count}} Parsers', {\n count: filteredParsers.length,\n })}\n </Badge>\n }\n data-testid=\"parser-results\"\n >\n {/* Loading state — keeps the section frame so the layout does not collapse */}\n {loading && (\n <div className=\"flex items-center justify-center py-12 sm:py-16\">\n <div className=\"text-center\">\n <Spinner\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n margin: '0 auto var(--space-formGap)',\n }}\n />\n <p className=\"text-sm text-text-muted\">\n {t('bigconsole.parsers.list.loading', 'Loading parsers...')}\n </p>\n </div>\n </div>\n )}\n\n {/* Empty state */}\n {!loading && filteredParsers.length === 0 && !hasActiveFilters && (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('bigconsole.parsers.list.emptyTitle', 'No Parsers yet')}\n description={t(\n 'bigconsole.parsers.list.emptyDescription',\n 'Create your first parser to start transforming data with JQ or JSONata.'\n )}\n action={\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.PARSER_CREATE} deniedBehavior=\"disable\">\n <Button onClick={handleCreateParser}>\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {t('bigconsole.parsers.actions.create', 'Create Parser')}\n </Button>\n </PermissionButton>\n }\n />\n )}\n\n {/* No results state */}\n {!loading && filteredParsers.length === 0 && hasActiveFilters && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={t('bigconsole.parsers.list.noMatchTitle', 'No matching parsers')}\n description={t('bigconsole.parsers.list.noMatchDescription', 'No parsers match your filters')}\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n {t('bigconsole.parsers.filters.clear', 'Clear Filters')}\n </Button>\n }\n />\n )}\n\n {/* Parser Grid */}\n {!loading && filteredParsers.length > 0 && (\n <ul className=\"grid list-none grid-cols-[repeat(auto-fill,minmax(min(100%,18rem),1fr))] gap-form-gap p-0\">\n {filteredParsers.map((parser: Parser) => (\n <li key={parser.id} className=\"min-w-0\">\n <ParserCard\n parser={parser}\n onView={handleViewParser}\n // ParserCard renders each action icon only when its handler is\n // provided, so gate edit/delete by passing undefined when the\n // caller lacks the corresponding permission. Export is a\n // read-only download and stays available to any reader.\n onEdit={canUpdate ? handleEditParser : undefined}\n onExport={handleExportParser}\n onDelete={canDelete ? handleDeleteParser : undefined}\n isDeleting={isDeleting && deleteTargetId === parser.id}\n />\n </li>\n ))}\n </ul>\n )}\n </Section>\n </SectionStack>\n </div>\n\n {/* Import Parser Dialog */}\n <ImportParserDialog\n isOpen={isImportDialogOpen}\n onClose={() => setIsImportDialogOpen(false)}\n consoleId={consoleId}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DataParsersPage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataParsersPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataParsersPage.tsx"],"sourcesContent":["/**\n * Data Parsers Page\n *\n * Page for listing and managing parsers with filtering and search.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { memo, useState, useMemo, useCallback } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { useParserStore } from '../store/parserStore';\nimport { useParserOperations } from '../hooks/useParserOperations';\nimport { useExportParser } from '../hooks/useExportParser';\nimport { ParserCard } from '../components/parser/ParserCard';\nimport { ImportParserDialog } from '../components/parser/ImportParserDialog';\nimport { Section, SectionStack } from '../components/common';\nimport type { Parser, ParserType, ParserStatus } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport {\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport { Plus, Search, AlertCircle, FileCode2, X, Upload } from 'lucide-react';\n\n// RBAC\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\n\n// i18n\nimport { useTr } from '../../shared/hooks/useTr';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface ParserFilterOption<T> {\n value: T | 'all';\n label: string;\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\n// Default console ID for standalone parser listing\nconst DEFAULT_CONSOLE_ID = 'default';\n\nexport const DataParsersPage = memo(function DataParsersPage() {\n const navigate = useAppNavigate();\n const { consoleId: urlConsoleId } = useParams<{ consoleId?: string }>();\n const tr = useTr();\n\n // Filter option labels are resolved via i18n inside the component.\n const TYPE_OPTIONS: ParserFilterOption<ParserType>[] = useMemo(\n () => [\n { value: 'all', label: tr('bigconsole.parsers.filters.allTypes', 'All Types') },\n { value: 'jq', label: tr('bigconsole.parsers.filters.jq', 'JQ') },\n { value: 'jsonata', label: tr('bigconsole.parsers.filters.jsonata', 'JSONata') },\n ],\n [tr]\n );\n\n const STATUS_OPTIONS: ParserFilterOption<ParserStatus>[] = useMemo(\n () => [\n { value: 'all', label: tr('bigconsole.parsers.filters.allStatus', 'All Status') },\n { value: 'active', label: tr('bigconsole.parsers.filters.active', 'Active') },\n { value: 'inactive', label: tr('bigconsole.parsers.filters.inactive', 'Inactive') },\n ],\n [tr]\n );\n\n // RBAC — gate the page and its actions on the seeded parser permissions.\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_READ);\n const canCreate = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_CREATE);\n const canUpdate = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_UPDATE);\n const canDelete = hasPermission(BIGCONSOLE_PERMISSIONS.PARSER_DELETE);\n\n // Use URL consoleId or fall back to default\n const consoleId = urlConsoleId || DEFAULT_CONSOLE_ID;\n\n // Store state\n const typeFilter = useParserStore((state) => state.typeFilter);\n const statusFilter = useParserStore((state) => state.statusFilter);\n const searchQuery = useParserStore((state) => state.searchQuery);\n const setTypeFilter = useParserStore((state) => state.setTypeFilter);\n const setStatusFilter = useParserStore((state) => state.setStatusFilter);\n const setSearchQuery = useParserStore((state) => state.setSearchQuery);\n\n // Local state\n const [isDeleting, setIsDeleting] = useState(false);\n const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null);\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n\n // Operations - use consoleId from URL or default\n const { loading, error, parsers, deleteParser, refetch } = useParserOperations({\n consoleId,\n skipFetch: false,\n });\n\n // Export hook\n const { downloadParser } = useExportParser();\n\n // Filter parsers\n const filteredParsers = useMemo(() => {\n if (!parsers) return [];\n\n return parsers.filter((parser: Parser) => {\n if (typeFilter && parser.type !== typeFilter) {\n return false;\n }\n if (statusFilter && parser.status !== statusFilter) {\n return false;\n }\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const nameMatch = parser.name?.toLowerCase().includes(query);\n const descMatch = parser.description?.toLowerCase().includes(query);\n const inputKeyMatch = parser.inputSinkKey?.toLowerCase().includes(query);\n const outputKeyMatch = parser.outputKey?.toLowerCase().includes(query);\n if (!nameMatch && !descMatch && !inputKeyMatch && !outputKeyMatch) {\n return false;\n }\n }\n return true;\n });\n }, [parsers, typeFilter, statusFilter, searchQuery]);\n\n // Handlers\n const handleTypeFilterChange = useCallback(\n (value: string) => {\n setTypeFilter(value === 'all' ? null : (value as ParserType));\n },\n [setTypeFilter]\n );\n\n const handleStatusFilterChange = useCallback(\n (value: string) => {\n setStatusFilter(value === 'all' ? null : (value as ParserStatus));\n },\n [setStatusFilter]\n );\n\n const handleSearchChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchQuery(e.target.value);\n },\n [setSearchQuery]\n );\n\n const handleCreateParser = useCallback(() => {\n // Navigate to dashboard-scoped parser creation if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/new`);\n } else {\n navigate('/parsers/new');\n }\n }, [navigate, urlConsoleId]);\n\n const handleViewParser = useCallback(\n (id: string) => {\n // Navigate to dashboard-scoped parser view if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/${id}`);\n } else {\n navigate(`/parsers/${id}`);\n }\n },\n [navigate, urlConsoleId]\n );\n\n const handleEditParser = useCallback(\n (id: string) => {\n // Navigate to dashboard-scoped parser edit if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/${id}/edit`);\n } else {\n navigate(`/parsers/${id}/edit`);\n }\n },\n [navigate, urlConsoleId]\n );\n\n const handleDeleteParser = useCallback(\n async (id: string) => {\n const native = await nativeConfirm({\n title: tr('bigconsole.parsers.confirmDelete.title', 'Delete parser?'),\n message: tr('bigconsole.parsers.confirmDelete.message', 'Are you sure you want to delete this parser?'),\n okButtonTitle: tr('bigconsole.parsers.actions.delete', 'Delete'),\n cancelButtonTitle: tr('bigconsole.parsers.actions.cancel', 'Cancel'),\n });\n const ok =\n native === null\n ? window.confirm(\n tr('bigconsole.parsers.confirmDelete.message', 'Are you sure you want to delete this parser?')\n )\n : native;\n if (!ok) return;\n nativeImpact('medium');\n setIsDeleting(true);\n setDeleteTargetId(id);\n try {\n await deleteParser(id);\n if (isNative()) void nativeNotify('success');\n } catch {\n if (isNative()) void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n setDeleteTargetId(null);\n }\n },\n [deleteParser, tr]\n );\n\n const handleClearFilters = useCallback(() => {\n setTypeFilter(null);\n setStatusFilter(null);\n setSearchQuery('');\n }, [setTypeFilter, setStatusFilter, setSearchQuery]);\n\n const handleExportParser = useCallback(\n async (id: string) => {\n const parser = parsers.find((p: Parser) => p.id === id);\n if (parser) {\n await downloadParser(id, parser.name);\n }\n },\n [parsers, downloadParser]\n );\n\n const handleImportParser = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_parserId: string, _parserName: string, _wasUpdate: boolean) => {\n // Refetch parsers to show the newly imported one\n refetch();\n },\n [refetch]\n );\n\n const hasActiveFilters = typeFilter !== null || statusFilter !== null || (searchQuery?.length ?? 0) > 0;\n\n // Error state\n if (error) {\n return (\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Section\n tone=\"danger\"\n headingLevel={2}\n title={tr('bigconsole.parsers.errors.loadFailed', 'Failed to load Parsers')}\n description=\"The parser list could not be fetched. Retry, or check that the BigConsole service is reachable.\"\n icon={<AlertCircle className=\"h-icon-md w-icon-md text-[var(--color-status-error-text)]\" />}\n actions={\n <Button size=\"sm\" onClick={() => refetch()}>\n {tr('bigconsole.parsers.actions.tryAgain', 'Try Again')}\n </Button>\n }\n data-testid=\"parsers-error\"\n >\n <p className=\"font-mono text-xs break-words text-text-secondary\">{error.message}</p>\n </Section>\n </div>\n );\n }\n\n // Read gate — once permissions have loaded, block users without parser:read.\n if (!permissionsLoading && !canRead) {\n return (\n <AccessDenied\n title={tr('bigconsole.parsers.accessDenied.title', 'Access Denied')}\n description={tr('bigconsole.parsers.accessDenied.list', \"You don't have permission to view parsers.\")}\n />\n );\n }\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Page header strip — structural only (no surface of its own beyond the gloss band) */}\n <div className=\"relative overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"relative\" style={{ padding: 'var(--space-pagePadding)' }}>\n <div className=\"flex flex-wrap items-center justify-between gap-form-gap\">\n <div className=\"flex min-w-0 items-center gap-form-gap\">\n <div\n className=\"flex shrink-0 items-center justify-center\"\n style={{\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <FileCode2\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n <div className=\"min-w-0\">\n <h1 className=\"m-0 text-2xl font-bold text-text-primary sm:text-3xl\">\n <GradientText>{tr('bigconsole.parsers.list.title', 'Data Parsers')}</GradientText>\n </h1>\n <p className=\"m-0 text-sm text-text-secondary\">\n {tr('bigconsole.parsers.list.subtitle', 'Transform and process data with JQ or JSONata expressions')}\n </p>\n </div>\n </div>\n\n {/*\n Creating and importing a parser both require parser:create, so the\n entire CTA (primary + Import overflow action) is gated on canCreate.\n */}\n <CTAOverflowMenu\n primary={\n canCreate\n ? {\n label: tr('bigconsole.parsers.actions.create', 'Create Parser'),\n onSelect: handleCreateParser,\n icon: (\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n ),\n }\n : undefined\n }\n actions={\n canCreate\n ? [\n {\n label: tr('bigconsole.parsers.actions.import', 'Import'),\n onSelect: handleImportParser,\n icon: <Upload style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />,\n },\n ]\n : []\n }\n />\n </div>\n </div>\n </div>\n\n {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <SectionStack>\n <PagePurpose className=\"max-w-[80ch]\">\n {tr(\n 'bigconsole.parsers.list.purpose',\n 'Parsers define how raw input — log lines, CSVs, JSON, or arbitrary text — is read and mapped into structured fields. Build a parser once, test it against a sample, then reuse it across pipelines so every source lands in a consistent, queryable shape.'\n )}\n </PagePurpose>\n\n {/* Controls — recessed so the eye separates filters from results */}\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={2}\n title={tr('bigconsole.parsers.list.findTitle', 'Find a parser')}\n headerClassName=\"mb-control-gap\"\n data-testid=\"parser-filters\"\n >\n <div className=\"flex flex-wrap items-center gap-control-gap\">\n {/* Search Bar */}\n <div className=\"relative w-full min-w-0 flex-1 sm:max-w-[400px]\">\n <Search\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n left: 'var(--space-controlGap)',\n top: '50%',\n transform: 'translateY(-50%)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <Input\n type=\"text\"\n aria-label={tr('bigconsole.parsers.list.searchPlaceholder', 'Search parsers...')}\n placeholder={tr('bigconsole.parsers.list.searchPlaceholder', 'Search parsers...')}\n value={searchQuery || ''}\n onChange={handleSearchChange}\n className=\"min-h-control-md\"\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Type Filter */}\n <Select value={typeFilter || 'all'} onValueChange={handleTypeFilterChange}>\n <SelectTrigger\n aria-label={tr('bigconsole.parsers.filters.typeAriaLabel', 'Filter by parser type')}\n className=\"min-h-control-md w-full sm:w-[150px]\"\n >\n <SelectValue placeholder={tr('bigconsole.parsers.filters.allTypes', 'All Types')} />\n </SelectTrigger>\n <SelectContent>\n {TYPE_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Status Filter */}\n <Select value={statusFilter || 'all'} onValueChange={handleStatusFilterChange}>\n <SelectTrigger\n aria-label={tr('bigconsole.parsers.filters.statusAriaLabel', 'Filter by status')}\n className=\"min-h-control-md w-full sm:w-[150px]\"\n >\n <SelectValue placeholder={tr('bigconsole.parsers.filters.allStatus', 'All Status')} />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Clear Filters */}\n {hasActiveFilters && (\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters} className=\"min-h-control-md\">\n <X\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.parsers.filters.clear', 'Clear Filters')}\n </Button>\n )}\n </div>\n </Section>\n\n {/* Results — the block this route exists for */}\n <Section\n emphasis=\"primary\"\n padding=\"compact\"\n headingLevel={2}\n title={tr('bigconsole.parsers.list.resultsTitle', 'Parsers')}\n description={tr(\n 'bigconsole.parsers.list.resultsDescription',\n 'Open a parser to review its expression, or use the actions on a card to edit, export or delete it.'\n )}\n meta={\n <Badge variant=\"secondary\">\n {filteredParsers.length === 1\n ? tr('bigconsole.parsers.list.countSingular', '{{count}} Parser', {\n count: filteredParsers.length,\n })\n : tr('bigconsole.parsers.list.countPlural', '{{count}} Parsers', {\n count: filteredParsers.length,\n })}\n </Badge>\n }\n data-testid=\"parser-results\"\n >\n {/* Loading state — keeps the section frame so the layout does not collapse */}\n {loading && (\n <div className=\"flex items-center justify-center py-12 sm:py-16\">\n <div className=\"text-center\">\n <Spinner\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n margin: '0 auto var(--space-formGap)',\n }}\n />\n <p className=\"text-sm text-text-muted\">\n {tr('bigconsole.parsers.list.loading', 'Loading parsers...')}\n </p>\n </div>\n </div>\n )}\n\n {/* Empty state */}\n {!loading && filteredParsers.length === 0 && !hasActiveFilters && (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('bigconsole.parsers.list.emptyTitle', 'No Parsers yet')}\n description={tr(\n 'bigconsole.parsers.list.emptyDescription',\n 'Create your first parser to start transforming data with JQ or JSONata.'\n )}\n action={\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.PARSER_CREATE} deniedBehavior=\"disable\">\n <Button onClick={handleCreateParser}>\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.parsers.actions.create', 'Create Parser')}\n </Button>\n </PermissionButton>\n }\n />\n )}\n\n {/* No results state */}\n {!loading && filteredParsers.length === 0 && hasActiveFilters && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tr('bigconsole.parsers.list.noMatchTitle', 'No matching parsers')}\n description={tr('bigconsole.parsers.list.noMatchDescription', 'No parsers match your filters')}\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n {tr('bigconsole.parsers.filters.clear', 'Clear Filters')}\n </Button>\n }\n />\n )}\n\n {/* Parser Grid */}\n {!loading && filteredParsers.length > 0 && (\n <ul className=\"grid list-none grid-cols-[repeat(auto-fill,minmax(min(100%,18rem),1fr))] gap-form-gap p-0\">\n {filteredParsers.map((parser: Parser) => (\n <li key={parser.id} className=\"min-w-0\">\n <ParserCard\n parser={parser}\n onView={handleViewParser}\n // ParserCard renders each action icon only when its handler is\n // provided, so gate edit/delete by passing undefined when the\n // caller lacks the corresponding permission. Export is a\n // read-only download and stays available to any reader.\n onEdit={canUpdate ? handleEditParser : undefined}\n onExport={handleExportParser}\n onDelete={canDelete ? handleDeleteParser : undefined}\n isDeleting={isDeleting && deleteTargetId === parser.id}\n />\n </li>\n ))}\n </ul>\n )}\n </Section>\n </SectionStack>\n </div>\n\n {/* Import Parser Dialog */}\n <ImportParserDialog\n isOpen={isImportDialogOpen}\n onClose={() => setIsImportDialogOpen(false)}\n consoleId={consoleId}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DataParsersPage;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback as e } from "react";
|
|
2
|
+
import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
3
|
+
//#region src/shared/hooks/useTr.ts
|
|
4
|
+
function n() {
|
|
5
|
+
let { t: n } = t();
|
|
6
|
+
return e((e, t, r) => {
|
|
7
|
+
let i = n(e), a = i === e ? t : i;
|
|
8
|
+
return r ? Object.entries(r).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), a) : a;
|
|
9
|
+
}, [n]);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { n as useTr };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=useTr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTr.js","names":[],"sources":["../../../src/shared/hooks/useTr.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('bigconsole.dashboards.title', 'Dashboards')}</h1>;\n * return <p>{tr('bigconsole.delete.confirm', 'Delete {{count}} items?', { count: 5 })}</p>;\n */\nexport function useTr(): (key: string, fallback: string, params?: Record<string, string | number>) => string {\n const { t } = useI18n();\n return useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key);\n const raw = translated === key ? fallback : translated;\n if (!params) return raw;\n return Object.entries(params).reduce(\n (str, [k, v]) => str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v)),\n raw\n );\n },\n [t]\n );\n}\n"],"mappings":";;;AAkBA,SAAgB,IAA6F;CAC3G,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,GACJ,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAa,EAAE,EAAI,EACnB,IAAM,MAAe,IAAM,IAAW;AAE5C,SADK,IACE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAG,OAAO,EAAI,QAAY,OAAO,SAAS,EAAE,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC,EAC5E,EACD,GAJmB;IAMtB,CAAC,EAAE,CACJ"}
|