@burdenoff/microfe-bigconsole 2026.707.4 → 2026.708.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,7 @@ function s(e, t = {}) {
24
24
  description: e.description,
25
25
  key: e.key,
26
26
  config: e.config,
27
+ data: e.data,
27
28
  ttl: e.ttl,
28
29
  isActive: e.isActive,
29
30
  fieldMaskingEnabled: e.fieldMaskingEnabled,
@@ -1 +1 @@
1
- {"version":3,"file":"useExportDataSink.js","names":[],"sources":["../../../src/bigconsole/hooks/useExportDataSink.ts"],"sourcesContent":["/**\n * useExportDataSink Hook\n *\n * Provides DataSink export functionality with file download.\n * Exports DataSink configuration as JSON for backup/transfer.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { GetDataSinkDocument } from '../../generated/wspace-operations';\nimport type { DataSink } from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ExportDataSinkOptions {\n /** Pretty print JSON output (default: true) */\n prettyPrint?: boolean;\n /** Include linked workflow info (default: false, for portability) */\n includeWorkflowLinks?: boolean;\n}\n\nexport interface UseExportDataSinkResult {\n /** Loading state */\n loading: boolean;\n /** Error state */\n error: Error | null;\n /** Export DataSink to JSON string */\n exportDataSink: (dataSinkId: string, options?: ExportDataSinkOptions) => Promise<string | null>;\n /** Export and download DataSink as JSON file */\n downloadDataSink: (dataSinkId: string, dataSinkName?: string, options?: ExportDataSinkOptions) => Promise<boolean>;\n /** Clear error */\n clearError: () => void;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst EXPORT_SCHEMA_VERSION = '1.0.0';\n\n// ============================================================================\n// Helper: Download JSON file\n// ============================================================================\n\nfunction downloadJsonFile(content: string, filename: string): void {\n const blob = new Blob([content], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\nfunction sanitizeFilename(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .substring(0, 50);\n}\n\n// ============================================================================\n// Helper: Create export structure\n// ============================================================================\n\ninterface DataSinkExportStructure {\n schemaVersion: string;\n exportedAt: string;\n exportedFrom: {\n dataSinkId: string;\n workspaceId?: string;\n };\n dataSink: {\n id: string;\n name: string | null;\n description: string | null;\n key: string;\n config: Record<string, unknown> | null;\n ttl: number;\n isActive: boolean;\n fieldMaskingEnabled: boolean;\n fieldMaskingRules: Record<string, unknown> | null;\n status: string;\n setupState: string;\n refresh: DataSink['refresh'];\n exports: DataSink['exports'];\n alerts: DataSink['alerts'];\n quota: DataSink['quota'];\n metadata: Record<string, unknown> | null;\n };\n}\n\nfunction createExportStructure(dataSink: DataSink, options: ExportDataSinkOptions = {}): DataSinkExportStructure {\n return {\n schemaVersion: EXPORT_SCHEMA_VERSION,\n exportedAt: new Date().toISOString(),\n exportedFrom: {\n dataSinkId: dataSink.id,\n workspaceId: dataSink.workspaceId,\n },\n dataSink: {\n id: dataSink.id,\n name: dataSink.name,\n description: dataSink.description,\n key: dataSink.key,\n config: dataSink.config,\n ttl: dataSink.ttl,\n isActive: dataSink.isActive,\n fieldMaskingEnabled: dataSink.fieldMaskingEnabled,\n fieldMaskingRules: dataSink.fieldMaskingRules,\n status: dataSink.status,\n setupState: dataSink.setupState,\n refresh: dataSink.refresh,\n exports: dataSink.exports,\n alerts: dataSink.alerts,\n quota: dataSink.quota,\n metadata: dataSink.metadata,\n },\n };\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useExportDataSink(): UseExportDataSinkResult {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const apolloClient = useApolloClient();\n\n // Export DataSink to JSON string\n const exportDataSink = useCallback(\n async (dataSinkId: string, options: ExportDataSinkOptions = {}): Promise<string | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.query<{ getDataSink?: DataSink }>({\n query: GetDataSinkDocument,\n variables: { id: dataSinkId },\n fetchPolicy: 'network-only',\n });\n\n const dataSink = result.data?.getDataSink;\n\n if (!dataSink) {\n throw new Error('DataSink not found');\n }\n\n const exportData = createExportStructure(dataSink, options);\n const prettyPrint = options.prettyPrint ?? true;\n const jsonContent = prettyPrint ? JSON.stringify(exportData, null, 2) : JSON.stringify(exportData);\n\n return jsonContent;\n } catch (err) {\n const exportError = err instanceof Error ? err : new Error('Failed to export DataSink');\n setError(exportError);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n // Export and download DataSink as JSON file\n const downloadDataSink = useCallback(\n async (dataSinkId: string, dataSinkName?: string, options: ExportDataSinkOptions = {}): Promise<boolean> => {\n try {\n const content = await exportDataSink(dataSinkId, options);\n\n if (!content) {\n return false;\n }\n\n // Extract name from content if not provided\n let filename = dataSinkName;\n try {\n const parsed = JSON.parse(content);\n filename = filename || parsed.dataSink?.name || parsed.dataSink?.key || 'datasink';\n } catch {\n filename = filename || 'datasink';\n }\n\n const sanitizedName = sanitizeFilename(filename || 'datasink');\n const timestamp = new Date().toISOString().split('T')[0];\n const fullFilename = `${sanitizedName}-datasink-export-${timestamp}.json`;\n\n downloadJsonFile(content, fullFilename);\n\n return true;\n } catch (err) {\n const downloadError = err instanceof Error ? err : new Error('Failed to download DataSink');\n setError(downloadError);\n return false;\n }\n },\n [exportDataSink]\n );\n\n // Clear error\n const clearError = useCallback(() => {\n setError(null);\n }, []);\n\n return {\n loading,\n error,\n exportDataSink,\n downloadDataSink,\n clearError,\n };\n}\n\nexport default useExportDataSink;\n"],"mappings":";;;;AAwCA,IAAM,IAAwB;AAM9B,SAAS,EAAiB,GAAiB,GAAwB;CACjE,IAAM,IAAO,IAAI,KAAK,CAAC,EAAQ,EAAE,EAAE,MAAM,oBAAoB,CAAC,EACxD,IAAM,IAAI,gBAAgB,EAAK,EAC/B,IAAO,SAAS,cAAc,IAAI;AAMxC,CALA,EAAK,OAAO,GACZ,EAAK,WAAW,GAChB,SAAS,KAAK,YAAY,EAAK,EAC/B,EAAK,OAAO,EACZ,SAAS,KAAK,YAAY,EAAK,EAC/B,IAAI,gBAAgB,EAAI;;AAG1B,SAAS,EAAiB,GAAsB;AAC9C,QAAO,EACJ,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,CACvB,UAAU,GAAG,GAAG;;AAkCrB,SAAS,EAAsB,GAAoB,IAAiC,EAAE,EAA2B;AAC/G,QAAO;EACL,eAAe;EACf,6BAAY,IAAI,MAAM,EAAC,aAAa;EACpC,cAAc;GACZ,YAAY,EAAS;GACrB,aAAa,EAAS;GACvB;EACD,UAAU;GACR,IAAI,EAAS;GACb,MAAM,EAAS;GACf,aAAa,EAAS;GACtB,KAAK,EAAS;GACd,QAAQ,EAAS;GACjB,KAAK,EAAS;GACd,UAAU,EAAS;GACnB,qBAAqB,EAAS;GAC9B,mBAAmB,EAAS;GAC5B,QAAQ,EAAS;GACjB,YAAY,EAAS;GACrB,SAAS,EAAS;GAClB,SAAS,EAAS;GAClB,QAAQ,EAAS;GACjB,OAAO,EAAS;GAChB,UAAU,EAAS;GACpB;EACF;;AAOH,SAAgB,IAA6C;CAC3D,IAAM,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAuB,KAAK,EAEhD,IAAe,GAAiB,EAGhC,IAAiB,EACrB,OAAO,GAAoB,IAAiC,EAAE,KAA6B;AACzF,MAAI;AAEF,GADA,EAAW,GAAK,EAChB,EAAS,KAAK;GAQd,IAAM,KANS,MAAM,EAAa,MAAkC;IAClE,OAAO;IACP,WAAW,EAAE,IAAI,GAAY;IAC7B,aAAa;IACd,CAAC,EAEsB,MAAM;AAE9B,OAAI,CAAC,EACH,OAAU,MAAM,qBAAqB;GAGvC,IAAM,IAAa,EAAsB,GAAU,EAAQ;AAI3D,UAHoB,EAAQ,eAAe,KACT,KAAK,UAAU,GAAY,MAAM,EAAE,GAAG,KAAK,UAAU,EAAW;WAG3F,GAAK;AAGZ,UADA,EADoB,aAAe,QAAQ,IAAM,gBAAI,MAAM,4BAA4B,CAClE,EACd;YACC;AACR,KAAW,GAAM;;IAGrB,CAAC,EAAa,CACf;AA0CD,QAAO;EACL;EACA;EACA;EACA,kBA3CuB,EACvB,OAAO,GAAoB,GAAuB,IAAiC,EAAE,KAAuB;AAC1G,OAAI;IACF,IAAM,IAAU,MAAM,EAAe,GAAY,EAAQ;AAEzD,QAAI,CAAC,EACH,QAAO;IAIT,IAAI,IAAW;AACf,QAAI;KACF,IAAM,IAAS,KAAK,MAAM,EAAQ;AAClC,SAAW,KAAY,EAAO,UAAU,QAAQ,EAAO,UAAU,OAAO;YAClE;AACN,WAAuB;;AASzB,WAFA,EAAiB,GAFI,GAFC,EAAiB,KAAY,WAAW,CAExB,oCADpB,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,IAAI,CAAC,GACa,OAE5B,EAEhC;YACA,GAAK;AAGZ,WADA,EADsB,aAAe,QAAQ,IAAM,gBAAI,MAAM,8BAA8B,CACpE,EAChB;;KAGX,CAAC,EAAe,CACjB;EAYC,YATiB,QAAkB;AACnC,KAAS,KAAK;KACb,EAAE,CAAC;EAQL"}
1
+ {"version":3,"file":"useExportDataSink.js","names":[],"sources":["../../../src/bigconsole/hooks/useExportDataSink.ts"],"sourcesContent":["/**\n * useExportDataSink Hook\n *\n * Provides DataSink export functionality with file download.\n * Exports DataSink configuration as JSON for backup/transfer.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { GetDataSinkDocument } from '../../generated/wspace-operations';\nimport type { DataSink } from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ExportDataSinkOptions {\n /** Pretty print JSON output (default: true) */\n prettyPrint?: boolean;\n /** Include linked workflow info (default: false, for portability) */\n includeWorkflowLinks?: boolean;\n}\n\nexport interface UseExportDataSinkResult {\n /** Loading state */\n loading: boolean;\n /** Error state */\n error: Error | null;\n /** Export DataSink to JSON string */\n exportDataSink: (dataSinkId: string, options?: ExportDataSinkOptions) => Promise<string | null>;\n /** Export and download DataSink as JSON file */\n downloadDataSink: (dataSinkId: string, dataSinkName?: string, options?: ExportDataSinkOptions) => Promise<boolean>;\n /** Clear error */\n clearError: () => void;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst EXPORT_SCHEMA_VERSION = '1.0.0';\n\n// ============================================================================\n// Helper: Download JSON file\n// ============================================================================\n\nfunction downloadJsonFile(content: string, filename: string): void {\n const blob = new Blob([content], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\nfunction sanitizeFilename(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .substring(0, 50);\n}\n\n// ============================================================================\n// Helper: Create export structure\n// ============================================================================\n\ninterface DataSinkExportStructure {\n schemaVersion: string;\n exportedAt: string;\n exportedFrom: {\n dataSinkId: string;\n workspaceId?: string;\n };\n dataSink: {\n id: string;\n name: string | null;\n description: string | null;\n key: string;\n config: Record<string, unknown> | null;\n data: Record<string, unknown>;\n ttl: number;\n isActive: boolean;\n fieldMaskingEnabled: boolean;\n fieldMaskingRules: Record<string, unknown> | null;\n status: string;\n setupState: string;\n refresh: DataSink['refresh'];\n exports: DataSink['exports'];\n alerts: DataSink['alerts'];\n quota: DataSink['quota'];\n metadata: Record<string, unknown> | null;\n };\n}\n\nfunction createExportStructure(dataSink: DataSink, options: ExportDataSinkOptions = {}): DataSinkExportStructure {\n return {\n schemaVersion: EXPORT_SCHEMA_VERSION,\n exportedAt: new Date().toISOString(),\n exportedFrom: {\n dataSinkId: dataSink.id,\n workspaceId: dataSink.workspaceId,\n },\n dataSink: {\n id: dataSink.id,\n name: dataSink.name,\n description: dataSink.description,\n key: dataSink.key,\n config: dataSink.config,\n data: dataSink.data,\n ttl: dataSink.ttl,\n isActive: dataSink.isActive,\n fieldMaskingEnabled: dataSink.fieldMaskingEnabled,\n fieldMaskingRules: dataSink.fieldMaskingRules,\n status: dataSink.status,\n setupState: dataSink.setupState,\n refresh: dataSink.refresh,\n exports: dataSink.exports,\n alerts: dataSink.alerts,\n quota: dataSink.quota,\n metadata: dataSink.metadata,\n },\n };\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useExportDataSink(): UseExportDataSinkResult {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const apolloClient = useApolloClient();\n\n // Export DataSink to JSON string\n const exportDataSink = useCallback(\n async (dataSinkId: string, options: ExportDataSinkOptions = {}): Promise<string | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.query<{ getDataSink?: DataSink }>({\n query: GetDataSinkDocument,\n variables: { id: dataSinkId },\n fetchPolicy: 'network-only',\n });\n\n const dataSink = result.data?.getDataSink;\n\n if (!dataSink) {\n throw new Error('DataSink not found');\n }\n\n const exportData = createExportStructure(dataSink, options);\n const prettyPrint = options.prettyPrint ?? true;\n const jsonContent = prettyPrint ? JSON.stringify(exportData, null, 2) : JSON.stringify(exportData);\n\n return jsonContent;\n } catch (err) {\n const exportError = err instanceof Error ? err : new Error('Failed to export DataSink');\n setError(exportError);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n // Export and download DataSink as JSON file\n const downloadDataSink = useCallback(\n async (dataSinkId: string, dataSinkName?: string, options: ExportDataSinkOptions = {}): Promise<boolean> => {\n try {\n const content = await exportDataSink(dataSinkId, options);\n\n if (!content) {\n return false;\n }\n\n // Extract name from content if not provided\n let filename = dataSinkName;\n try {\n const parsed = JSON.parse(content);\n filename = filename || parsed.dataSink?.name || parsed.dataSink?.key || 'datasink';\n } catch {\n filename = filename || 'datasink';\n }\n\n const sanitizedName = sanitizeFilename(filename || 'datasink');\n const timestamp = new Date().toISOString().split('T')[0];\n const fullFilename = `${sanitizedName}-datasink-export-${timestamp}.json`;\n\n downloadJsonFile(content, fullFilename);\n\n return true;\n } catch (err) {\n const downloadError = err instanceof Error ? err : new Error('Failed to download DataSink');\n setError(downloadError);\n return false;\n }\n },\n [exportDataSink]\n );\n\n // Clear error\n const clearError = useCallback(() => {\n setError(null);\n }, []);\n\n return {\n loading,\n error,\n exportDataSink,\n downloadDataSink,\n clearError,\n };\n}\n\nexport default useExportDataSink;\n"],"mappings":";;;;AAwCA,IAAM,IAAwB;AAM9B,SAAS,EAAiB,GAAiB,GAAwB;CACjE,IAAM,IAAO,IAAI,KAAK,CAAC,EAAQ,EAAE,EAAE,MAAM,oBAAoB,CAAC,EACxD,IAAM,IAAI,gBAAgB,EAAK,EAC/B,IAAO,SAAS,cAAc,IAAI;AAMxC,CALA,EAAK,OAAO,GACZ,EAAK,WAAW,GAChB,SAAS,KAAK,YAAY,EAAK,EAC/B,EAAK,OAAO,EACZ,SAAS,KAAK,YAAY,EAAK,EAC/B,IAAI,gBAAgB,EAAI;;AAG1B,SAAS,EAAiB,GAAsB;AAC9C,QAAO,EACJ,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,CACvB,UAAU,GAAG,GAAG;;AAmCrB,SAAS,EAAsB,GAAoB,IAAiC,EAAE,EAA2B;AAC/G,QAAO;EACL,eAAe;EACf,6BAAY,IAAI,MAAM,EAAC,aAAa;EACpC,cAAc;GACZ,YAAY,EAAS;GACrB,aAAa,EAAS;GACvB;EACD,UAAU;GACR,IAAI,EAAS;GACb,MAAM,EAAS;GACf,aAAa,EAAS;GACtB,KAAK,EAAS;GACd,QAAQ,EAAS;GACjB,MAAM,EAAS;GACf,KAAK,EAAS;GACd,UAAU,EAAS;GACnB,qBAAqB,EAAS;GAC9B,mBAAmB,EAAS;GAC5B,QAAQ,EAAS;GACjB,YAAY,EAAS;GACrB,SAAS,EAAS;GAClB,SAAS,EAAS;GAClB,QAAQ,EAAS;GACjB,OAAO,EAAS;GAChB,UAAU,EAAS;GACpB;EACF;;AAOH,SAAgB,IAA6C;CAC3D,IAAM,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAuB,KAAK,EAEhD,IAAe,GAAiB,EAGhC,IAAiB,EACrB,OAAO,GAAoB,IAAiC,EAAE,KAA6B;AACzF,MAAI;AAEF,GADA,EAAW,GAAK,EAChB,EAAS,KAAK;GAQd,IAAM,KANS,MAAM,EAAa,MAAkC;IAClE,OAAO;IACP,WAAW,EAAE,IAAI,GAAY;IAC7B,aAAa;IACd,CAAC,EAEsB,MAAM;AAE9B,OAAI,CAAC,EACH,OAAU,MAAM,qBAAqB;GAGvC,IAAM,IAAa,EAAsB,GAAU,EAAQ;AAI3D,UAHoB,EAAQ,eAAe,KACT,KAAK,UAAU,GAAY,MAAM,EAAE,GAAG,KAAK,UAAU,EAAW;WAG3F,GAAK;AAGZ,UADA,EADoB,aAAe,QAAQ,IAAM,gBAAI,MAAM,4BAA4B,CAClE,EACd;YACC;AACR,KAAW,GAAM;;IAGrB,CAAC,EAAa,CACf;AA0CD,QAAO;EACL;EACA;EACA;EACA,kBA3CuB,EACvB,OAAO,GAAoB,GAAuB,IAAiC,EAAE,KAAuB;AAC1G,OAAI;IACF,IAAM,IAAU,MAAM,EAAe,GAAY,EAAQ;AAEzD,QAAI,CAAC,EACH,QAAO;IAIT,IAAI,IAAW;AACf,QAAI;KACF,IAAM,IAAS,KAAK,MAAM,EAAQ;AAClC,SAAW,KAAY,EAAO,UAAU,QAAQ,EAAO,UAAU,OAAO;YAClE;AACN,WAAuB;;AASzB,WAFA,EAAiB,GAFI,GAFC,EAAiB,KAAY,WAAW,CAExB,oCADpB,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,IAAI,CAAC,GACa,OAE5B,EAEhC;YACA,GAAK;AAGZ,WADA,EADsB,aAAe,QAAQ,IAAM,gBAAI,MAAM,8BAA8B,CACpE,EAChB;;KAGX,CAAC,EAAe,CACjB;EAYC,YATiB,QAAkB;AACnC,KAAS,KAAK;KACb,EAAE,CAAC;EAQL"}
@@ -12,57 +12,69 @@ function o(e) {
12
12
  });
