@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.
@@ -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 c, nativeNotify as l } from "../../utils/nativeBridge.js";
10
- import te from "../components/parser/ParserCard.js";
11
- import ne from "../components/parser/ImportParserDialog.js";
12
- import { memo as u, useCallback as d, useMemo as f, useState as p } from "react";
13
- import { useParams as re } from "react-router";
14
- import { AccessDenied as ie, PermissionButton as ae } from "@burdenoff/fe-libs/shared/components";
15
- import { usePermissions as oe } from "@burdenoff/fe-libs/shared/providers/shell";
16
- import { AlertCircle as se, FileCode2 as ce, Plus as m, Search as le, Upload as ue, X as h } from "lucide-react";
17
- import { useI18n as de } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
18
- import { AuroraBackground as fe, Badge as g, Button as _, CTAOverflowMenu as pe, GradientText as me, IllustratedEmptyState as v, Input as he, PagePurpose as ge, Select as y, SelectContent as b, SelectItem as x, SelectTrigger as S, SelectValue as C, Spinner as _e } from "@burdenoff/fe-libs/ui";
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 = u(function() {
22
- let u = t(), { consoleId: E } = re(), { t: D } = de(), ye = f(() => [
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 = f(() => [
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 } = oe(), 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] = p(!1), [Se, H] = p(null), [Ce, U] = p(!1), { loading: W, error: G, parsers: K, deleteParser: q, refetch: J } = i({
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 = f(() => K ? K.filter((e) => {
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 = d((e) => {
63
+ ]), we = u((e) => {
64
64
  R(e === "all" ? null : e);
65
- }, [R]), Te = d((e) => {
65
+ }, [R]), Te = u((e) => {
66
66
  z(e === "all" ? null : e);
67
- }, [z]), Ee = d((e) => {
67
+ }, [z]), Ee = u((e) => {
68
68
  B(e.target.value);
69
- }, [B]), Z = d(() => {
70
- u(E ? `/dashboards/${E}/parsers/new` : "/parsers/new");
71
- }, [u, E]), De = d((e) => {
72
- u(E ? `/dashboards/${E}/parsers/${e}` : `/parsers/${e}`);
73
- }, [u, E]), Oe = d((e) => {
74
- u(E ? `/dashboards/${E}/parsers/${e}/edit` : `/parsers/${e}/edit`);
75
- }, [u, E]), ke = d(async (e) => {
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
- c("medium"), V(!0), H(e);
83
+ te("medium"), V(!0), H(e);
84
84
  try {
85
- await q(e), s() && l("success");
85
+ await q(e), s() && c("success");
86
86
  } catch {
87
- s() && l("error");
87
+ s() && c("error");
88
88
  } finally {
89
89
  V(!1), H(null);
90
90
  }
91
91
  }
92
- }, [q, D]), Q = d(() => {
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 = d(async (e) => {
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 = d(() => {
101
+ }, [K, Y]), je = u(() => {
102
102
  U(!0);
103
- }, []), Me = d((e, t, n) => {
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(se, { className: "h-icon-md w-icon-md text-[var(--color-status-error-text)]" }),
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(ie, {
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(fe, { intensity: "subtle" }), /* @__PURE__ */ w("div", {
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(ce, { style: {
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(me, { children: D("bigconsole.parsers.list.title", "Data Parsers") })
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(pe, {
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(m, { style: {
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(ue, { style: {
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(ge, {
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(le, {
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(he, {
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(h, { style: {
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(g, {
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(v, {
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(ae, {
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(m, { style: {
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(v, {
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(te, {
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(ne, {
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.828.1",
3
+ "version": "2026.829.2",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [