@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.
- package/dist/bigconsole/hooks/useExportDataSink.js +1 -0
- package/dist/bigconsole/hooks/useExportDataSink.js.map +1 -1
- package/dist/bigconsole/hooks/useImportDataSink.js +101 -85
- package/dist/bigconsole/hooks/useImportDataSink.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js +30 -22
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/SettingsPage.js +2 -2
- package/dist/bigconsole/pages/SettingsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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;;
|
|
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) =>
|
|
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] =
|
|
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
|
|
37
|
+
function d(e) {
|
|
25
38
|
try {
|
|
26
|
-
let t = JSON.parse(e);
|
|
27
|
-
return
|
|
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
|
|
45
|
+
function f(e) {
|
|
33
46
|
let t = [];
|
|
34
47
|
try {
|
|
35
|
-
let n = JSON.parse(e);
|
|
36
|
-
if (
|
|
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
|
-
}), !
|
|
52
|
+
}), !i) return t.push({
|
|
40
53
|
code: "MISSING_DATASINK",
|
|
41
|
-
message: "
|
|
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
|
-
|
|
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
|
|
54
|
-
name:
|
|
55
|
-
description:
|
|
56
|
-
key:
|
|
57
|
-
status:
|
|
58
|
-
setupState:
|
|
59
|
-
exportedAt:
|
|
60
|
-
exportedFrom:
|
|
61
|
-
schemaVersion:
|
|
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:
|
|
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
|
|
80
|
-
let [
|
|
81
|
-
let t =
|
|
82
|
-
return
|
|
83
|
-
}, []),
|
|
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
|
-
|
|
86
|
-
let a =
|
|
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)
|
|
89
|
-
if (
|
|
90
|
-
|
|
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:
|
|
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:
|
|
112
|
+
id: m,
|
|
99
113
|
version: e.version,
|
|
100
114
|
name: e.name,
|
|
101
115
|
description: o.description || void 0,
|
|
102
|
-
|
|
116
|
+
...s && { data: u(o.data) },
|
|
117
|
+
ttl: o.ttl ?? void 0,
|
|
103
118
|
fieldMaskingEnabled: o.fieldMaskingEnabled ?? !1,
|
|
104
|
-
fieldMaskingRules:
|
|
105
|
-
refresh:
|
|
106
|
-
exports:
|
|
107
|
-
alerts:
|
|
108
|
-
quota:
|
|
109
|
-
metadata:
|
|
110
|
-
}, r = (await
|
|
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 && (
|
|
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
|
-
|
|
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:
|
|
142
|
+
dataSink: _,
|
|
128
143
|
originalUpdated: !1,
|
|
129
|
-
originalDataSink:
|
|
144
|
+
originalDataSink: _,
|
|
130
145
|
alreadyExists: !0
|
|
131
146
|
};
|
|
132
|
-
return
|
|
147
|
+
return y(t), t;
|
|
133
148
|
}
|
|
134
149
|
let t = {
|
|
135
150
|
success: !0,
|
|
136
|
-
dataSink:
|
|
137
|
-
originalUpdated:
|
|
138
|
-
originalDataSink:
|
|
151
|
+
dataSink: _,
|
|
152
|
+
originalUpdated: g,
|
|
153
|
+
originalDataSink: _,
|
|
139
154
|
alreadyExists: !1
|
|
140
155
|
};
|
|
141
|
-
return
|
|
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
|
|
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:
|
|
151
|
-
|
|
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:
|
|
154
|
-
refresh:
|
|
155
|
-
exports:
|
|
156
|
-
alerts:
|
|
157
|
-
quota:
|
|
158
|
-
metadata:
|
|
159
|
-
},
|
|
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:
|
|
177
|
+
variables: { input: v }
|
|
162
178
|
})).data?.createDataSink;
|
|
163
|
-
if (
|
|
179
|
+
if (x) {
|
|
164
180
|
let e = {
|
|
165
181
|
success: !0,
|
|
166
182
|
dataSink: {
|
|
167
|
-
id:
|
|
168
|
-
name:
|
|
169
|
-
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
|
|
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
|
-
|
|
195
|
+
h(t);
|
|
180
196
|
let n = {
|
|
181
197
|
success: !1,
|
|
182
198
|
errorMessage: t.message
|
|
183
199
|
};
|
|
184
|
-
return
|
|
200
|
+
return y(n), n;
|
|
185
201
|
} finally {
|
|
186
|
-
|
|
202
|
+
p(!1);
|
|
187
203
|
}
|
|
188
|
-
}, [
|
|
204
|
+
}, [b]);
|
|
189
205
|
return {
|
|
190
|
-
loading:
|
|
191
|
-
error:
|
|
192
|
-
validationResult:
|
|
193
|
-
importResult:
|
|
194
|
-
validateImport:
|
|
195
|
-
importDataSink:
|
|
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
|
|
221
|
+
return await C(n, t);
|
|
206
222
|
} catch (e) {
|
|
207
|
-
return
|
|
223
|
+
return h(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to import from file")), null;
|
|
208
224
|
}
|
|
209
|
-
}, [
|
|
210
|
-
extractDataSinkId:
|
|
225
|
+
}, [C]),
|
|
226
|
+
extractDataSinkId: S,
|
|
211
227
|
clearState: r(() => {
|
|
212
|
-
|
|
228
|
+
h(null), _(null), y(null);
|
|
213
229
|
}, [])
|
|
214
230
|
};
|
|
215
231
|
}
|
|
216
232
|
//#endregion
|
|
217
|
-
export {
|
|
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":"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"}
|
|
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", {
|
|
202
|
-
style: {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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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"}
|