13
13
  }
14
14
  function s(e) {
15
+ return typeof e == "object" && !!e && !Array.isArray(e);
16
+ }
17
+ function c(e) {
18
+ let t = e;
19
+ return s(t.dataSink) ? t.dataSink : typeof e.key == "string" ? e : null;
20
+ }
21
+ function l(e) {
22
+ if (e) return {
23
+ type: e.type,
24
+ ...e.schedule ? { schedule: u(e.schedule) } : {}
25
+ };
26
+ }
27
+ function u(e) {
15
28
  if (e == null) return e;
16
- if (Array.isArray(e)) return e.map((e) => s(e));
29
+ if (Array.isArray(e)) return e.map((e) => u(e));
17
30
  if (typeof e == "object") {
18
31
  let t = {};
19
- for (let [n, r] of Object.entries(e)) n !== "__typename" && (t[n] = s(r));
32
+ for (let [n, r] of Object.entries(e)) n !== "__typename" && (t[n] = u(r));
20
33
  return t;
21
34
  }
22
35
  return e;
23
36
  }
24
- function c(e) {
37
+ function d(e) {
25
38
  try {
26
- let t = JSON.parse(e);
27
- return t.exportedFrom?.dataSinkId || t.dataSink?.id || null;
39
+ let t = JSON.parse(e), n = t;
40
+ return n.exportedFrom?.dataSinkId || n.dataSink?.id || t.id || null;
28
41
  } catch {
29
42
  return null;
30
43
  }
31
44
  }
32
- function l(e) {
45
+ function f(e) {
33
46
  let t = [];
34
47
  try {
35
- let n = JSON.parse(e);
36
- if (n.schemaVersion || t.push({
48
+ let n = JSON.parse(e), r = n, i = c(n);
49
+ if (!r.schemaVersion && !i && t.push({
37
50
  code: "MISSING_SCHEMA_VERSION",
38
51
  message: "Missing schema version"
39
- }), !n.dataSink) return t.push({
52
+ }), !i) return t.push({
40
53
  code: "MISSING_DATASINK",
41
- message: "Missing dataSink object in export file"
54
+ message: "This file is not a BigConsole DataSink export. Expected a top-level dataSink object."
42
55
  }), {
43
56
  isValid: !1,
44
57
  dataSinkInfo: null,
45
58
  errors: t
46
59
  };
47
- let r = n.dataSink;
48
- r.key || t.push({
60
+ i.key || t.push({
49
61
  code: "MISSING_KEY",
50
62
  message: "DataSink key is required",
51
63
  path: "dataSink.key"
52
64
  });
53
- let i = {
54
- name: r.name || null,
55
- description: r.description || null,
56
- key: r.key || "",
57
- status: r.status || "ready",
58
- setupState: r.setupState || "not_started",
59
- exportedAt: n.exportedAt,
60
- exportedFrom: n.exportedFrom?.dataSinkId,
61
- schemaVersion: n.schemaVersion || "unknown"
65
+ let a = {
66
+ name: i.name || null,
67
+ description: i.description || null,
68
+ key: i.key || "",
69
+ status: i.status || "ready",
70
+ setupState: i.setupState || "not_started",
71
+ exportedAt: r.exportedAt,
72
+ exportedFrom: r.exportedFrom?.dataSinkId,
73
+ schemaVersion: r.schemaVersion || "legacy"
62
74
  };
63
75
  return {
64
76
  isValid: t.length === 0,
65
- dataSinkInfo: i,
77
+ dataSinkInfo: a,
66
78
  errors: t
67
79
  };
68
80
  } catch {
@@ -76,123 +88,127 @@ function l(e) {
76
88
  };
77
89
  }
78
90
  }
79
- function u() {
80
- let [u, d] = i(!1), [f, p] = i(null), [m, h] = i(null), [g, _] = i(null), v = a(), y = r((e) => {
81
- let t = l(e);
82
- return h(t), t;
83
- }, []), b = r((e) => c(e), []), x = r(async (r, i = {}) => {
91
+ function p() {
92
+ let [s, p] = i(!1), [m, h] = i(null), [g, _] = i(null), [v, y] = i(null), b = a(), x = r((e) => {
93
+ let t = f(e);
94
+ return _(t), t;
95
+ }, []), S = r((e) => d(e), []), C = r(async (r, i = {}) => {
84
96
  try {
85
- d(!0), p(null);
86
- let a = l(r);
97
+ p(!0), h(null);
98
+ let a = f(r);
87
99
  if (!a.isValid || !a.dataSinkInfo) throw Error(a.errors[0]?.message || "Invalid import content");
88
- let o = JSON.parse(r).dataSink, u = c(r), f = !1, m;
89
- if (u) try {
90
- let e = (await v.query({
100
+ let o = c(JSON.parse(r));
101
+ if (!o) throw Error("Missing DataSink payload in import content");
102
+ let s = Object.prototype.hasOwnProperty.call(o, "data"), m = d(r), g = !1, _;
103
+ if (m) try {
104
+ let e = (await b.query({
91
105
  query: t,
92
- variables: { id: u },
106
+ variables: { id: m },
93
107
  fetchPolicy: "network-only"
94
108
  })).data?.getDataSink;
95
109
  if (e) {
96
- if ((e.description || "") !== (o.description || "") || e.ttl !== o.ttl || e.fieldMaskingEnabled !== (o.fieldMaskingEnabled ?? !1) || JSON.stringify(e.fieldMaskingRules) !== JSON.stringify(o.fieldMaskingRules) || JSON.stringify(e.refresh) !== JSON.stringify(o.refresh) || JSON.stringify(e.exports) !== JSON.stringify(o.exports) || JSON.stringify(e.alerts) !== JSON.stringify(o.alerts) || JSON.stringify(e.quota) !== JSON.stringify(o.quota) || JSON.stringify(e.metadata) !== JSON.stringify(o.metadata)) {
110
+ if (s && JSON.stringify(e.data) !== JSON.stringify(o.data) || (e.description || "") !== (o.description || "") || e.ttl !== o.ttl || e.fieldMaskingEnabled !== (o.fieldMaskingEnabled ?? !1) || JSON.stringify(e.fieldMaskingRules) !== JSON.stringify(o.fieldMaskingRules) || JSON.stringify(e.refresh) !== JSON.stringify(o.refresh) || JSON.stringify(e.exports) !== JSON.stringify(o.exports) || JSON.stringify(e.alerts) !== JSON.stringify(o.alerts) || JSON.stringify(e.quota) !== JSON.stringify(o.quota) || JSON.stringify(e.metadata) !== JSON.stringify(o.metadata)) {
97
111
  let t = {
98
- id: u,
112
+ id: m,
99
113
  version: e.version,
100
114
  name: e.name,
101
115
  description: o.description || void 0,
102
- ttl: o.ttl || void 0,
116
+ ...s && { data: u(o.data) },
117
+ ttl: o.ttl ?? void 0,
103
118
  fieldMaskingEnabled: o.fieldMaskingEnabled ?? !1,
104
- fieldMaskingRules: s(o.fieldMaskingRules) || void 0,
105
- refresh: s(o.refresh) || void 0,
106
- exports: s(o.exports) || void 0,
107
- alerts: s(o.alerts) || void 0,
108
- quota: s(o.quota) || void 0,
109
- metadata: s(o.metadata) || void 0
110
- }, r = (await v.mutate({
119
+ fieldMaskingRules: u(o.fieldMaskingRules) || void 0,
120
+ refresh: l(o.refresh),
121
+ exports: u(o.exports) || void 0,
122
+ alerts: u(o.alerts) || void 0,
123
+ quota: u(o.quota) || void 0,
124
+ metadata: u(o.metadata) || void 0
125
+ }, r = (await b.mutate({
111
126
  mutation: n,
112
127
  variables: { input: t }
113
128
  })).data?.updateDataSink;
114
- r && (f = !0, m = {
129
+ r && (g = !0, _ = {
115
130
  id: r.id,
116
131
  name: r.name,
117
132
  key: r.key
118
133
  });
119
134
  } else {
120
- m = {
135
+ _ = {
121
136
  id: e.id,
122
137
  name: e.name,
123
138
  key: e.key
124
139
  };
125
140
  let t = {
126
141
  success: !0,
127
- dataSink: m,
142
+ dataSink: _,
128
143
  originalUpdated: !1,
129
- originalDataSink: m,
144
+ originalDataSink: _,
130
145
  alreadyExists: !0
131
146
  };
132
- return _(t), t;
147
+ return y(t), t;
133
148
  }
134
149
  let t = {
135
150
  success: !0,
136
- dataSink: m,
137
- originalUpdated: f,
138
- originalDataSink: m,
151
+ dataSink: _,
152
+ originalUpdated: g,
153
+ originalDataSink: _,
139
154
  alreadyExists: !1
140
155
  };
141
- return _(t), t;
156
+ return y(t), t;
142
157
  }
143
158
  } catch (e) {
144
159
  console.warn("Could not fetch original DataSink:", e);
145
160
  }
146
- let h = {
161
+ let v = {
147
162
  name: i.name || o.name || "Imported DataSink",
148
- key: i.key || o.key,
163
+ key: i.key || o.key || "",
149
164
  description: o.description || void 0,
150
- config: s(o.config) || void 0,
151
- ttl: o.ttl || void 0,
165
+ config: u(o.config) || void 0,
166
+ ...s && { data: u(o.data) },
167
+ ttl: o.ttl ?? void 0,
152
168
  fieldMaskingEnabled: o.fieldMaskingEnabled ?? !1,
153
- fieldMaskingRules: s(o.fieldMaskingRules) || void 0,
154
- refresh: s(o.refresh) || void 0,
155
- exports: s(o.exports) || void 0,
156
- alerts: s(o.alerts) || void 0,
157
- quota: s(o.quota) || void 0,
158
- metadata: s(o.metadata) || void 0
159
- }, g = (await v.mutate({
169
+ fieldMaskingRules: u(o.fieldMaskingRules) || void 0,
170
+ refresh: l(o.refresh),
171
+ exports: u(o.exports) || void 0,
172
+ alerts: u(o.alerts) || void 0,
173
+ quota: u(o.quota) || void 0,
174
+ metadata: u(o.metadata) || void 0
175
+ }, x = (await b.mutate({
160
176
  mutation: e,
161
- variables: { input: h }
177
+ variables: { input: v }
162
178
  })).data?.createDataSink;
163
- if (g) {
179
+ if (x) {
164
180
  let e = {
165
181
  success: !0,
166
182
  dataSink: {
167
- id: g.id,
168
- name: g.name,
169
- key: g.key
183
+ id: x.id,
184
+ name: x.name,
185
+ key: x.key
170
186
  },
171
187
  originalUpdated: !1,
172
188
  originalDataSink: void 0
173
189
  };
174
- return _(e), e;
190
+ return y(e), e;
175
191
  }
176
192
  throw Error("Import returned empty result");
177
193
  } catch (e) {
178
194
  let t = e instanceof Error ? e : /* @__PURE__ */ Error("Failed to import DataSink");
179
- p(t);
195
+ h(t);
180
196
  let n = {
181
197
  success: !1,
182
198
  errorMessage: t.message
183
199
  };
184
- return _(n), n;
200
+ return y(n), n;
185
201
  } finally {
186
- d(!1);
202
+ p(!1);
187
203
  }
188
- }, [v]);
204
+ }, [b]);
189
205
  return {
190
- loading: u,
191
- error: f,
192
- validationResult: m,
193
- importResult: g,
194
- validateImport: y,
195
- importDataSink: x,
206
+ loading: s,
207
+ error: m,
208
+ validationResult: g,
209
+ importResult: v,
210
+ validateImport: x,
211
+ importDataSink: C,
196
212
  importFromFile: r(async (e, t = {}) => {
197
213
  try {
198
214
  if (!e.name.endsWith(".json")) throw Error("Invalid file type. Please select a JSON file.");
@@ -202,18 +218,18 @@ function u() {
202
218
  } catch {
203
219
  throw Error("Invalid JSON format. Please select a valid export file.");
204
220
  }
205
- return await x(n, t);
221
+ return await C(n, t);
206
222
  } catch (e) {
207
- return p(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to import from file")), null;
223
+ return h(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to import from file")), null;
208
224
  }
209
- }, [x]),
210
- extractDataSinkId: b,
225
+ }, [C]),
226
+ extractDataSinkId: S,
211
227
  clearState: r(() => {
212
- p(null), h(null), _(null);
228
+ h(null), _(null), y(null);
213
229
  }, [])
214
230
  };
215
231
  }
216
232
  //#endregion
217
- export { u as default };
233
+ export { p as default };
218
234
 
219
235
  //# sourceMappingURL=useImportDataSink.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useImportDataSink.js","names":[],"sources":["../../../src/bigconsole/hooks/useImportDataSink.ts"],"sourcesContent":["/**\n * useImportDataSink Hook\n *\n * Provides DataSink import functionality with validation and conflict resolution.\n * Supports both creating new DataSinks and updating existing ones on re-import.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { CreateDataSinkDocument, UpdateDataSinkDocument, GetDataSinkDocument } from '../../generated/wspace-operations';\nimport type { DataSink, CreateDataSinkInput } from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ImportDataSinkInfo {\n name: string | null;\n description: string | null;\n key: string;\n status: string;\n setupState: string;\n exportedAt?: string;\n exportedFrom?: string;\n schemaVersion: string;\n}\n\nexport interface ImportDataSinkValidationError {\n code: string;\n message: string;\n path?: string;\n}\n\nexport interface ImportDataSinkValidationResult {\n isValid: boolean;\n dataSinkInfo: ImportDataSinkInfo | null;\n errors: ImportDataSinkValidationError[];\n}\n\nexport interface ImportDataSinkResult {\n success: boolean;\n dataSink?: {\n id: string;\n name: string | null;\n key: string;\n };\n errorMessage?: string;\n /** Whether the original DataSink was also updated */\n originalUpdated?: boolean;\n /** The original DataSink that was updated (if any) */\n originalDataSink?: {\n id: string;\n name: string | null;\n key: string;\n };\n /** Whether the entity already exists with no changes (show \"already exists\" popup) */\n alreadyExists?: boolean;\n}\n\nexport interface ImportDataSinkOptions {\n /** Override DataSink name (optional) */\n name?: string;\n /** Override DataSink key (optional - required to avoid conflicts) */\n key?: string;\n}\n\nexport interface UseImportDataSinkResult {\n /** Loading state */\n loading: boolean;\n /** Error state */\n error: Error | null;\n /** Last validation result */\n validationResult: ImportDataSinkValidationResult | null;\n /** Last import result */\n importResult: ImportDataSinkResult | null;\n /** Validate import content without importing */\n validateImport: (content: string) => ImportDataSinkValidationResult;\n /** Import DataSink from JSON content */\n importDataSink: (content: string, options?: ImportDataSinkOptions) => Promise<ImportDataSinkResult | null>;\n /** Import DataSink from file */\n importFromFile: (file: File, options?: ImportDataSinkOptions) => Promise<ImportDataSinkResult | null>;\n /** Extract DataSink ID from import content */\n extractDataSinkId: (content: string) => string | null;\n /** Clear state */\n clearState: () => void;\n}\n\n// ============================================================================\n// Helper: Read file as text\n// ============================================================================\n\nfunction readFileAsText(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = (e) => {\n const result = e.target?.result;\n if (typeof result === 'string') {\n resolve(result);\n } else {\n reject(new Error('Failed to read file as text'));\n }\n };\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsText(file);\n });\n}\n\n/**\n * Recursively remove __typename fields from an object\n * This is necessary because exported data contains __typename fields\n * which are not accepted by GraphQL input types\n */\nfunction stripTypename<T>(obj: T): T {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => stripTypename(item)) as T;\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (key !== '__typename') {\n cleaned[key] = stripTypename(value);\n }\n }\n return cleaned as T;\n }\n\n return obj;\n}\n\n/**\n * Extract DataSink ID from imported JSON content\n * The ID is stored during export for re-import update support\n */\nfunction extractDataSinkIdFromContent(content: string): string | null {\n try {\n const parsed = JSON.parse(content);\n // Check both locations where ID might be stored\n return parsed.exportedFrom?.dataSinkId || parsed.dataSink?.id || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Validate DataSink export content\n */\nfunction validateDataSinkContent(content: string): ImportDataSinkValidationResult {\n const errors: ImportDataSinkValidationError[] = [];\n\n try {\n const parsed = JSON.parse(content);\n\n // Check schema version\n if (!parsed.schemaVersion) {\n errors.push({\n code: 'MISSING_SCHEMA_VERSION',\n message: 'Missing schema version',\n });\n }\n\n // Check dataSink object exists\n if (!parsed.dataSink) {\n errors.push({\n code: 'MISSING_DATASINK',\n message: 'Missing dataSink object in export file',\n });\n return { isValid: false, dataSinkInfo: null, errors };\n }\n\n const dataSink = parsed.dataSink;\n\n // Validate required fields\n if (!dataSink.key) {\n errors.push({\n code: 'MISSING_KEY',\n message: 'DataSink key is required',\n path: 'dataSink.key',\n });\n }\n\n const dataSinkInfo: ImportDataSinkInfo = {\n name: dataSink.name || null,\n description: dataSink.description || null,\n key: dataSink.key || '',\n status: dataSink.status || 'ready',\n setupState: dataSink.setupState || 'not_started',\n exportedAt: parsed.exportedAt,\n exportedFrom: parsed.exportedFrom?.dataSinkId,\n schemaVersion: parsed.schemaVersion || 'unknown',\n };\n\n return {\n isValid: errors.length === 0,\n dataSinkInfo,\n errors,\n };\n } catch (err) {\n return {\n isValid: false,\n dataSinkInfo: null,\n errors: [\n {\n code: 'INVALID_JSON',\n message: 'Invalid JSON format',\n },\n ],\n };\n }\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useImportDataSink(): UseImportDataSinkResult {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [validationResult, setValidationResult] = useState<ImportDataSinkValidationResult | null>(null);\n const [importResult, setImportResult] = useState<ImportDataSinkResult | null>(null);\n\n const apolloClient = useApolloClient();\n\n // Validate import content\n const validateImport = useCallback((content: string): ImportDataSinkValidationResult => {\n const result = validateDataSinkContent(content);\n setValidationResult(result);\n return result;\n }, []);\n\n // Extract DataSink ID from content\n const extractDataSinkId = useCallback((content: string): string | null => {\n return extractDataSinkIdFromContent(content);\n }, []);\n\n // Import DataSink from JSON content\n // Logic (DECLARATIVE - idempotent re-import):\n // 1. If original DataSink ID exists in JSON, check if original still exists\n // 2. If original exists → UPDATE it with the JSON data (no new copy created)\n // 3. If original doesn't exist → CREATE a new DataSink from the JSON\n // This ensures re-importing the same JSON is idempotent and updates the existing entity\n const importDataSink = useCallback(\n async (content: string, options: ImportDataSinkOptions = {}): Promise<ImportDataSinkResult | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Validate content first\n const validation = validateDataSinkContent(content);\n if (!validation.isValid || !validation.dataSinkInfo) {\n throw new Error(validation.errors[0]?.message || 'Invalid import content');\n }\n\n // Parse the content to get DataSink data\n const parsed = JSON.parse(content);\n const dataSinkData = parsed.dataSink;\n\n // Extract original DataSink ID from the JSON (if it was exported from an existing DataSink)\n const originalDataSinkId = extractDataSinkIdFromContent(content);\n let originalUpdated = false;\n let originalDataSinkInfo: { id: string; name: string | null; key: string } | undefined;\n\n // Step 1: Check if original DataSink exists and update it\n if (originalDataSinkId) {\n try {\n const getDataSinkResult = await apolloClient.query<{ getDataSink?: DataSink }>({\n query: GetDataSinkDocument,\n variables: { id: originalDataSinkId },\n fetchPolicy: 'network-only',\n });\n\n const existingDataSink = getDataSinkResult.data?.getDataSink;\n\n if (existingDataSink) {\n // Compare to detect changes (excluding name - we don't update the original's name)\n const hasChanges =\n (existingDataSink.description || '') !== (dataSinkData.description || '') ||\n existingDataSink.ttl !== dataSinkData.ttl ||\n existingDataSink.fieldMaskingEnabled !== (dataSinkData.fieldMaskingEnabled ?? false) ||\n JSON.stringify(existingDataSink.fieldMaskingRules) !== JSON.stringify(dataSinkData.fieldMaskingRules) ||\n JSON.stringify(existingDataSink.refresh) !== JSON.stringify(dataSinkData.refresh) ||\n JSON.stringify(existingDataSink.exports) !== JSON.stringify(dataSinkData.exports) ||\n JSON.stringify(existingDataSink.alerts) !== JSON.stringify(dataSinkData.alerts) ||\n JSON.stringify(existingDataSink.quota) !== JSON.stringify(dataSinkData.quota) ||\n JSON.stringify(existingDataSink.metadata) !== JSON.stringify(dataSinkData.metadata);\n\n // Update original if there are changes\n // Note: We intentionally do NOT update the name - the original keeps its name\n if (hasChanges) {\n const updateInput = {\n id: originalDataSinkId,\n version: existingDataSink.version,\n name: existingDataSink.name, // Keep original name\n description: dataSinkData.description || undefined,\n ttl: dataSinkData.ttl || undefined,\n fieldMaskingEnabled: dataSinkData.fieldMaskingEnabled ?? false,\n fieldMaskingRules: stripTypename(dataSinkData.fieldMaskingRules) || undefined,\n refresh: stripTypename(dataSinkData.refresh) || undefined,\n exports: stripTypename(dataSinkData.exports) || undefined,\n alerts: stripTypename(dataSinkData.alerts) || undefined,\n quota: stripTypename(dataSinkData.quota) || undefined,\n metadata: stripTypename(dataSinkData.metadata) || undefined,\n };\n\n const updateResult = await apolloClient.mutate<{ updateDataSink?: DataSink }>({\n mutation: UpdateDataSinkDocument,\n variables: { input: updateInput },\n });\n\n const updatedDataSink = updateResult.data?.updateDataSink;\n if (updatedDataSink) {\n originalUpdated = true;\n originalDataSinkInfo = {\n id: updatedDataSink.id,\n name: updatedDataSink.name,\n key: updatedDataSink.key,\n };\n }\n } else {\n // No changes - entity already exists with same data\n // Return with alreadyExists flag so UI can show \"already exists\" popup\n originalDataSinkInfo = {\n id: existingDataSink.id,\n name: existingDataSink.name,\n key: existingDataSink.key,\n };\n const alreadyExistsResult: ImportDataSinkResult = {\n success: true,\n dataSink: originalDataSinkInfo,\n originalUpdated: false,\n originalDataSink: originalDataSinkInfo,\n alreadyExists: true,\n };\n setImportResult(alreadyExistsResult);\n return alreadyExistsResult;\n }\n\n // If original was updated, return success with the updated DataSink info\n const finalResult: ImportDataSinkResult = {\n success: true,\n dataSink: originalDataSinkInfo,\n originalUpdated,\n originalDataSink: originalDataSinkInfo,\n alreadyExists: false,\n };\n setImportResult(finalResult);\n return finalResult;\n }\n // If original doesn't exist, continue to create a new DataSink\n } catch (fetchErr) {\n // Original DataSink fetch failed (might not exist), continue with creating new\n console.warn('Could not fetch original DataSink:', fetchErr);\n }\n }\n\n // Step 2: Create a NEW DataSink only if original doesn't exist\n // (This happens when importing for the first time, or if original was deleted)\n // Use stripTypename to remove __typename fields from nested objects\n const createInput: CreateDataSinkInput = {\n name: options.name || dataSinkData.name || 'Imported DataSink',\n key: options.key || dataSinkData.key,\n description: dataSinkData.description || undefined,\n config: stripTypename(dataSinkData.config) || undefined,\n ttl: dataSinkData.ttl || undefined,\n fieldMaskingEnabled: dataSinkData.fieldMaskingEnabled ?? false,\n fieldMaskingRules: stripTypename(dataSinkData.fieldMaskingRules) || undefined,\n refresh: stripTypename(dataSinkData.refresh) || undefined,\n exports: stripTypename(dataSinkData.exports) || undefined,\n alerts: stripTypename(dataSinkData.alerts) || undefined,\n quota: stripTypename(dataSinkData.quota) || undefined,\n metadata: stripTypename(dataSinkData.metadata) || undefined,\n };\n\n const result = await apolloClient.mutate<{ createDataSink?: DataSink }>({\n mutation: CreateDataSinkDocument,\n variables: { input: createInput },\n });\n\n const createdDataSink = result.data?.createDataSink;\n\n if (createdDataSink) {\n const finalResult: ImportDataSinkResult = {\n success: true,\n dataSink: {\n id: createdDataSink.id,\n name: createdDataSink.name,\n key: createdDataSink.key,\n },\n originalUpdated: false,\n originalDataSink: undefined,\n };\n setImportResult(finalResult);\n return finalResult;\n }\n\n throw new Error('Import returned empty result');\n } catch (err) {\n const importError = err instanceof Error ? err : new Error('Failed to import DataSink');\n setError(importError);\n const failResult: ImportDataSinkResult = {\n success: false,\n errorMessage: importError.message,\n };\n setImportResult(failResult);\n return failResult;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n // Import DataSink from file\n const importFromFile = useCallback(\n async (file: File, options: ImportDataSinkOptions = {}): Promise<ImportDataSinkResult | null> => {\n try {\n // Validate file type\n if (!file.name.endsWith('.json')) {\n throw new Error('Invalid file type. Please select a JSON file.');\n }\n\n // Read file content\n const content = await readFileAsText(file);\n\n // Validate JSON format\n try {\n JSON.parse(content);\n } catch {\n throw new Error('Invalid JSON format. Please select a valid export file.');\n }\n\n // Perform import\n return await importDataSink(content, options);\n } catch (err) {\n const fileError = err instanceof Error ? err : new Error('Failed to import from file');\n setError(fileError);\n return null;\n }\n },\n [importDataSink]\n );\n\n // Clear state\n const clearState = useCallback(() => {\n setError(null);\n setValidationResult(null);\n setImportResult(null);\n }, []);\n\n return {\n loading,\n error,\n validationResult,\n importResult,\n validateImport,\n importDataSink,\n importFromFile,\n extractDataSinkId,\n clearState,\n };\n}\n\nexport default useImportDataSink;\n"],"mappings":";;;;AA2FA,SAAS,EAAe,GAA6B;AACnD,QAAO,IAAI,SAAS,GAAS,MAAW;EACtC,IAAM,IAAS,IAAI,YAAY;AAU/B,EATA,EAAO,UAAU,MAAM;GACrB,IAAM,IAAS,EAAE,QAAQ;AACzB,GAAI,OAAO,KAAW,WACpB,EAAQ,EAAO,GAEf,EAAO,gBAAI,MAAM,8BAA8B,CAAC;KAGpD,EAAO,gBAAgB,EAAO,gBAAI,MAAM,sBAAsB,CAAC,EAC/D,EAAO,WAAW,EAAK;GACvB;;AAQJ,SAAS,EAAiB,GAAW;AACnC,KAAI,KAAQ,KACV,QAAO;AAGT,KAAI,MAAM,QAAQ,EAAI,CACpB,QAAO,EAAI,KAAK,MAAS,EAAc,EAAK,CAAC;AAG/C,KAAI,OAAO,KAAQ,UAAU;EAC3B,IAAM,IAAmC,EAAE;AAC3C,OAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,EAA+B,CACvE,CAAI,MAAQ,iBACV,EAAQ,KAAO,EAAc,EAAM;AAGvC,SAAO;;AAGT,QAAO;;AAOT,SAAS,EAA6B,GAAgC;AACpE,KAAI;EACF,IAAM,IAAS,KAAK,MAAM,EAAQ;AAElC,SAAO,EAAO,cAAc,cAAc,EAAO,UAAU,MAAM;SAC3D;AACN,SAAO;;;AAOX,SAAS,EAAwB,GAAiD;CAChF,IAAM,IAA0C,EAAE;AAElD,KAAI;EACF,IAAM,IAAS,KAAK,MAAM,EAAQ;AAWlC,MARK,EAAO,iBACV,EAAO,KAAK;GACV,MAAM;GACN,SAAS;GACV,CAAC,EAIA,CAAC,EAAO,SAKV,QAJA,EAAO,KAAK;GACV,MAAM;GACN,SAAS;GACV,CAAC,EACK;GAAE,SAAS;GAAO,cAAc;GAAM;GAAQ;EAGvD,IAAM,IAAW,EAAO;AAGxB,EAAK,EAAS,OACZ,EAAO,KAAK;GACV,MAAM;GACN,SAAS;GACT,MAAM;GACP,CAAC;EAGJ,IAAM,IAAmC;GACvC,MAAM,EAAS,QAAQ;GACvB,aAAa,EAAS,eAAe;GACrC,KAAK,EAAS,OAAO;GACrB,QAAQ,EAAS,UAAU;GAC3B,YAAY,EAAS,cAAc;GACnC,YAAY,EAAO;GACnB,cAAc,EAAO,cAAc;GACnC,eAAe,EAAO,iBAAiB;GACxC;AAED,SAAO;GACL,SAAS,EAAO,WAAW;GAC3B;GACA;GACD;SACW;AACZ,SAAO;GACL,SAAS;GACT,cAAc;GACd,QAAQ,CACN;IACE,MAAM;IACN,SAAS;IACV,CACF;GACF;;;AAQL,SAAgB,IAA6C;CAC3D,IAAM,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAuB,KAAK,EAChD,CAAC,GAAkB,KAAuB,EAAgD,KAAK,EAC/F,CAAC,GAAc,KAAmB,EAAsC,KAAK,EAE7E,IAAe,GAAiB,EAGhC,IAAiB,GAAa,MAAoD;EACtF,IAAM,IAAS,EAAwB,EAAQ;AAE/C,SADA,EAAoB,EAAO,EACpB;IACN,EAAE,CAAC,EAGA,IAAoB,GAAa,MAC9B,EAA6B,EAAQ,EAC3C,EAAE,CAAC,EAQA,IAAiB,EACrB,OAAO,GAAiB,IAAiC,EAAE,KAA2C;AACpG,MAAI;AAEF,GADA,EAAW,GAAK,EAChB,EAAS,KAAK;GAGd,IAAM,IAAa,EAAwB,EAAQ;AACnD,OAAI,CAAC,EAAW,WAAW,CAAC,EAAW,aACrC,OAAU,MAAM,EAAW,OAAO,IAAI,WAAW,yBAAyB;GAK5E,IAAM,IADS,KAAK,MAAM,EAAQ,CACN,UAGtB,IAAqB,EAA6B,EAAQ,EAC5D,IAAkB,IAClB;AAGJ,OAAI,EACF,KAAI;IAOF,IAAM,KANoB,MAAM,EAAa,MAAkC;KAC7E,OAAO;KACP,WAAW,EAAE,IAAI,GAAoB;KACrC,aAAa;KACd,CAAC,EAEyC,MAAM;AAEjD,QAAI,GAAkB;AAepB,UAZG,EAAiB,eAAe,SAAS,EAAa,eAAe,OACtE,EAAiB,QAAQ,EAAa,OACtC,EAAiB,yBAAyB,EAAa,uBAAuB,OAC9E,KAAK,UAAU,EAAiB,kBAAkB,KAAK,KAAK,UAAU,EAAa,kBAAkB,IACrG,KAAK,UAAU,EAAiB,QAAQ,KAAK,KAAK,UAAU,EAAa,QAAQ,IACjF,KAAK,UAAU,EAAiB,QAAQ,KAAK,KAAK,UAAU,EAAa,QAAQ,IACjF,KAAK,UAAU,EAAiB,OAAO,KAAK,KAAK,UAAU,EAAa,OAAO,IAC/E,KAAK,UAAU,EAAiB,MAAM,KAAK,KAAK,UAAU,EAAa,MAAM,IAC7E,KAAK,UAAU,EAAiB,SAAS,KAAK,KAAK,UAAU,EAAa,SAAS,EAIrE;MACd,IAAM,IAAc;OAClB,IAAI;OACJ,SAAS,EAAiB;OAC1B,MAAM,EAAiB;OACvB,aAAa,EAAa,eAAe,KAAA;OACzC,KAAK,EAAa,OAAO,KAAA;OACzB,qBAAqB,EAAa,uBAAuB;OACzD,mBAAmB,EAAc,EAAa,kBAAkB,IAAI,KAAA;OACpE,SAAS,EAAc,EAAa,QAAQ,IAAI,KAAA;OAChD,SAAS,EAAc,EAAa,QAAQ,IAAI,KAAA;OAChD,QAAQ,EAAc,EAAa,OAAO,IAAI,KAAA;OAC9C,OAAO,EAAc,EAAa,MAAM,IAAI,KAAA;OAC5C,UAAU,EAAc,EAAa,SAAS,IAAI,KAAA;OACnD,EAOK,KALe,MAAM,EAAa,OAAsC;OAC5E,UAAU;OACV,WAAW,EAAE,OAAO,GAAa;OAClC,CAAC,EAEmC,MAAM;AAC3C,MAAI,MACF,IAAkB,IAClB,IAAuB;OACrB,IAAI,EAAgB;OACpB,MAAM,EAAgB;OACtB,KAAK,EAAgB;OACtB;YAEE;AAGL,UAAuB;OACrB,IAAI,EAAiB;OACrB,MAAM,EAAiB;OACvB,KAAK,EAAiB;OACvB;MACD,IAAM,IAA4C;OAChD,SAAS;OACT,UAAU;OACV,iBAAiB;OACjB,kBAAkB;OAClB,eAAe;OAChB;AAED,aADA,EAAgB,EAAoB,EAC7B;;KAIT,IAAM,IAAoC;MACxC,SAAS;MACT,UAAU;MACV;MACA,kBAAkB;MAClB,eAAe;MAChB;AAED,YADA,EAAgB,EAAY,EACrB;;YAGF,GAAU;AAEjB,YAAQ,KAAK,sCAAsC,EAAS;;GAOhE,IAAM,IAAmC;IACvC,MAAM,EAAQ,QAAQ,EAAa,QAAQ;IAC3C,KAAK,EAAQ,OAAO,EAAa;IACjC,aAAa,EAAa,eAAe,KAAA;IACzC,QAAQ,EAAc,EAAa,OAAO,IAAI,KAAA;IAC9C,KAAK,EAAa,OAAO,KAAA;IACzB,qBAAqB,EAAa,uBAAuB;IACzD,mBAAmB,EAAc,EAAa,kBAAkB,IAAI,KAAA;IACpE,SAAS,EAAc,EAAa,QAAQ,IAAI,KAAA;IAChD,SAAS,EAAc,EAAa,QAAQ,IAAI,KAAA;IAChD,QAAQ,EAAc,EAAa,OAAO,IAAI,KAAA;IAC9C,OAAO,EAAc,EAAa,MAAM,IAAI,KAAA;IAC5C,UAAU,EAAc,EAAa,SAAS,IAAI,KAAA;IACnD,EAOK,KALS,MAAM,EAAa,OAAsC;IACtE,UAAU;IACV,WAAW,EAAE,OAAO,GAAa;IAClC,CAAC,EAE6B,MAAM;AAErC,OAAI,GAAiB;IACnB,IAAM,IAAoC;KACxC,SAAS;KACT,UAAU;MACR,IAAI,EAAgB;MACpB,MAAM,EAAgB;MACtB,KAAK,EAAgB;MACtB;KACD,iBAAiB;KACjB,kBAAkB,KAAA;KACnB;AAED,WADA,EAAgB,EAAY,EACrB;;AAGT,SAAU,MAAM,+BAA+B;WACxC,GAAK;GACZ,IAAM,IAAc,aAAe,QAAQ,IAAM,gBAAI,MAAM,4BAA4B;AACvF,KAAS,EAAY;GACrB,IAAM,IAAmC;IACvC,SAAS;IACT,cAAc,EAAY;IAC3B;AAED,UADA,EAAgB,EAAW,EACpB;YACC;AACR,KAAW,GAAM;;IAGrB,CAAC,EAAa,CACf;AAuCD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA,gBA3CqB,EACrB,OAAO,GAAY,IAAiC,EAAE,KAA2C;AAC/F,OAAI;AAEF,QAAI,CAAC,EAAK,KAAK,SAAS,QAAQ,CAC9B,OAAU,MAAM,gDAAgD;IAIlE,IAAM,IAAU,MAAM,EAAe,EAAK;AAG1C,QAAI;AACF,UAAK,MAAM,EAAQ;YACb;AACN,WAAU,MAAM,0DAA0D;;AAI5E,WAAO,MAAM,EAAe,GAAS,EAAQ;YACtC,GAAK;AAGZ,WADA,EADkB,aAAe,QAAQ,IAAM,gBAAI,MAAM,6BAA6B,CACnE,EACZ;;KAGX,CAAC,EAAe,CACjB;EAiBC;EACA,YAfiB,QAAkB;AAGnC,GAFA,EAAS,KAAK,EACd,EAAoB,KAAK,EACzB,EAAgB,KAAK;KACpB,EAAE,CAAC;EAYL"}
1
+ {"version":3,"file":"useImportDataSink.js","names":[],"sources":["../../../src/bigconsole/hooks/useImportDataSink.ts"],"sourcesContent":["/**\n * useImportDataSink Hook\n *\n * Provides DataSink import functionality with validation and conflict resolution.\n * Supports both creating new DataSinks and updating existing ones on re-import.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { CreateDataSinkDocument, UpdateDataSinkDocument, GetDataSinkDocument } from '../../generated/wspace-operations';\nimport type { DataSink, CreateDataSinkInput } from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ImportDataSinkInfo {\n name: string | null;\n description: string | null;\n key: string;\n status: string;\n setupState: string;\n exportedAt?: string;\n exportedFrom?: string;\n schemaVersion: string;\n}\n\nexport interface ImportDataSinkValidationError {\n code: string;\n message: string;\n path?: string;\n}\n\nexport interface ImportDataSinkValidationResult {\n isValid: boolean;\n dataSinkInfo: ImportDataSinkInfo | null;\n errors: ImportDataSinkValidationError[];\n}\n\nexport interface ImportDataSinkResult {\n success: boolean;\n dataSink?: {\n id: string;\n name: string | null;\n key: string;\n };\n errorMessage?: string;\n /** Whether the original DataSink was also updated */\n originalUpdated?: boolean;\n /** The original DataSink that was updated (if any) */\n originalDataSink?: {\n id: string;\n name: string | null;\n key: string;\n };\n /** Whether the entity already exists with no changes (show \"already exists\" popup) */\n alreadyExists?: boolean;\n}\n\nexport interface ImportDataSinkOptions {\n /** Override DataSink name (optional) */\n name?: string;\n /** Override DataSink key (optional - required to avoid conflicts) */\n key?: string;\n}\n\nexport interface UseImportDataSinkResult {\n /** Loading state */\n loading: boolean;\n /** Error state */\n error: Error | null;\n /** Last validation result */\n validationResult: ImportDataSinkValidationResult | null;\n /** Last import result */\n importResult: ImportDataSinkResult | null;\n /** Validate import content without importing */\n validateImport: (content: string) => ImportDataSinkValidationResult;\n /** Import DataSink from JSON content */\n importDataSink: (content: string, options?: ImportDataSinkOptions) => Promise<ImportDataSinkResult | null>;\n /** Import DataSink from file */\n importFromFile: (file: File, options?: ImportDataSinkOptions) => Promise<ImportDataSinkResult | null>;\n /** Extract DataSink ID from import content */\n extractDataSinkId: (content: string) => string | null;\n /** Clear state */\n clearState: () => void;\n}\n\ninterface ImportedDataSinkPayload {\n id?: string;\n name?: string | null;\n description?: string | null;\n key?: string;\n config?: Record<string, unknown> | null;\n data?: Record<string, unknown>;\n ttl?: number;\n fieldMaskingEnabled?: boolean;\n fieldMaskingRules?: Record<string, unknown> | null;\n status?: string;\n setupState?: string;\n refresh?: DataSink['refresh'];\n exports?: DataSink['exports'];\n alerts?: DataSink['alerts'];\n quota?: DataSink['quota'];\n metadata?: Record<string, unknown> | null;\n}\n\ninterface ParsedDataSinkExport {\n schemaVersion?: string;\n exportedAt?: string;\n exportedFrom?: {\n dataSinkId?: string;\n };\n dataSink?: ImportedDataSinkPayload;\n}\n\n// ============================================================================\n// Helper: Read file as text\n// ============================================================================\n\nfunction readFileAsText(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = (e) => {\n const result = e.target?.result;\n if (typeof result === 'string') {\n resolve(result);\n } else {\n reject(new Error('Failed to read file as text'));\n }\n };\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsText(file);\n });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction resolveDataSinkPayload(\n parsed: ParsedDataSinkExport | ImportedDataSinkPayload\n): ImportedDataSinkPayload | null {\n const wrapped = parsed as ParsedDataSinkExport;\n if (isRecord(wrapped.dataSink)) {\n return wrapped.dataSink as ImportedDataSinkPayload;\n }\n\n // Backward-compatible import for raw DataSink JSON objects.\n if (typeof (parsed as ImportedDataSinkPayload).key === 'string') {\n return parsed as ImportedDataSinkPayload;\n }\n\n return null;\n}\n\nfunction normalizeRefreshInput(refresh: DataSink['refresh'] | undefined): CreateDataSinkInput['refresh'] {\n if (!refresh) {\n return undefined;\n }\n\n return {\n type: refresh.type,\n ...(refresh.schedule ? { schedule: stripTypename(refresh.schedule) } : {}),\n };\n}\n\n/**\n * Recursively remove __typename fields from an object\n * This is necessary because exported data contains __typename fields\n * which are not accepted by GraphQL input types\n */\nfunction stripTypename<T>(obj: T): T {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => stripTypename(item)) as T;\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (key !== '__typename') {\n cleaned[key] = stripTypename(value);\n }\n }\n return cleaned as T;\n }\n\n return obj;\n}\n\n/**\n * Extract DataSink ID from imported JSON content\n * The ID is stored during export for re-import update support\n */\nfunction extractDataSinkIdFromContent(content: string): string | null {\n try {\n const parsed = JSON.parse(content) as ParsedDataSinkExport | ImportedDataSinkPayload;\n const wrapped = parsed as ParsedDataSinkExport;\n // Check both locations where ID might be stored\n return wrapped.exportedFrom?.dataSinkId || wrapped.dataSink?.id || (parsed as ImportedDataSinkPayload).id || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Validate DataSink export content\n */\nfunction validateDataSinkContent(content: string): ImportDataSinkValidationResult {\n const errors: ImportDataSinkValidationError[] = [];\n\n try {\n const parsed = JSON.parse(content) as ParsedDataSinkExport | ImportedDataSinkPayload;\n const wrapped = parsed as ParsedDataSinkExport;\n const dataSink = resolveDataSinkPayload(parsed);\n\n // Check schema version\n if (!wrapped.schemaVersion && !dataSink) {\n errors.push({\n code: 'MISSING_SCHEMA_VERSION',\n message: 'Missing schema version',\n });\n }\n\n // Check dataSink object exists\n if (!dataSink) {\n errors.push({\n code: 'MISSING_DATASINK',\n message: 'This file is not a BigConsole DataSink export. Expected a top-level dataSink object.',\n });\n return { isValid: false, dataSinkInfo: null, errors };\n }\n\n // Validate required fields\n if (!dataSink.key) {\n errors.push({\n code: 'MISSING_KEY',\n message: 'DataSink key is required',\n path: 'dataSink.key',\n });\n }\n\n const dataSinkInfo: ImportDataSinkInfo = {\n name: dataSink.name || null,\n description: dataSink.description || null,\n key: dataSink.key || '',\n status: dataSink.status || 'ready',\n setupState: dataSink.setupState || 'not_started',\n exportedAt: wrapped.exportedAt,\n exportedFrom: wrapped.exportedFrom?.dataSinkId,\n schemaVersion: wrapped.schemaVersion || 'legacy',\n };\n\n return {\n isValid: errors.length === 0,\n dataSinkInfo,\n errors,\n };\n } catch {\n return {\n isValid: false,\n dataSinkInfo: null,\n errors: [\n {\n code: 'INVALID_JSON',\n message: 'Invalid JSON format',\n },\n ],\n };\n }\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useImportDataSink(): UseImportDataSinkResult {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [validationResult, setValidationResult] = useState<ImportDataSinkValidationResult | null>(null);\n const [importResult, setImportResult] = useState<ImportDataSinkResult | null>(null);\n\n const apolloClient = useApolloClient();\n\n // Validate import content\n const validateImport = useCallback((content: string): ImportDataSinkValidationResult => {\n const result = validateDataSinkContent(content);\n setValidationResult(result);\n return result;\n }, []);\n\n // Extract DataSink ID from content\n const extractDataSinkId = useCallback((content: string): string | null => {\n return extractDataSinkIdFromContent(content);\n }, []);\n\n // Import DataSink from JSON content\n // Logic (DECLARATIVE - idempotent re-import):\n // 1. If original DataSink ID exists in JSON, check if original still exists\n // 2. If original exists → UPDATE it with the JSON data (no new copy created)\n // 3. If original doesn't exist → CREATE a new DataSink from the JSON\n // This ensures re-importing the same JSON is idempotent and updates the existing entity\n const importDataSink = useCallback(\n async (content: string, options: ImportDataSinkOptions = {}): Promise<ImportDataSinkResult | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Validate content first\n const validation = validateDataSinkContent(content);\n if (!validation.isValid || !validation.dataSinkInfo) {\n throw new Error(validation.errors[0]?.message || 'Invalid import content');\n }\n\n // Parse the content to get DataSink data\n const parsed = JSON.parse(content) as ParsedDataSinkExport | ImportedDataSinkPayload;\n const dataSinkData = resolveDataSinkPayload(parsed);\n if (!dataSinkData) {\n throw new Error('Missing DataSink payload in import content');\n }\n const hasImportedData = Object.prototype.hasOwnProperty.call(dataSinkData, 'data');\n\n // Extract original DataSink ID from the JSON (if it was exported from an existing DataSink)\n const originalDataSinkId = extractDataSinkIdFromContent(content);\n let originalUpdated = false;\n let originalDataSinkInfo: { id: string; name: string | null; key: string } | undefined;\n\n // Step 1: Check if original DataSink exists and update it\n if (originalDataSinkId) {\n try {\n const getDataSinkResult = await apolloClient.query<{ getDataSink?: DataSink }>({\n query: GetDataSinkDocument,\n variables: { id: originalDataSinkId },\n fetchPolicy: 'network-only',\n });\n\n const existingDataSink = getDataSinkResult.data?.getDataSink;\n\n if (existingDataSink) {\n // Compare to detect changes (excluding name - we don't update the original's name)\n const hasChanges =\n (hasImportedData && JSON.stringify(existingDataSink.data) !== JSON.stringify(dataSinkData.data)) ||\n (existingDataSink.description || '') !== (dataSinkData.description || '') ||\n existingDataSink.ttl !== dataSinkData.ttl ||\n existingDataSink.fieldMaskingEnabled !== (dataSinkData.fieldMaskingEnabled ?? false) ||\n JSON.stringify(existingDataSink.fieldMaskingRules) !== JSON.stringify(dataSinkData.fieldMaskingRules) ||\n JSON.stringify(existingDataSink.refresh) !== JSON.stringify(dataSinkData.refresh) ||\n JSON.stringify(existingDataSink.exports) !== JSON.stringify(dataSinkData.exports) ||\n JSON.stringify(existingDataSink.alerts) !== JSON.stringify(dataSinkData.alerts) ||\n JSON.stringify(existingDataSink.quota) !== JSON.stringify(dataSinkData.quota) ||\n JSON.stringify(existingDataSink.metadata) !== JSON.stringify(dataSinkData.metadata);\n\n // Update original if there are changes\n // Note: We intentionally do NOT update the name - the original keeps its name\n if (hasChanges) {\n const updateInput = {\n id: originalDataSinkId,\n version: existingDataSink.version,\n name: existingDataSink.name, // Keep original name\n description: dataSinkData.description || undefined,\n ...(hasImportedData && { data: stripTypename(dataSinkData.data) }),\n ttl: dataSinkData.ttl ?? undefined,\n fieldMaskingEnabled: dataSinkData.fieldMaskingEnabled ?? false,\n fieldMaskingRules: stripTypename(dataSinkData.fieldMaskingRules) || undefined,\n refresh: normalizeRefreshInput(dataSinkData.refresh),\n exports: stripTypename(dataSinkData.exports) || undefined,\n alerts: stripTypename(dataSinkData.alerts) || undefined,\n quota: stripTypename(dataSinkData.quota) || undefined,\n metadata: stripTypename(dataSinkData.metadata) || undefined,\n };\n\n const updateResult = await apolloClient.mutate<{ updateDataSink?: DataSink }>({\n mutation: UpdateDataSinkDocument,\n variables: { input: updateInput },\n });\n\n const updatedDataSink = updateResult.data?.updateDataSink;\n if (updatedDataSink) {\n originalUpdated = true;\n originalDataSinkInfo = {\n id: updatedDataSink.id,\n name: updatedDataSink.name,\n key: updatedDataSink.key,\n };\n }\n } else {\n // No changes - entity already exists with same data\n // Return with alreadyExists flag so UI can show \"already exists\" popup\n originalDataSinkInfo = {\n id: existingDataSink.id,\n name: existingDataSink.name,\n key: existingDataSink.key,\n };\n const alreadyExistsResult: ImportDataSinkResult = {\n success: true,\n dataSink: originalDataSinkInfo,\n originalUpdated: false,\n originalDataSink: originalDataSinkInfo,\n alreadyExists: true,\n };\n setImportResult(alreadyExistsResult);\n return alreadyExistsResult;\n }\n\n // If original was updated, return success with the updated DataSink info\n const finalResult: ImportDataSinkResult = {\n success: true,\n dataSink: originalDataSinkInfo,\n originalUpdated,\n originalDataSink: originalDataSinkInfo,\n alreadyExists: false,\n };\n setImportResult(finalResult);\n return finalResult;\n }\n // If original doesn't exist, continue to create a new DataSink\n } catch (fetchErr) {\n // Original DataSink fetch failed (might not exist), continue with creating new\n console.warn('Could not fetch original DataSink:', fetchErr);\n }\n }\n\n // Step 2: Create a NEW DataSink only if original doesn't exist\n // (This happens when importing for the first time, or if original was deleted)\n // Use stripTypename to remove __typename fields from nested objects\n const createInput: CreateDataSinkInput = {\n name: options.name || dataSinkData.name || 'Imported DataSink',\n key: options.key || dataSinkData.key || '',\n description: dataSinkData.description || undefined,\n config: stripTypename(dataSinkData.config) || undefined,\n ...(hasImportedData && { data: stripTypename(dataSinkData.data) }),\n ttl: dataSinkData.ttl ?? undefined,\n fieldMaskingEnabled: dataSinkData.fieldMaskingEnabled ?? false,\n fieldMaskingRules: stripTypename(dataSinkData.fieldMaskingRules) || undefined,\n refresh: normalizeRefreshInput(dataSinkData.refresh),\n exports: stripTypename(dataSinkData.exports) || undefined,\n alerts: stripTypename(dataSinkData.alerts) || undefined,\n quota: stripTypename(dataSinkData.quota) || undefined,\n metadata: stripTypename(dataSinkData.metadata) || undefined,\n };\n\n const result = await apolloClient.mutate<{ createDataSink?: DataSink }>({\n mutation: CreateDataSinkDocument,\n variables: { input: createInput },\n });\n\n const createdDataSink = result.data?.createDataSink;\n\n if (createdDataSink) {\n const finalResult: ImportDataSinkResult = {\n success: true,\n dataSink: {\n id: createdDataSink.id,\n name: createdDataSink.name,\n key: createdDataSink.key,\n },\n originalUpdated: false,\n originalDataSink: undefined,\n };\n setImportResult(finalResult);\n return finalResult;\n }\n\n throw new Error('Import returned empty result');\n } catch (err) {\n const importError = err instanceof Error ? err : new Error('Failed to import DataSink');\n setError(importError);\n const failResult: ImportDataSinkResult = {\n success: false,\n errorMessage: importError.message,\n };\n setImportResult(failResult);\n return failResult;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n // Import DataSink from file\n const importFromFile = useCallback(\n async (file: File, options: ImportDataSinkOptions = {}): Promise<ImportDataSinkResult | null> => {\n try {\n // Validate file type\n if (!file.name.endsWith('.json')) {\n throw new Error('Invalid file type. Please select a JSON file.');\n }\n\n // Read file content\n const content = await readFileAsText(file);\n\n // Validate JSON format\n try {\n JSON.parse(content);\n } catch {\n throw new Error('Invalid JSON format. Please select a valid export file.');\n }\n\n // Perform import\n return await importDataSink(content, options);\n } catch (err) {\n const fileError = err instanceof Error ? err : new Error('Failed to import from file');\n setError(fileError);\n return null;\n }\n },\n [importDataSink]\n );\n\n // Clear state\n const clearState = useCallback(() => {\n setError(null);\n setValidationResult(null);\n setImportResult(null);\n }, []);\n\n return {\n loading,\n error,\n validationResult,\n importResult,\n validateImport,\n importDataSink,\n importFromFile,\n extractDataSinkId,\n clearState,\n };\n}\n\nexport default useImportDataSink;\n"],"mappings":"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"}
@@ -175,13 +175,17 @@ var x = "bigconsole-datasinks-view-mode", Ae = (e) => e.setupState === "complete
175
175
  style: {
176
176
  display: "flex",
177
177
  alignItems: "center",
178
- justifyContent: "space-between"
178
+ justifyContent: "space-between",
179
+ gap: "var(--space-formGap)",
180
+ flexWrap: "wrap"
179
181
  },
180
182
  children: [/* @__PURE__ */ b("div", {
181
183
  style: {
182
184
  display: "flex",
183
185
  alignItems: "center",
184
- gap: "var(--space-formGap)"
186
+ gap: "var(--space-formGap)",
187
+ flex: "1 1 280px",
188
+ minWidth: 0
185
189
  },
186
190
  children: [/* @__PURE__ */ y("div", {
187
191
  style: {
@@ -198,22 +202,25 @@ var x = "bigconsole-datasinks-view-mode", Ae = (e) => e.setupState === "complete
198
202
  width: "var(--size-icon-lg)",
199
203
  color: "var(--color-action-primary-text)"
200
204
  } })
201
- }), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("h1", {
202
- style: {
203
- fontSize: "var(--font-size-heading-h4)",
204
- fontWeight: "var(--font-weight-bold)",
205
- margin: 0,
206
- color: "var(--color-text-primary)"
207
- },
208
- children: /* @__PURE__ */ y(Te, { children: C("bigconsole.dataSinks.title", "Data Sinks") })
209
- }), /* @__PURE__ */ y("p", {
210
- style: {
211
- fontSize: "var(--font-size-body-sm)",
212
- color: "var(--color-text-secondary)",
213
- margin: 0
214
- },
215
- children: C("bigconsole.dataSinks.subtitle", "Manage pre-processed data from FluidGrids workflows")
216
- })] })]
205
+ }), /* @__PURE__ */ b("div", {
206
+ style: { minWidth: 0 },
207
+ children: [/* @__PURE__ */ y("h1", {
208
+ style: {
209
+ fontSize: "var(--font-size-heading-h4)",
210
+ fontWeight: "var(--font-weight-bold)",
211
+ margin: 0,
212
+ color: "var(--color-text-primary)"
213
+ },
214
+ children: /* @__PURE__ */ y(Te, { children: C("bigconsole.dataSinks.title", "Data Sinks") })
215
+ }), /* @__PURE__ */ y("p", {
216
+ style: {
217
+ fontSize: "var(--font-size-body-sm)",
218
+ color: "var(--color-text-secondary)",
219
+ margin: 0
220
+ },
221
+ children: C("bigconsole.dataSinks.subtitle", "Manage pre-processed data from FluidGrids workflows")
222
+ })]
223
+ })]
217
224
  }), /* @__PURE__ */ y(Se, {
218
225
  primary: {
219
226
  label: C("bigconsole.dataSinks.actions.create", "Create Data Sink"),
@@ -287,7 +294,8 @@ var x = "bigconsole-datasinks-view-mode", Ae = (e) => e.setupState === "complete
287
294
  style: {
288
295
  position: "relative",
289
296
  maxWidth: "400px",
290
- flex: 1
297
+ minWidth: "min(100%, 240px)",
298
+ flex: "1 1 240px"
291
299
  },
292
300
  children: [/* @__PURE__ */ y(me, { style: {
293
301
  position: "absolute",
@@ -312,7 +320,7 @@ var x = "bigconsole-datasinks-view-mode", Ae = (e) => e.setupState === "complete
312
320
  value: k || "all",
313
321
  onValueChange: (e) => M(e === "all" ? null : e),
314
322
  children: [/* @__PURE__ */ y(g, {
315
- style: { width: "150px" },
323
+ style: { width: "min(100%, 150px)" },
316
324
  children: /* @__PURE__ */ y(_, { placeholder: C("bigconsole.dataSinks.filters.status.placeholder", "All Statuses") })
317
325
  }), /* @__PURE__ */ b(m, { children: [
318
326
  /* @__PURE__ */ y(h, {
@@ -341,7 +349,7 @@ var x = "bigconsole-datasinks-view-mode", Ae = (e) => e.setupState === "complete
341
349
  value: A === null ? "all" : A ? "true" : "false",
342
350
  onValueChange: (e) => N(e === "all" ? null : e === "true"),
343
351
  children: [/* @__PURE__ */ y(g, {
344
- style: { width: "130px" },
352
+ style: { width: "min(100%, 130px)" },
345
353
  children: /* @__PURE__ */ y(_, { placeholder: C("bigconsole.dataSinks.filters.active.placeholder", "All") })
346
354
  }), /* @__PURE__ */ b(m, { children: [
347
355
  /* @__PURE__ */ y(h, {
@@ -476,7 +484,7 @@ var x = "bigconsole-datasinks-view-mode", Ae = (e) => e.setupState === "complete
476
484
  J.length > 0 && D === "grid" && /* @__PURE__ */ y("div", {
477
485
  style: {
478
486
  display: "grid",
479
- gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))",
487
+ gridTemplateColumns: "repeat(auto-fill, minmax(min(100%, 320px), 1fr))",
480
488
  gap: "var(--space-formGap)"
481
489
  },
482
490
  children: J.map((e) => /* @__PURE__ */ y(ne, {
@@ -1 +1 @@
1
- {"version":3,"file":"DataSinksPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinksPage.tsx"],"sourcesContent":["/**\n * DataSinksPage\n *\n * List and manage DataSinks - fetches data from backend.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { type FC, memo, useCallback, useMemo, useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeNotify } from '../../utils/nativeBridge';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useExportDataSink } from '../hooks/useExportDataSink';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport type { DataSink, DataSinkSetupState } from '../types';\nimport { DataSinkCard } from '../components/datasink/DataSinkCard';\nimport { DataSinkDeleteDialog } from '../components/datasink/DataSinkDeleteDialog';\nimport { DataSinkRefreshDialog } from '../components/datasink/DataSinkRefreshDialog';\nimport { ImportDataSinkDialog } from '../components/datasink/ImportDataSinkDialog';\n\n// fe-libs 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 { Card, CardContent } 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 GlassCard,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n Plus,\n Search,\n AlertCircle,\n Container,\n X,\n Upload,\n LayoutGrid,\n Table2,\n Eye,\n Pencil,\n RefreshCw,\n Download,\n Trash2,\n} from 'lucide-react';\nimport { DataSinkStatusBadge } from '../components/datasink/DataSinkStatusBadge';\nimport { SetupStateBadge } from '../components/datasink/SetupStateBadge';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype SetupStateFilter = 'all' | 'in_progress' | 'completed';\ntype ListViewMode = 'grid' | 'table';\n\nconst VIEW_MODE_KEY = 'bigconsole-datasinks-view-mode';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Determine setup state for a DataSink based on its properties.\n */\nconst getSetupState = (dataSink: DataSink): DataSinkSetupState => {\n if (dataSink.setupState === 'completed') {\n return 'completed';\n }\n if (dataSink.ttl !== null && dataSink.ttl !== undefined && dataSink.ttl > 0) {\n return 'completed';\n }\n if (dataSink.fluidGridsWorkflowId && dataSink.data && Object.keys(dataSink.data).length > 0) {\n return 'completed';\n }\n if (dataSink.fluidGridsWorkflowId) {\n return 'workflow_running';\n }\n return 'workflow_pending';\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DataSinksPage: FC = memo(function DataSinksPage() {\n const navigate = useAppNavigate();\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\n\n // Local state\n const [setupStateFilter, setSetupStateFilter] = useState<SetupStateFilter>('all');\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n const [viewMode, setViewMode] = useState<ListViewMode>(\n () => (localStorage.getItem(getProfileScopedKey(VIEW_MODE_KEY)) as ListViewMode) || 'grid'\n );\n\n const toggleViewMode = useCallback((mode: ListViewMode) => {\n setViewMode(mode);\n localStorage.setItem(getProfileScopedKey(VIEW_MODE_KEY), mode);\n }, []);\n\n // Store state\n const statusFilter = useDataSinkStore((state) => state.statusFilter);\n const activeFilter = useDataSinkStore((state) => state.activeFilter);\n const searchQuery = useDataSinkStore((state) => state.searchQuery);\n const setStatusFilter = useDataSinkStore((state) => state.setStatusFilter);\n const setActiveFilter = useDataSinkStore((state) => state.setActiveFilter);\n const setSearchQuery = useDataSinkStore((state) => state.setSearchQuery);\n const deleteDialogOpen = useDataSinkStore((state) => state.deleteDialogOpen);\n const refreshDialogOpen = useDataSinkStore((state) => state.refreshDialogOpen);\n const currentDataSink = useDataSinkStore((state) => state.currentDataSink);\n const openDeleteDialog = useDataSinkStore((state) => state.openDeleteDialog);\n const closeDeleteDialog = useDataSinkStore((state) => state.closeDeleteDialog);\n const openRefreshDialog = useDataSinkStore((state) => state.openRefreshDialog);\n const closeRefreshDialog = useDataSinkStore((state) => state.closeRefreshDialog);\n\n // Data operations\n const { loading, error, dataSinks, deleteDataSink, refreshDataSink, refetch } = useDataSinkOperations();\n\n // Export hook\n const { downloadDataSink } = useExportDataSink();\n\n // Compute filtered DataSinks with setup states\n const dataSinksWithSetupState = useMemo(() => {\n return dataSinks.map((ds) => ({\n dataSink: ds,\n setupState: getSetupState(ds),\n }));\n }, [dataSinks]);\n\n // Compute filtered DataSinks\n const filteredDataSinks = useMemo(() => {\n let filtered = [...dataSinksWithSetupState];\n\n if (setupStateFilter === 'in_progress') {\n filtered = filtered.filter((item) => item.setupState !== 'completed');\n } else if (setupStateFilter === 'completed') {\n filtered = filtered.filter((item) => item.setupState === 'completed');\n }\n\n if (statusFilter !== null) {\n filtered = filtered.filter((item) => item.dataSink.status === statusFilter);\n }\n\n if (activeFilter !== null) {\n filtered = filtered.filter((item) => item.dataSink.isActive === activeFilter);\n }\n\n if (searchQuery.trim()) {\n const query = searchQuery.toLowerCase();\n filtered = filtered.filter(\n (item) =>\n item.dataSink.name?.toLowerCase().includes(query) ||\n item.dataSink.key.toLowerCase().includes(query) ||\n item.dataSink.description?.toLowerCase().includes(query)\n );\n }\n\n return filtered.sort((a, b) => new Date(b.dataSink.updatedAt).getTime() - new Date(a.dataSink.updatedAt).getTime());\n }, [dataSinksWithSetupState, setupStateFilter, statusFilter, activeFilter, searchQuery]);\n\n // Handlers\n const handleCreateDataSink = useCallback(() => {\n navigate('/datasinks/new');\n }, [navigate]);\n\n const handleViewDataSink = useCallback(\n (id: string) => {\n navigate(`/datasinks/${id}`);\n },\n [navigate]\n );\n\n const handleEditDataSink = useCallback(\n (id: string) => {\n navigate(`/datasinks/${id}/edit`);\n },\n [navigate]\n );\n\n const handleRefreshClick = useCallback(\n (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n openRefreshDialog(dataSink);\n }\n },\n [dataSinks, openRefreshDialog]\n );\n\n const handleRefreshConfirm = useCallback(\n async (forceRefresh: boolean) => {\n if (currentDataSink) {\n await refreshDataSink({\n id: currentDataSink.id,\n forceRefresh,\n });\n closeRefreshDialog();\n }\n },\n [currentDataSink, refreshDataSink, closeRefreshDialog]\n );\n\n const handleDeleteClick = useCallback(\n (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n openDeleteDialog(dataSink);\n }\n },\n [dataSinks, openDeleteDialog]\n );\n\n const handleDeleteConfirm = useCallback(async () => {\n if (currentDataSink) {\n try {\n await deleteDataSink(currentDataSink.id);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n closeDeleteDialog();\n }\n }, [currentDataSink, deleteDataSink, closeDeleteDialog]);\n\n const handleExportDataSink = useCallback(\n async (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n await downloadDataSink(id, dataSink.name || dataSink.key);\n }\n },\n [dataSinks, downloadDataSink]\n );\n\n const handleImportDataSink = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_dataSinkId: string, _dataSinkName: string | null, _wasUpdate: boolean) => {\n // Refetch DataSinks to show the newly imported one\n refetch();\n },\n [refetch]\n );\n\n const hasActiveFilters = useMemo(() => {\n return setupStateFilter !== 'all' || statusFilter !== null || activeFilter !== null || searchQuery.trim() !== '';\n }, [setupStateFilter, statusFilter, activeFilter, searchQuery]);\n\n const handleClearFilters = useCallback(() => {\n setSetupStateFilter('all');\n setStatusFilter(null);\n setActiveFilter(null);\n setSearchQuery('');\n }, [setStatusFilter, setActiveFilter, setSearchQuery]);\n\n // Setup state filter options\n const setupStateOptions: { value: SetupStateFilter; label: string }[] = [\n { value: 'all', label: tr('bigconsole.dataSinks.filters.setupState.all', 'All') },\n { value: 'in_progress', label: tr('bigconsole.dataSinks.filters.setupState.inProgress', 'In Progress') },\n { value: 'completed', label: tr('bigconsole.dataSinks.filters.setupState.completed', 'Completed') },\n ];\n\n // Error state\n if (error) {\n return (\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Card\n style={{\n borderColor: 'var(--color-status-error-border)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n }}\n >\n <CardContent style={{ padding: 'var(--space-pagePadding)' }}>\n <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>\n <div\n style={{\n padding: 'var(--space-controlGap)',\n borderRadius: '50%',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n <AlertCircle\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n color: 'var(--color-status-error-text)',\n }}\n />\n </div>\n <h3\n style={{\n fontSize: 'var(--font-size-body-lg)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-status-error-text)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n {tr('bigconsole.dataSinks.error.title', 'Failed to load Data Sinks')}\n </h3>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n {error.message}\n </p>\n <Button size=\"sm\" onClick={() => refetch()}>\n {tr('bigconsole.dataSinks.error.retry', 'Try Again')}\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Header Section */}\n <div className=\"relative overflow-hidden border-b border-border-seam bg-gloss-card\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-formGap)' }}>\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <Container\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>\n <h1\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n margin: 0,\n color: 'var(--color-text-primary)',\n }}\n >\n <GradientText>{tr('bigconsole.dataSinks.title', 'Data Sinks')}</GradientText>\n </h1>\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-secondary)', margin: 0 }}>\n {tr('bigconsole.dataSinks.subtitle', 'Manage pre-processed data from FluidGrids workflows')}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={{\n label: tr('bigconsole.dataSinks.actions.create', 'Create Data Sink'),\n onSelect: handleCreateDataSink,\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 actions={[\n {\n label: tr('bigconsole.dataSinks.actions.import', 'Import'),\n onSelect: handleImportDataSink,\n icon: <Upload style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />,\n },\n ]}\n />\n </div>\n </div>\n </div>\n\n {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Data sinks are the structured destinations that hold your processed data — define a schema once and every\n pipeline, parser, and workflow can write into it. Dashboards then query sinks directly, so a sink is where\n your data lives and where your charts read from.\n </PagePurpose>\n\n {/* Setup State Filter Tabs */}\n <div style={{ marginBottom: 'var(--space-formGap)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '2px',\n padding: '4px',\n backgroundColor: 'var(--color-bg-sunken)',\n borderRadius: 'var(--radius-card)',\n width: 'fit-content',\n }}\n >\n {setupStateOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => setSetupStateFilter(option.value)}\n style={{\n padding: 'var(--space-controlGap) var(--space-formGap)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n cursor: 'pointer',\n transition: 'all 0.2s ease',\n backgroundColor: setupStateFilter === option.value ? 'var(--color-bg-surface)' : 'transparent',\n color:\n setupStateFilter === option.value ? 'var(--color-text-primary)' : 'var(--color-text-secondary)',\n boxShadow: setupStateFilter === option.value ? '0 1px 2px rgba(0,0,0,0.05)' : 'none',\n }}\n >\n {option.label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Search and Filters Row */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginBottom: 'var(--space-sectionGap)',\n flexWrap: 'wrap',\n }}\n >\n {/* Search Bar */}\n <div style={{ position: 'relative', maxWidth: '400px', flex: 1 }}>\n <Search\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 placeholder={tr('bigconsole.dataSinks.search.placeholder', 'Search data sinks...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Status Filter */}\n <Select\n value={statusFilter || 'all'}\n onValueChange={(value) =>\n setStatusFilter(value === 'all' ? null : (value as 'ready' | 'error' | 'stale' | 'refreshing'))\n }\n >\n <SelectTrigger style={{ width: '150px' }}>\n <SelectValue placeholder={tr('bigconsole.dataSinks.filters.status.placeholder', 'All Statuses')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{tr('bigconsole.dataSinks.filters.status.all', 'All Statuses')}</SelectItem>\n <SelectItem value=\"ready\">{tr('bigconsole.dataSinks.filters.status.ready', 'Ready')}</SelectItem>\n <SelectItem value=\"error\">{tr('bigconsole.dataSinks.filters.status.error', 'Error')}</SelectItem>\n <SelectItem value=\"stale\">{tr('bigconsole.dataSinks.filters.status.stale', 'Stale')}</SelectItem>\n <SelectItem value=\"refreshing\">\n {tr('bigconsole.dataSinks.filters.status.refreshing', 'Refreshing')}\n </SelectItem>\n </SelectContent>\n </Select>\n\n {/* Active Filter */}\n <Select\n value={activeFilter === null ? 'all' : activeFilter ? 'true' : 'false'}\n onValueChange={(value) => setActiveFilter(value === 'all' ? null : value === 'true')}\n >\n <SelectTrigger style={{ width: '130px' }}>\n <SelectValue placeholder={tr('bigconsole.dataSinks.filters.active.placeholder', 'All')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{tr('bigconsole.dataSinks.filters.active.all', 'All')}</SelectItem>\n <SelectItem value=\"true\">{tr('bigconsole.dataSinks.filters.active.active', 'Active')}</SelectItem>\n <SelectItem value=\"false\">{tr('bigconsole.dataSinks.filters.active.inactive', 'Inactive')}</SelectItem>\n </SelectContent>\n </Select>\n\n {/* Clear Filters */}\n {hasActiveFilters && (\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\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.dataSinks.filters.clear', 'Clear Filters')}\n </Button>\n )}\n\n {/* Results Count + View Toggle */}\n <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 'var(--space-controlGap)' }}>\n <Badge variant=\"secondary\">\n {filteredDataSinks.length}{' '}\n {filteredDataSinks.length === 1\n ? tr('bigconsole.dataSinks.count.single', 'Data Sink')\n : tr('bigconsole.dataSinks.count.plural', 'Data Sinks')}\n </Badge>\n <div\n style={{\n display: 'flex',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n overflow: 'hidden',\n }}\n >\n <button\n type=\"button\"\n onClick={() => toggleViewMode('grid')}\n title={tr('bigconsole.dataSinks.view.grid', 'Grid view')}\n style={{\n padding: '6px 8px',\n border: 'none',\n cursor: 'pointer',\n backgroundColor: viewMode === 'grid' ? 'var(--color-bg-sunken)' : 'transparent',\n color: viewMode === 'grid' ? 'var(--color-text-primary)' : 'var(--color-text-muted)',\n }}\n >\n <LayoutGrid size={16} />\n </button>\n <button\n type=\"button\"\n onClick={() => toggleViewMode('table')}\n title={tr('bigconsole.dataSinks.view.table', 'Table view')}\n style={{\n padding: '6px 8px',\n border: 'none',\n borderLeft: '1px solid var(--color-border)',\n cursor: 'pointer',\n backgroundColor: viewMode === 'table' ? 'var(--color-bg-sunken)' : 'transparent',\n color: viewMode === 'table' ? 'var(--color-text-primary)' : 'var(--color-text-muted)',\n }}\n >\n <Table2 size={16} />\n </button>\n </div>\n </div>\n </div>\n\n {/* Loading state */}\n {loading && dataSinks.length === 0 && (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '80px 0' }}>\n <div style={{ textAlign: '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 style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dataSinks.loading', 'Loading data sinks...')}\n </p>\n </div>\n </div>\n )}\n\n {/* Empty state */}\n {!loading && dataSinks.length === 0 && (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('bigconsole.dataSinks.empty.title', 'No Data Sinks yet')}\n description={tr(\n 'bigconsole.dataSinks.empty.description',\n 'Data Sinks store pre-processed data from FluidGrids workflows for fast access in widgets.'\n )}\n action={\n <Button onClick={handleCreateDataSink}>\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.dataSinks.actions.create', 'Create Data Sink')}\n </Button>\n }\n />\n </GlassCard>\n )}\n\n {/* No results state */}\n {!loading && filteredDataSinks.length === 0 && dataSinks.length > 0 && (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tr('bigconsole.dataSinks.empty.filteredTitle', 'No matching data sinks')}\n description={tr('bigconsole.dataSinks.empty.filtered', 'No data sinks match your filters')}\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n {tr('bigconsole.dataSinks.filters.clear', 'Clear Filters')}\n </Button>\n }\n />\n </GlassCard>\n )}\n\n {/* DataSink Grid / Table */}\n {filteredDataSinks.length > 0 && viewMode === 'grid' && (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {filteredDataSinks.map((item) => (\n <DataSinkCard\n key={item.dataSink.id}\n dataSink={item.dataSink}\n setupState={item.setupState}\n onView={handleViewDataSink}\n onEdit={handleEditDataSink}\n onRefresh={handleRefreshClick}\n onExport={handleExportDataSink}\n onDelete={handleDeleteClick}\n />\n ))}\n </div>\n )}\n\n {/* Table View */}\n {filteredDataSinks.length > 0 && viewMode === 'table' && (\n <div\n style={{\n border: '1px solid var(--color-border)',\n borderRadius: 'var(--radius-card)',\n overflow: 'hidden',\n }}\n >\n <div style={{ overflowX: 'auto' }}>\n <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 'var(--font-size-body-sm)' }}>\n <thead>\n <tr\n style={{\n backgroundColor: 'var(--color-bg-sunken)',\n borderBottom: '1px solid var(--color-border)',\n }}\n >\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.name', 'Name')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.key', 'Key')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.status', 'Status')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.setup', 'Setup')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'center',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.active', 'Active')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.version', 'Version')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.updated', 'Updated')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'right',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {filteredDataSinks.map((item) => {\n const ds = item.dataSink;\n return (\n <tr\n key={ds.id}\n onClick={() => handleViewDataSink(ds.id)}\n style={{\n borderBottom: '1px solid var(--color-border)',\n cursor: 'pointer',\n transition: 'background-color 0.15s',\n }}\n onMouseEnter={(e) => {\n (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--color-bg-sunken)';\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as HTMLElement).style.backgroundColor = 'transparent';\n }}\n >\n <td\n style={{\n padding: '10px 12px',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n maxWidth: 200,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {ds.name || ds.key}\n </td>\n <td\n style={{\n padding: '10px 12px',\n color: 'var(--color-text-muted)',\n fontFamily: 'monospace',\n fontSize: '12px',\n maxWidth: 160,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {ds.key}\n </td>\n <td style={{ padding: '10px 12px' }}>\n <DataSinkStatusBadge status={ds.status} />\n </td>\n <td style={{ padding: '10px 12px' }}>\n <SetupStateBadge state={item.setupState} />\n </td>\n <td style={{ padding: '10px 12px', textAlign: 'center' }}>\n <span\n style={{\n display: 'inline-block',\n width: 8,\n height: 8,\n borderRadius: '50%',\n backgroundColor: ds.isActive ? '#22c55e' : 'var(--color-text-muted)',\n }}\n title={\n ds.isActive\n ? tr('bigconsole.dataSinks.badges.active', 'Active')\n : tr('bigconsole.dataSinks.badges.inactive', 'Inactive')\n }\n />\n </td>\n <td style={{ padding: '10px 12px', color: 'var(--color-text-muted)' }}>v{ds.version ?? 0}</td>\n <td style={{ padding: '10px 12px', color: 'var(--color-text-muted)', whiteSpace: 'nowrap' }}>\n {new Date(ds.updatedAt).toLocaleDateString()}\n </td>\n <td style={{ padding: '10px 12px', textAlign: 'right' }}>\n <div\n style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}\n onClick={(e) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.view', 'View')}\n onClick={() => handleViewDataSink(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <Eye size={14} />\n </button>\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.edit', 'Edit')}\n onClick={() => handleEditDataSink(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <Pencil size={14} />\n </button>\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.refresh', 'Refresh')}\n onClick={() => handleRefreshClick(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <RefreshCw size={14} />\n </button>\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.export', 'Export')}\n onClick={() => handleExportDataSink(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <Download size={14} />\n </button>\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.delete', 'Delete')}\n onClick={() => handleDeleteClick(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <Trash2 size={14} />\n </button>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n )}\n </div>\n\n {/* Dialogs */}\n {currentDataSink && (\n <>\n <DataSinkDeleteDialog\n dataSink={currentDataSink}\n isOpen={deleteDialogOpen}\n onClose={closeDeleteDialog}\n onConfirm={handleDeleteConfirm}\n />\n\n <DataSinkRefreshDialog\n dataSink={currentDataSink}\n isOpen={refreshDialogOpen}\n onClose={closeRefreshDialog}\n onConfirm={handleRefreshConfirm}\n />\n </>\n )}\n\n {/* Import DataSink Dialog */}\n <ImportDataSinkDialog\n isOpen={isImportDialogOpen}\n onClose={() => setIsImportDialogOpen(false)}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DataSinksPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataSinksPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinksPage.tsx"],"sourcesContent":["/**\n * DataSinksPage\n *\n * List and manage DataSinks - fetches data from backend.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { type FC, memo, useCallback, useMemo, useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeNotify } from '../../utils/nativeBridge';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useExportDataSink } from '../hooks/useExportDataSink';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport type { DataSink, DataSinkSetupState } from '../types';\nimport { DataSinkCard } from '../components/datasink/DataSinkCard';\nimport { DataSinkDeleteDialog } from '../components/datasink/DataSinkDeleteDialog';\nimport { DataSinkRefreshDialog } from '../components/datasink/DataSinkRefreshDialog';\nimport { ImportDataSinkDialog } from '../components/datasink/ImportDataSinkDialog';\n\n// fe-libs 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 { Card, CardContent } 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 GlassCard,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n Plus,\n Search,\n AlertCircle,\n Container,\n X,\n Upload,\n LayoutGrid,\n Table2,\n Eye,\n Pencil,\n RefreshCw,\n Download,\n Trash2,\n} from 'lucide-react';\nimport { DataSinkStatusBadge } from '../components/datasink/DataSinkStatusBadge';\nimport { SetupStateBadge } from '../components/datasink/SetupStateBadge';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype SetupStateFilter = 'all' | 'in_progress' | 'completed';\ntype ListViewMode = 'grid' | 'table';\n\nconst VIEW_MODE_KEY = 'bigconsole-datasinks-view-mode';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Determine setup state for a DataSink based on its properties.\n */\nconst getSetupState = (dataSink: DataSink): DataSinkSetupState => {\n if (dataSink.setupState === 'completed') {\n return 'completed';\n }\n if (dataSink.ttl !== null && dataSink.ttl !== undefined && dataSink.ttl > 0) {\n return 'completed';\n }\n if (dataSink.fluidGridsWorkflowId && dataSink.data && Object.keys(dataSink.data).length > 0) {\n return 'completed';\n }\n if (dataSink.fluidGridsWorkflowId) {\n return 'workflow_running';\n }\n return 'workflow_pending';\n};\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DataSinksPage: FC = memo(function DataSinksPage() {\n const navigate = useAppNavigate();\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\n\n // Local state\n const [setupStateFilter, setSetupStateFilter] = useState<SetupStateFilter>('all');\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n const [viewMode, setViewMode] = useState<ListViewMode>(\n () => (localStorage.getItem(getProfileScopedKey(VIEW_MODE_KEY)) as ListViewMode) || 'grid'\n );\n\n const toggleViewMode = useCallback((mode: ListViewMode) => {\n setViewMode(mode);\n localStorage.setItem(getProfileScopedKey(VIEW_MODE_KEY), mode);\n }, []);\n\n // Store state\n const statusFilter = useDataSinkStore((state) => state.statusFilter);\n const activeFilter = useDataSinkStore((state) => state.activeFilter);\n const searchQuery = useDataSinkStore((state) => state.searchQuery);\n const setStatusFilter = useDataSinkStore((state) => state.setStatusFilter);\n const setActiveFilter = useDataSinkStore((state) => state.setActiveFilter);\n const setSearchQuery = useDataSinkStore((state) => state.setSearchQuery);\n const deleteDialogOpen = useDataSinkStore((state) => state.deleteDialogOpen);\n const refreshDialogOpen = useDataSinkStore((state) => state.refreshDialogOpen);\n const currentDataSink = useDataSinkStore((state) => state.currentDataSink);\n const openDeleteDialog = useDataSinkStore((state) => state.openDeleteDialog);\n const closeDeleteDialog = useDataSinkStore((state) => state.closeDeleteDialog);\n const openRefreshDialog = useDataSinkStore((state) => state.openRefreshDialog);\n const closeRefreshDialog = useDataSinkStore((state) => state.closeRefreshDialog);\n\n // Data operations\n const { loading, error, dataSinks, deleteDataSink, refreshDataSink, refetch } = useDataSinkOperations();\n\n // Export hook\n const { downloadDataSink } = useExportDataSink();\n\n // Compute filtered DataSinks with setup states\n const dataSinksWithSetupState = useMemo(() => {\n return dataSinks.map((ds) => ({\n dataSink: ds,\n setupState: getSetupState(ds),\n }));\n }, [dataSinks]);\n\n // Compute filtered DataSinks\n const filteredDataSinks = useMemo(() => {\n let filtered = [...dataSinksWithSetupState];\n\n if (setupStateFilter === 'in_progress') {\n filtered = filtered.filter((item) => item.setupState !== 'completed');\n } else if (setupStateFilter === 'completed') {\n filtered = filtered.filter((item) => item.setupState === 'completed');\n }\n\n if (statusFilter !== null) {\n filtered = filtered.filter((item) => item.dataSink.status === statusFilter);\n }\n\n if (activeFilter !== null) {\n filtered = filtered.filter((item) => item.dataSink.isActive === activeFilter);\n }\n\n if (searchQuery.trim()) {\n const query = searchQuery.toLowerCase();\n filtered = filtered.filter(\n (item) =>\n item.dataSink.name?.toLowerCase().includes(query) ||\n item.dataSink.key.toLowerCase().includes(query) ||\n item.dataSink.description?.toLowerCase().includes(query)\n );\n }\n\n return filtered.sort((a, b) => new Date(b.dataSink.updatedAt).getTime() - new Date(a.dataSink.updatedAt).getTime());\n }, [dataSinksWithSetupState, setupStateFilter, statusFilter, activeFilter, searchQuery]);\n\n // Handlers\n const handleCreateDataSink = useCallback(() => {\n navigate('/datasinks/new');\n }, [navigate]);\n\n const handleViewDataSink = useCallback(\n (id: string) => {\n navigate(`/datasinks/${id}`);\n },\n [navigate]\n );\n\n const handleEditDataSink = useCallback(\n (id: string) => {\n navigate(`/datasinks/${id}/edit`);\n },\n [navigate]\n );\n\n const handleRefreshClick = useCallback(\n (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n openRefreshDialog(dataSink);\n }\n },\n [dataSinks, openRefreshDialog]\n );\n\n const handleRefreshConfirm = useCallback(\n async (forceRefresh: boolean) => {\n if (currentDataSink) {\n await refreshDataSink({\n id: currentDataSink.id,\n forceRefresh,\n });\n closeRefreshDialog();\n }\n },\n [currentDataSink, refreshDataSink, closeRefreshDialog]\n );\n\n const handleDeleteClick = useCallback(\n (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n openDeleteDialog(dataSink);\n }\n },\n [dataSinks, openDeleteDialog]\n );\n\n const handleDeleteConfirm = useCallback(async () => {\n if (currentDataSink) {\n try {\n await deleteDataSink(currentDataSink.id);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n closeDeleteDialog();\n }\n }, [currentDataSink, deleteDataSink, closeDeleteDialog]);\n\n const handleExportDataSink = useCallback(\n async (id: string) => {\n const dataSink = dataSinks.find((ds) => ds.id === id);\n if (dataSink) {\n await downloadDataSink(id, dataSink.name || dataSink.key);\n }\n },\n [dataSinks, downloadDataSink]\n );\n\n const handleImportDataSink = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_dataSinkId: string, _dataSinkName: string | null, _wasUpdate: boolean) => {\n // Refetch DataSinks to show the newly imported one\n refetch();\n },\n [refetch]\n );\n\n const hasActiveFilters = useMemo(() => {\n return setupStateFilter !== 'all' || statusFilter !== null || activeFilter !== null || searchQuery.trim() !== '';\n }, [setupStateFilter, statusFilter, activeFilter, searchQuery]);\n\n const handleClearFilters = useCallback(() => {\n setSetupStateFilter('all');\n setStatusFilter(null);\n setActiveFilter(null);\n setSearchQuery('');\n }, [setStatusFilter, setActiveFilter, setSearchQuery]);\n\n // Setup state filter options\n const setupStateOptions: { value: SetupStateFilter; label: string }[] = [\n { value: 'all', label: tr('bigconsole.dataSinks.filters.setupState.all', 'All') },\n { value: 'in_progress', label: tr('bigconsole.dataSinks.filters.setupState.inProgress', 'In Progress') },\n { value: 'completed', label: tr('bigconsole.dataSinks.filters.setupState.completed', 'Completed') },\n ];\n\n // Error state\n if (error) {\n return (\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Card\n style={{\n borderColor: 'var(--color-status-error-border)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n }}\n >\n <CardContent style={{ padding: 'var(--space-pagePadding)' }}>\n <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>\n <div\n style={{\n padding: 'var(--space-controlGap)',\n borderRadius: '50%',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n <AlertCircle\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n color: 'var(--color-status-error-text)',\n }}\n />\n </div>\n <h3\n style={{\n fontSize: 'var(--font-size-body-lg)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-status-error-text)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n {tr('bigconsole.dataSinks.error.title', 'Failed to load Data Sinks')}\n </h3>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n {error.message}\n </p>\n <Button size=\"sm\" onClick={() => refetch()}>\n {tr('bigconsole.dataSinks.error.retry', 'Try Again')}\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Header Section */}\n <div className=\"relative overflow-hidden border-b border-border-seam bg-gloss-card\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 'var(--space-formGap)',\n flexWrap: 'wrap',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n flex: '1 1 280px',\n minWidth: 0,\n }}\n >\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <Container\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 style={{ minWidth: 0 }}>\n <h1\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n margin: 0,\n color: 'var(--color-text-primary)',\n }}\n >\n <GradientText>{tr('bigconsole.dataSinks.title', 'Data Sinks')}</GradientText>\n </h1>\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-secondary)', margin: 0 }}>\n {tr('bigconsole.dataSinks.subtitle', 'Manage pre-processed data from FluidGrids workflows')}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={{\n label: tr('bigconsole.dataSinks.actions.create', 'Create Data Sink'),\n onSelect: handleCreateDataSink,\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 actions={[\n {\n label: tr('bigconsole.dataSinks.actions.import', 'Import'),\n onSelect: handleImportDataSink,\n icon: <Upload style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />,\n },\n ]}\n />\n </div>\n </div>\n </div>\n\n {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Data sinks are the structured destinations that hold your processed data — define a schema once and every\n pipeline, parser, and workflow can write into it. Dashboards then query sinks directly, so a sink is where\n your data lives and where your charts read from.\n </PagePurpose>\n\n {/* Setup State Filter Tabs */}\n <div style={{ marginBottom: 'var(--space-formGap)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '2px',\n padding: '4px',\n backgroundColor: 'var(--color-bg-sunken)',\n borderRadius: 'var(--radius-card)',\n width: 'fit-content',\n }}\n >\n {setupStateOptions.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => setSetupStateFilter(option.value)}\n style={{\n padding: 'var(--space-controlGap) var(--space-formGap)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n cursor: 'pointer',\n transition: 'all 0.2s ease',\n backgroundColor: setupStateFilter === option.value ? 'var(--color-bg-surface)' : 'transparent',\n color:\n setupStateFilter === option.value ? 'var(--color-text-primary)' : 'var(--color-text-secondary)',\n boxShadow: setupStateFilter === option.value ? '0 1px 2px rgba(0,0,0,0.05)' : 'none',\n }}\n >\n {option.label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Search and Filters Row */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginBottom: 'var(--space-sectionGap)',\n flexWrap: 'wrap',\n }}\n >\n {/* Search Bar */}\n <div style={{ position: 'relative', maxWidth: '400px', minWidth: 'min(100%, 240px)', flex: '1 1 240px' }}>\n <Search\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 placeholder={tr('bigconsole.dataSinks.search.placeholder', 'Search data sinks...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Status Filter */}\n <Select\n value={statusFilter || 'all'}\n onValueChange={(value) =>\n setStatusFilter(value === 'all' ? null : (value as 'ready' | 'error' | 'stale' | 'refreshing'))\n }\n >\n <SelectTrigger style={{ width: 'min(100%, 150px)' }}>\n <SelectValue placeholder={tr('bigconsole.dataSinks.filters.status.placeholder', 'All Statuses')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{tr('bigconsole.dataSinks.filters.status.all', 'All Statuses')}</SelectItem>\n <SelectItem value=\"ready\">{tr('bigconsole.dataSinks.filters.status.ready', 'Ready')}</SelectItem>\n <SelectItem value=\"error\">{tr('bigconsole.dataSinks.filters.status.error', 'Error')}</SelectItem>\n <SelectItem value=\"stale\">{tr('bigconsole.dataSinks.filters.status.stale', 'Stale')}</SelectItem>\n <SelectItem value=\"refreshing\">\n {tr('bigconsole.dataSinks.filters.status.refreshing', 'Refreshing')}\n </SelectItem>\n </SelectContent>\n </Select>\n\n {/* Active Filter */}\n <Select\n value={activeFilter === null ? 'all' : activeFilter ? 'true' : 'false'}\n onValueChange={(value) => setActiveFilter(value === 'all' ? null : value === 'true')}\n >\n <SelectTrigger style={{ width: 'min(100%, 130px)' }}>\n <SelectValue placeholder={tr('bigconsole.dataSinks.filters.active.placeholder', 'All')} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{tr('bigconsole.dataSinks.filters.active.all', 'All')}</SelectItem>\n <SelectItem value=\"true\">{tr('bigconsole.dataSinks.filters.active.active', 'Active')}</SelectItem>\n <SelectItem value=\"false\">{tr('bigconsole.dataSinks.filters.active.inactive', 'Inactive')}</SelectItem>\n </SelectContent>\n </Select>\n\n {/* Clear Filters */}\n {hasActiveFilters && (\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\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.dataSinks.filters.clear', 'Clear Filters')}\n </Button>\n )}\n\n {/* Results Count + View Toggle */}\n <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 'var(--space-controlGap)' }}>\n <Badge variant=\"secondary\">\n {filteredDataSinks.length}{' '}\n {filteredDataSinks.length === 1\n ? tr('bigconsole.dataSinks.count.single', 'Data Sink')\n : tr('bigconsole.dataSinks.count.plural', 'Data Sinks')}\n </Badge>\n <div\n style={{\n display: 'flex',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n overflow: 'hidden',\n }}\n >\n <button\n type=\"button\"\n onClick={() => toggleViewMode('grid')}\n title={tr('bigconsole.dataSinks.view.grid', 'Grid view')}\n style={{\n padding: '6px 8px',\n border: 'none',\n cursor: 'pointer',\n backgroundColor: viewMode === 'grid' ? 'var(--color-bg-sunken)' : 'transparent',\n color: viewMode === 'grid' ? 'var(--color-text-primary)' : 'var(--color-text-muted)',\n }}\n >\n <LayoutGrid size={16} />\n </button>\n <button\n type=\"button\"\n onClick={() => toggleViewMode('table')}\n title={tr('bigconsole.dataSinks.view.table', 'Table view')}\n style={{\n padding: '6px 8px',\n border: 'none',\n borderLeft: '1px solid var(--color-border)',\n cursor: 'pointer',\n backgroundColor: viewMode === 'table' ? 'var(--color-bg-sunken)' : 'transparent',\n color: viewMode === 'table' ? 'var(--color-text-primary)' : 'var(--color-text-muted)',\n }}\n >\n <Table2 size={16} />\n </button>\n </div>\n </div>\n </div>\n\n {/* Loading state */}\n {loading && dataSinks.length === 0 && (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '80px 0' }}>\n <div style={{ textAlign: '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 style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dataSinks.loading', 'Loading data sinks...')}\n </p>\n </div>\n </div>\n )}\n\n {/* Empty state */}\n {!loading && dataSinks.length === 0 && (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('bigconsole.dataSinks.empty.title', 'No Data Sinks yet')}\n description={tr(\n 'bigconsole.dataSinks.empty.description',\n 'Data Sinks store pre-processed data from FluidGrids workflows for fast access in widgets.'\n )}\n action={\n <Button onClick={handleCreateDataSink}>\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.dataSinks.actions.create', 'Create Data Sink')}\n </Button>\n }\n />\n </GlassCard>\n )}\n\n {/* No results state */}\n {!loading && filteredDataSinks.length === 0 && dataSinks.length > 0 && (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tr('bigconsole.dataSinks.empty.filteredTitle', 'No matching data sinks')}\n description={tr('bigconsole.dataSinks.empty.filtered', 'No data sinks match your filters')}\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n {tr('bigconsole.dataSinks.filters.clear', 'Clear Filters')}\n </Button>\n }\n />\n </GlassCard>\n )}\n\n {/* DataSink Grid / Table */}\n {filteredDataSinks.length > 0 && viewMode === 'grid' && (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 320px), 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {filteredDataSinks.map((item) => (\n <DataSinkCard\n key={item.dataSink.id}\n dataSink={item.dataSink}\n setupState={item.setupState}\n onView={handleViewDataSink}\n onEdit={handleEditDataSink}\n onRefresh={handleRefreshClick}\n onExport={handleExportDataSink}\n onDelete={handleDeleteClick}\n />\n ))}\n </div>\n )}\n\n {/* Table View */}\n {filteredDataSinks.length > 0 && viewMode === 'table' && (\n <div\n style={{\n border: '1px solid var(--color-border)',\n borderRadius: 'var(--radius-card)',\n overflow: 'hidden',\n }}\n >\n <div style={{ overflowX: 'auto' }}>\n <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 'var(--font-size-body-sm)' }}>\n <thead>\n <tr\n style={{\n backgroundColor: 'var(--color-bg-sunken)',\n borderBottom: '1px solid var(--color-border)',\n }}\n >\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.name', 'Name')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.key', 'Key')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.status', 'Status')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.setup', 'Setup')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'center',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.active', 'Active')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.version', 'Version')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'left',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.updated', 'Updated')}\n </th>\n <th\n style={{\n padding: '10px 12px',\n textAlign: 'right',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {tr('bigconsole.dataSinks.table.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {filteredDataSinks.map((item) => {\n const ds = item.dataSink;\n return (\n <tr\n key={ds.id}\n onClick={() => handleViewDataSink(ds.id)}\n style={{\n borderBottom: '1px solid var(--color-border)',\n cursor: 'pointer',\n transition: 'background-color 0.15s',\n }}\n onMouseEnter={(e) => {\n (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--color-bg-sunken)';\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as HTMLElement).style.backgroundColor = 'transparent';\n }}\n >\n <td\n style={{\n padding: '10px 12px',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n maxWidth: 200,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {ds.name || ds.key}\n </td>\n <td\n style={{\n padding: '10px 12px',\n color: 'var(--color-text-muted)',\n fontFamily: 'monospace',\n fontSize: '12px',\n maxWidth: 160,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {ds.key}\n </td>\n <td style={{ padding: '10px 12px' }}>\n <DataSinkStatusBadge status={ds.status} />\n </td>\n <td style={{ padding: '10px 12px' }}>\n <SetupStateBadge state={item.setupState} />\n </td>\n <td style={{ padding: '10px 12px', textAlign: 'center' }}>\n <span\n style={{\n display: 'inline-block',\n width: 8,\n height: 8,\n borderRadius: '50%',\n backgroundColor: ds.isActive ? '#22c55e' : 'var(--color-text-muted)',\n }}\n title={\n ds.isActive\n ? tr('bigconsole.dataSinks.badges.active', 'Active')\n : tr('bigconsole.dataSinks.badges.inactive', 'Inactive')\n }\n />\n </td>\n <td style={{ padding: '10px 12px', color: 'var(--color-text-muted)' }}>v{ds.version ?? 0}</td>\n <td style={{ padding: '10px 12px', color: 'var(--color-text-muted)', whiteSpace: 'nowrap' }}>\n {new Date(ds.updatedAt).toLocaleDateString()}\n </td>\n <td style={{ padding: '10px 12px', textAlign: 'right' }}>\n <div\n style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}\n onClick={(e) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.view', 'View')}\n onClick={() => handleViewDataSink(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <Eye size={14} />\n </button>\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.edit', 'Edit')}\n onClick={() => handleEditDataSink(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <Pencil size={14} />\n </button>\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.refresh', 'Refresh')}\n onClick={() => handleRefreshClick(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <RefreshCw size={14} />\n </button>\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.export', 'Export')}\n onClick={() => handleExportDataSink(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <Download size={14} />\n </button>\n <button\n type=\"button\"\n title={tr('bigconsole.dataSinks.actions.delete', 'Delete')}\n onClick={() => handleDeleteClick(ds.id)}\n style={{\n padding: 4,\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: 'var(--color-text-muted)',\n borderRadius: 4,\n }}\n >\n <Trash2 size={14} />\n </button>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n )}\n </div>\n\n {/* Dialogs */}\n {currentDataSink && (\n <>\n <DataSinkDeleteDialog\n dataSink={currentDataSink}\n isOpen={deleteDialogOpen}\n onClose={closeDeleteDialog}\n onConfirm={handleDeleteConfirm}\n />\n\n <DataSinkRefreshDialog\n dataSink={currentDataSink}\n isOpen={refreshDialogOpen}\n onClose={closeRefreshDialog}\n onConfirm={handleRefreshConfirm}\n />\n </>\n )}\n\n {/* Import DataSink Dialog */}\n <ImportDataSinkDialog\n isOpen={isImportDialogOpen}\n onClose={() => setIsImportDialogOpen(false)}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DataSinksPage;\n"],"mappings":"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"}
@@ -158,7 +158,7 @@ var _ = e(function() {
158
158
  children: [
159
159
  /* @__PURE__ */ d(a, { size: 14 }),
160
160
  " ",
161
- _ ? "Saved" : "Save"
161
+ _ ? "Saved locally" : "Save locally"
162
162
  ]
163
163
  })]
164
164
  })
@@ -166,7 +166,7 @@ var _ = e(function() {
166
166
  }),
167
167
  /* @__PURE__ */ d(u, {
168
168
  className: "mb-[var(--space-sectionGap)]",
169
- children: "Settings control the defaults BigConsole applies everywhere — how new dashboards are laid out, how aggressively widget data is cached, and how the UI is displayed. Tune these once to match how your team works; they apply across every dashboard you build."
169
+ children: "Settings control the defaults BigConsole applies everywhere — how new dashboards are laid out, how aggressively widget data is cached, and how the UI is displayed. Tune these once to match how your team works; they apply across every dashboard you build. These preferences are saved locally in this browser until the backend settings API is available."
170
170
  }),
171
171
  /* @__PURE__ */ f(c, {
172
172
  className: "mb-[var(--space-sectionGap)] p-[var(--space-cardPadding)]",
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsPage.js","names":[],"sources":["../../../src/bigconsole/pages/SettingsPage.tsx"],"sourcesContent":["/**\n * SettingsPage\n *\n * BigConsole workspace settings and preferences.\n * Settings are persisted to localStorage until a backend settings API is available.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport { AuroraBackground, GradientText, PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { Settings, Save, RotateCcw } from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface BigConsoleSettings {\n defaultRefreshInterval: number;\n defaultGridColumns: number;\n defaultWidgetHeight: number;\n cacheEnabled: boolean;\n cacheTTLSeconds: number;\n showWidgetBorders: boolean;\n compactMode: boolean;\n animationsEnabled: boolean;\n}\n\nconst STORAGE_KEY = 'bigconsole-settings';\n\nconst DEFAULT_SETTINGS: BigConsoleSettings = {\n defaultRefreshInterval: 0,\n defaultGridColumns: 12,\n defaultWidgetHeight: 4,\n cacheEnabled: true,\n cacheTTLSeconds: 300,\n showWidgetBorders: true,\n compactMode: false,\n animationsEnabled: true,\n};\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\nfunction loadSettings(): BigConsoleSettings {\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored) {\n return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) };\n }\n } catch {\n // Fall through to defaults\n }\n return { ...DEFAULT_SETTINGS };\n}\n\nfunction saveSettings(settings: BigConsoleSettings): void {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const SettingsPage: FC = memo(function SettingsPage() {\n const [settings, setSettings] = useState<BigConsoleSettings>(loadSettings);\n const [saved, setSaved] = useState(false);\n\n useEffect(() => {\n if (saved) {\n const timer = setTimeout(() => setSaved(false), 2000);\n return () => clearTimeout(timer);\n }\n }, [saved]);\n\n const handleChange = useCallback(<K extends keyof BigConsoleSettings>(key: K, value: BigConsoleSettings[K]) => {\n setSettings((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const handleSave = useCallback(() => {\n saveSettings(settings);\n setSaved(true);\n }, [settings]);\n\n const handleReset = useCallback(() => {\n setSettings({ ...DEFAULT_SETTINGS });\n saveSettings(DEFAULT_SETTINGS);\n setSaved(true);\n }, []);\n\n const inputStyle: React.CSSProperties = {\n width: '100%',\n padding: '8px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-canvas)',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n outline: 'none',\n };\n\n const labelStyle: React.CSSProperties = {\n display: 'block',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n marginBottom: 4,\n };\n\n const descStyle: React.CSSProperties = {\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginTop: 4,\n };\n\n const sectionStyle: React.CSSProperties = {\n backgroundColor: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 'var(--radius-card)',\n padding: 'var(--space-cardPadding)',\n marginBottom: 'var(--space-sectionGap)',\n boxShadow: 'var(--shadow-pop)',\n };\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n <div style={{ padding: 'var(--space-pagePadding)', maxWidth: 720, margin: '0 auto' }}>\n {/* Header */}\n <div\n className=\"relative overflow-hidden rounded-card border border-border-seam bg-gloss-card p-6\"\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: 'var(--space-sectionGap)',\n flexWrap: 'wrap',\n gap: 'var(--space-formGap)',\n }}\n >\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Settings size={24} style={{ color: 'var(--color-text-primary)' }} />\n <h1\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n color: 'var(--color-text-primary)',\n }}\n >\n <GradientText>Settings</GradientText>\n </h1>\n </div>\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-secondary)', marginTop: 4 }}>\n Configure BigConsole preferences\n </p>\n </div>\n <div style={{ display: 'flex', gap: 8 }}>\n <button\n onClick={handleReset}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '8px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-secondary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n <RotateCcw size={14} /> Reset\n </button>\n <button\n onClick={handleSave}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '8px 16px',\n borderRadius: 'var(--radius-button)',\n backgroundColor: saved ? 'var(--color-status-success-bg)' : 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n fontWeight: 'var(--font-weight-medium)',\n fontSize: 'var(--font-size-body-sm)',\n border: 'none',\n cursor: 'pointer',\n transition: 'background-color 200ms',\n }}\n >\n <Save size={14} /> {saved ? 'Saved' : 'Save'}\n </button>\n </div>\n </div>\n\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Settings control the defaults BigConsole applies everywhere — how new dashboards are laid out, how\n aggressively widget data is cached, and how the UI is displayed. Tune these once to match how your team works;\n they apply across every dashboard you build.\n </PagePurpose>\n\n {/* Dashboard Defaults — primary emphasis zone */}\n <EmphasisPanel className=\"mb-[var(--space-sectionGap)] p-[var(--space-cardPadding)]\">\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 16,\n }}\n >\n Dashboard Defaults\n </h2>\n <div style={{ display: 'grid', gap: 16 }}>\n <div>\n <label htmlFor=\"grid-cols\" style={labelStyle}>\n Grid Columns\n </label>\n <input\n id=\"grid-cols\"\n type=\"number\"\n min={4}\n max={24}\n value={settings.defaultGridColumns}\n onChange={(e) => handleChange('defaultGridColumns', parseInt(e.target.value) || 12)}\n style={inputStyle}\n />\n <p style={descStyle}>Number of columns in the dashboard grid (4-24)</p>\n </div>\n <div>\n <label htmlFor=\"widget-height\" style={labelStyle}>\n Default Widget Height\n </label>\n <input\n id=\"widget-height\"\n type=\"number\"\n min={2}\n max={12}\n value={settings.defaultWidgetHeight}\n onChange={(e) => handleChange('defaultWidgetHeight', parseInt(e.target.value) || 4)}\n style={inputStyle}\n />\n <p style={descStyle}>Default height units for new widgets (2-12)</p>\n </div>\n <div>\n <label htmlFor=\"refresh-interval\" style={labelStyle}>\n Auto-Refresh Interval (seconds)\n </label>\n <select\n id=\"refresh-interval\"\n value={settings.defaultRefreshInterval}\n onChange={(e) => handleChange('defaultRefreshInterval', parseInt(e.target.value))}\n style={inputStyle}\n >\n <option value={0}>Disabled</option>\n <option value={10}>10 seconds</option>\n <option value={30}>30 seconds</option>\n <option value={60}>1 minute</option>\n <option value={300}>5 minutes</option>\n <option value={600}>10 minutes</option>\n </select>\n <p style={descStyle}>How often widgets refresh their data automatically</p>\n </div>\n </div>\n </EmphasisPanel>\n\n {/* Cache Settings */}\n <div style={sectionStyle}>\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 16,\n }}\n >\n Cache\n </h2>\n <div style={{ display: 'grid', gap: 16 }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div>\n <label style={labelStyle}>Enable Cache</label>\n <p style={descStyle}>Cache widget data to reduce API calls</p>\n </div>\n <button\n onClick={() => handleChange('cacheEnabled', !settings.cacheEnabled)}\n role=\"switch\"\n aria-checked={settings.cacheEnabled}\n aria-label=\"Enable cache\"\n style={{\n width: 44,\n height: 24,\n borderRadius: 12,\n backgroundColor: settings.cacheEnabled ? 'var(--color-action-primary-bg)' : 'var(--color-bg-sunken)',\n border: 'none',\n cursor: 'pointer',\n position: 'relative',\n transition: 'background-color 200ms',\n }}\n >\n <div\n style={{\n width: 18,\n height: 18,\n borderRadius: '50%',\n backgroundColor: 'white',\n position: 'absolute',\n top: 3,\n left: settings.cacheEnabled ? 23 : 3,\n transition: 'left 200ms',\n }}\n />\n </button>\n </div>\n {settings.cacheEnabled && (\n <div>\n <label htmlFor=\"cache-ttl\" style={labelStyle}>\n Cache TTL (seconds)\n </label>\n <input\n id=\"cache-ttl\"\n type=\"number\"\n min={30}\n max={3600}\n value={settings.cacheTTLSeconds}\n onChange={(e) => handleChange('cacheTTLSeconds', parseInt(e.target.value) || 300)}\n style={inputStyle}\n />\n <p style={descStyle}>How long cached data is considered fresh (30-3600)</p>\n </div>\n )}\n </div>\n </div>\n\n {/* Display Settings */}\n <div style={sectionStyle}>\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 16,\n }}\n >\n Display\n </h2>\n <div style={{ display: 'grid', gap: 16 }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div>\n <label style={labelStyle}>Show Widget Borders</label>\n <p style={descStyle}>Display borders around widgets in dashboards</p>\n </div>\n <button\n onClick={() => handleChange('showWidgetBorders', !settings.showWidgetBorders)}\n role=\"switch\"\n aria-checked={settings.showWidgetBorders}\n aria-label=\"Show widget borders\"\n style={{\n width: 44,\n height: 24,\n borderRadius: 12,\n backgroundColor: settings.showWidgetBorders\n ? 'var(--color-action-primary-bg)'\n : 'var(--color-bg-sunken)',\n border: 'none',\n cursor: 'pointer',\n position: 'relative',\n transition: 'background-color 200ms',\n }}\n >\n <div\n style={{\n width: 18,\n height: 18,\n borderRadius: '50%',\n backgroundColor: 'white',\n position: 'absolute',\n top: 3,\n left: settings.showWidgetBorders ? 23 : 3,\n transition: 'left 200ms',\n }}\n />\n </button>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div>\n <label style={labelStyle}>Compact Mode</label>\n <p style={descStyle}>Reduce padding and spacing for more content</p>\n </div>\n <button\n onClick={() => handleChange('compactMode', !settings.compactMode)}\n role=\"switch\"\n aria-checked={settings.compactMode}\n aria-label=\"Compact mode\"\n style={{\n width: 44,\n height: 24,\n borderRadius: 12,\n backgroundColor: settings.compactMode ? 'var(--color-action-primary-bg)' : 'var(--color-bg-sunken)',\n border: 'none',\n cursor: 'pointer',\n position: 'relative',\n transition: 'background-color 200ms',\n }}\n >\n <div\n style={{\n width: 18,\n height: 18,\n borderRadius: '50%',\n backgroundColor: 'white',\n position: 'absolute',\n top: 3,\n left: settings.compactMode ? 23 : 3,\n transition: 'left 200ms',\n }}\n />\n </button>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div>\n <label style={labelStyle}>Animations</label>\n <p style={descStyle}>Enable transition animations and effects</p>\n </div>\n <button\n onClick={() => handleChange('animationsEnabled', !settings.animationsEnabled)}\n role=\"switch\"\n aria-checked={settings.animationsEnabled}\n aria-label=\"Animations\"\n style={{\n width: 44,\n height: 24,\n borderRadius: 12,\n backgroundColor: settings.animationsEnabled\n ? 'var(--color-action-primary-bg)'\n : 'var(--color-bg-sunken)',\n border: 'none',\n cursor: 'pointer',\n position: 'relative',\n transition: 'background-color 200ms',\n }}\n >\n <div\n style={{\n width: 18,\n height: 18,\n borderRadius: '50%',\n backgroundColor: 'white',\n position: 'absolute',\n top: 3,\n left: settings.animationsEnabled ? 23 : 3,\n transition: 'left 200ms',\n }}\n />\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n});\n\nexport default SettingsPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"SettingsPage.js","names":[],"sources":["../../../src/bigconsole/pages/SettingsPage.tsx"],"sourcesContent":["/**\n * SettingsPage\n *\n * BigConsole workspace settings and preferences.\n * Settings are persisted to localStorage until a backend settings API is available.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport { AuroraBackground, GradientText, PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { Settings, Save, RotateCcw } from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface BigConsoleSettings {\n defaultRefreshInterval: number;\n defaultGridColumns: number;\n defaultWidgetHeight: number;\n cacheEnabled: boolean;\n cacheTTLSeconds: number;\n showWidgetBorders: boolean;\n compactMode: boolean;\n animationsEnabled: boolean;\n}\n\nconst STORAGE_KEY = 'bigconsole-settings';\n\nconst DEFAULT_SETTINGS: BigConsoleSettings = {\n defaultRefreshInterval: 0,\n defaultGridColumns: 12,\n defaultWidgetHeight: 4,\n cacheEnabled: true,\n cacheTTLSeconds: 300,\n showWidgetBorders: true,\n compactMode: false,\n animationsEnabled: true,\n};\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\nfunction loadSettings(): BigConsoleSettings {\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored) {\n return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) };\n }\n } catch {\n // Fall through to defaults\n }\n return { ...DEFAULT_SETTINGS };\n}\n\nfunction saveSettings(settings: BigConsoleSettings): void {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const SettingsPage: FC = memo(function SettingsPage() {\n const [settings, setSettings] = useState<BigConsoleSettings>(loadSettings);\n const [saved, setSaved] = useState(false);\n\n useEffect(() => {\n if (saved) {\n const timer = setTimeout(() => setSaved(false), 2000);\n return () => clearTimeout(timer);\n }\n }, [saved]);\n\n const handleChange = useCallback(<K extends keyof BigConsoleSettings>(key: K, value: BigConsoleSettings[K]) => {\n setSettings((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const handleSave = useCallback(() => {\n saveSettings(settings);\n setSaved(true);\n }, [settings]);\n\n const handleReset = useCallback(() => {\n setSettings({ ...DEFAULT_SETTINGS });\n saveSettings(DEFAULT_SETTINGS);\n setSaved(true);\n }, []);\n\n const inputStyle: React.CSSProperties = {\n width: '100%',\n padding: '8px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-canvas)',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n outline: 'none',\n };\n\n const labelStyle: React.CSSProperties = {\n display: 'block',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n marginBottom: 4,\n };\n\n const descStyle: React.CSSProperties = {\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginTop: 4,\n };\n\n const sectionStyle: React.CSSProperties = {\n backgroundColor: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 'var(--radius-card)',\n padding: 'var(--space-cardPadding)',\n marginBottom: 'var(--space-sectionGap)',\n boxShadow: 'var(--shadow-pop)',\n };\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n <div style={{ padding: 'var(--space-pagePadding)', maxWidth: 720, margin: '0 auto' }}>\n {/* Header */}\n <div\n className=\"relative overflow-hidden rounded-card border border-border-seam bg-gloss-card p-6\"\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: 'var(--space-sectionGap)',\n flexWrap: 'wrap',\n gap: 'var(--space-formGap)',\n }}\n >\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Settings size={24} style={{ color: 'var(--color-text-primary)' }} />\n <h1\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n color: 'var(--color-text-primary)',\n }}\n >\n <GradientText>Settings</GradientText>\n </h1>\n </div>\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-secondary)', marginTop: 4 }}>\n Configure BigConsole preferences\n </p>\n </div>\n <div style={{ display: 'flex', gap: 8 }}>\n <button\n onClick={handleReset}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '8px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-secondary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n <RotateCcw size={14} /> Reset\n </button>\n <button\n onClick={handleSave}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '8px 16px',\n borderRadius: 'var(--radius-button)',\n backgroundColor: saved ? 'var(--color-status-success-bg)' : 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n fontWeight: 'var(--font-weight-medium)',\n fontSize: 'var(--font-size-body-sm)',\n border: 'none',\n cursor: 'pointer',\n transition: 'background-color 200ms',\n }}\n >\n <Save size={14} /> {saved ? 'Saved locally' : 'Save locally'}\n </button>\n </div>\n </div>\n\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Settings control the defaults BigConsole applies everywhere — how new dashboards are laid out, how\n aggressively widget data is cached, and how the UI is displayed. Tune these once to match how your team works;\n they apply across every dashboard you build. These preferences are saved locally in this browser until the\n backend settings API is available.\n </PagePurpose>\n\n {/* Dashboard Defaults — primary emphasis zone */}\n <EmphasisPanel className=\"mb-[var(--space-sectionGap)] p-[var(--space-cardPadding)]\">\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 16,\n }}\n >\n Dashboard Defaults\n </h2>\n <div style={{ display: 'grid', gap: 16 }}>\n <div>\n <label htmlFor=\"grid-cols\" style={labelStyle}>\n Grid Columns\n </label>\n <input\n id=\"grid-cols\"\n type=\"number\"\n min={4}\n max={24}\n value={settings.defaultGridColumns}\n onChange={(e) => handleChange('defaultGridColumns', parseInt(e.target.value) || 12)}\n style={inputStyle}\n />\n <p style={descStyle}>Number of columns in the dashboard grid (4-24)</p>\n </div>\n <div>\n <label htmlFor=\"widget-height\" style={labelStyle}>\n Default Widget Height\n </label>\n <input\n id=\"widget-height\"\n type=\"number\"\n min={2}\n max={12}\n value={settings.defaultWidgetHeight}\n onChange={(e) => handleChange('defaultWidgetHeight', parseInt(e.target.value) || 4)}\n style={inputStyle}\n />\n <p style={descStyle}>Default height units for new widgets (2-12)</p>\n </div>\n <div>\n <label htmlFor=\"refresh-interval\" style={labelStyle}>\n Auto-Refresh Interval (seconds)\n </label>\n <select\n id=\"refresh-interval\"\n value={settings.defaultRefreshInterval}\n onChange={(e) => handleChange('defaultRefreshInterval', parseInt(e.target.value))}\n style={inputStyle}\n >\n <option value={0}>Disabled</option>\n <option value={10}>10 seconds</option>\n <option value={30}>30 seconds</option>\n <option value={60}>1 minute</option>\n <option value={300}>5 minutes</option>\n <option value={600}>10 minutes</option>\n </select>\n <p style={descStyle}>How often widgets refresh their data automatically</p>\n </div>\n </div>\n </EmphasisPanel>\n\n {/* Cache Settings */}\n <div style={sectionStyle}>\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 16,\n }}\n >\n Cache\n </h2>\n <div style={{ display: 'grid', gap: 16 }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div>\n <label style={labelStyle}>Enable Cache</label>\n <p style={descStyle}>Cache widget data to reduce API calls</p>\n </div>\n <button\n onClick={() => handleChange('cacheEnabled', !settings.cacheEnabled)}\n role=\"switch\"\n aria-checked={settings.cacheEnabled}\n aria-label=\"Enable cache\"\n style={{\n width: 44,\n height: 24,\n borderRadius: 12,\n backgroundColor: settings.cacheEnabled ? 'var(--color-action-primary-bg)' : 'var(--color-bg-sunken)',\n border: 'none',\n cursor: 'pointer',\n position: 'relative',\n transition: 'background-color 200ms',\n }}\n >\n <div\n style={{\n width: 18,\n height: 18,\n borderRadius: '50%',\n backgroundColor: 'white',\n position: 'absolute',\n top: 3,\n left: settings.cacheEnabled ? 23 : 3,\n transition: 'left 200ms',\n }}\n />\n </button>\n </div>\n {settings.cacheEnabled && (\n <div>\n <label htmlFor=\"cache-ttl\" style={labelStyle}>\n Cache TTL (seconds)\n </label>\n <input\n id=\"cache-ttl\"\n type=\"number\"\n min={30}\n max={3600}\n value={settings.cacheTTLSeconds}\n onChange={(e) => handleChange('cacheTTLSeconds', parseInt(e.target.value) || 300)}\n style={inputStyle}\n />\n <p style={descStyle}>How long cached data is considered fresh (30-3600)</p>\n </div>\n )}\n </div>\n </div>\n\n {/* Display Settings */}\n <div style={sectionStyle}>\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 16,\n }}\n >\n Display\n </h2>\n <div style={{ display: 'grid', gap: 16 }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div>\n <label style={labelStyle}>Show Widget Borders</label>\n <p style={descStyle}>Display borders around widgets in dashboards</p>\n </div>\n <button\n onClick={() => handleChange('showWidgetBorders', !settings.showWidgetBorders)}\n role=\"switch\"\n aria-checked={settings.showWidgetBorders}\n aria-label=\"Show widget borders\"\n style={{\n width: 44,\n height: 24,\n borderRadius: 12,\n backgroundColor: settings.showWidgetBorders\n ? 'var(--color-action-primary-bg)'\n : 'var(--color-bg-sunken)',\n border: 'none',\n cursor: 'pointer',\n position: 'relative',\n transition: 'background-color 200ms',\n }}\n >\n <div\n style={{\n width: 18,\n height: 18,\n borderRadius: '50%',\n backgroundColor: 'white',\n position: 'absolute',\n top: 3,\n left: settings.showWidgetBorders ? 23 : 3,\n transition: 'left 200ms',\n }}\n />\n </button>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div>\n <label style={labelStyle}>Compact Mode</label>\n <p style={descStyle}>Reduce padding and spacing for more content</p>\n </div>\n <button\n onClick={() => handleChange('compactMode', !settings.compactMode)}\n role=\"switch\"\n aria-checked={settings.compactMode}\n aria-label=\"Compact mode\"\n style={{\n width: 44,\n height: 24,\n borderRadius: 12,\n backgroundColor: settings.compactMode ? 'var(--color-action-primary-bg)' : 'var(--color-bg-sunken)',\n border: 'none',\n cursor: 'pointer',\n position: 'relative',\n transition: 'background-color 200ms',\n }}\n >\n <div\n style={{\n width: 18,\n height: 18,\n borderRadius: '50%',\n backgroundColor: 'white',\n position: 'absolute',\n top: 3,\n left: settings.compactMode ? 23 : 3,\n transition: 'left 200ms',\n }}\n />\n </button>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div>\n <label style={labelStyle}>Animations</label>\n <p style={descStyle}>Enable transition animations and effects</p>\n </div>\n <button\n onClick={() => handleChange('animationsEnabled', !settings.animationsEnabled)}\n role=\"switch\"\n aria-checked={settings.animationsEnabled}\n aria-label=\"Animations\"\n style={{\n width: 44,\n height: 24,\n borderRadius: 12,\n backgroundColor: settings.animationsEnabled\n ? 'var(--color-action-primary-bg)'\n : 'var(--color-bg-sunken)',\n border: 'none',\n cursor: 'pointer',\n position: 'relative',\n transition: 'background-color 200ms',\n }}\n >\n <div\n style={{\n width: 18,\n height: 18,\n borderRadius: '50%',\n backgroundColor: 'white',\n position: 'absolute',\n top: 3,\n left: settings.animationsEnabled ? 23 : 3,\n transition: 'left 200ms',\n }}\n />\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n});\n\nexport default SettingsPage;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.707.4",
3
+ "version": "2026.708.1",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [