retold-facto 0.0.4

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.
Files changed (92) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.dockerignore +8 -0
  3. package/.quackage.json +19 -0
  4. package/Dockerfile +26 -0
  5. package/bin/retold-facto.js +909 -0
  6. package/examples/facto-government-data.sqlite +0 -0
  7. package/examples/government-data-catalog.json +137 -0
  8. package/examples/government-data-loader.js +1432 -0
  9. package/package.json +91 -0
  10. package/scripts/facto-download.js +425 -0
  11. package/source/Retold-Facto.js +1042 -0
  12. package/source/services/Retold-Facto-BeaconProvider.js +511 -0
  13. package/source/services/Retold-Facto-CatalogManager.js +1252 -0
  14. package/source/services/Retold-Facto-DataLakeService.js +1642 -0
  15. package/source/services/Retold-Facto-DatasetManager.js +417 -0
  16. package/source/services/Retold-Facto-IngestEngine.js +1315 -0
  17. package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
  18. package/source/services/Retold-Facto-RecordManager.js +360 -0
  19. package/source/services/Retold-Facto-SchemaManager.js +1110 -0
  20. package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
  21. package/source/services/Retold-Facto-SourceManager.js +730 -0
  22. package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
  23. package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
  24. package/source/services/web-app/codemirror-entry.js +7 -0
  25. package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
  26. package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
  27. package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
  28. package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
  29. package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
  30. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
  31. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
  32. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
  33. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
  34. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
  35. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
  36. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
  37. package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
  38. package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
  39. package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
  40. package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
  41. package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
  42. package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
  43. package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
  44. package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
  45. package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
  46. package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
  47. package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
  48. package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
  49. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
  50. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
  51. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
  52. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
  53. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
  54. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
  55. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
  56. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
  57. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
  58. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
  59. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
  60. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
  61. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
  62. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
  63. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
  64. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
  65. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
  66. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
  67. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
  68. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
  69. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
  70. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
  71. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
  72. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
  73. package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
  74. package/source/services/web-app/web/chart.min.js +20 -0
  75. package/source/services/web-app/web/codemirror-bundle.js +30099 -0
  76. package/source/services/web-app/web/css/facto-themes.css +467 -0
  77. package/source/services/web-app/web/css/facto.css +502 -0
  78. package/source/services/web-app/web/index.html +28 -0
  79. package/source/services/web-app/web/retold-facto.js +12138 -0
  80. package/source/services/web-app/web/retold-facto.js.map +1 -0
  81. package/source/services/web-app/web/retold-facto.min.js +2 -0
  82. package/source/services/web-app/web/retold-facto.min.js.map +1 -0
  83. package/source/services/web-app/web/simple/index.html +17 -0
  84. package/test/Facto_Browser_Integration_tests.js +798 -0
  85. package/test/RetoldFacto_tests.js +4117 -0
  86. package/test/fixtures/weather-readings.csv +17 -0
  87. package/test/fixtures/weather-stations.csv +9 -0
  88. package/test/model/MeadowModel-Extended.json +8497 -0
  89. package/test/model/MeadowModel-PICT.json +1 -0
  90. package/test/model/MeadowModel.json +1355 -0
  91. package/test/model/ddl/Facto.ddl +225 -0
  92. package/test/model/fable-configuration.json +14 -0
@@ -0,0 +1,526 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ class FactoCatalogView extends libPictView
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+ }
9
+
10
+ onAfterRender()
11
+ {
12
+ // Load catalog entries from API on first render
13
+ this.pict.providers.Facto.loadCatalogEntries().then(
14
+ () =>
15
+ {
16
+ this.refreshList();
17
+ }).catch(
18
+ (pError) =>
19
+ {
20
+ this.pict.views['Pict-Section-Modal'].toast('Error loading catalog: ' + pError.message, {type: 'error'});
21
+ });
22
+ }
23
+
24
+ refreshList()
25
+ {
26
+ let tmpContainer = document.getElementById('facto-catalog-list');
27
+ if (!tmpContainer) return;
28
+
29
+ let tmpEntries = this.pict.AppData.Facto.CatalogEntries;
30
+ if (!tmpEntries || tmpEntries.length === 0)
31
+ {
32
+ tmpContainer.innerHTML = '<p style="color:#888; font-style:italic;">No catalog entries yet. Add sources to your research catalog.</p>';
33
+ return;
34
+ }
35
+
36
+ let tmpHtml = '<table><thead><tr><th>ID</th><th>Agency</th><th>Name</th><th>Type</th><th>Category</th><th>Region</th><th>Verified</th><th>Actions</th></tr></thead><tbody>';
37
+ for (let i = 0; i < tmpEntries.length; i++)
38
+ {
39
+ let tmpEntry = tmpEntries[i];
40
+ let tmpVerified = tmpEntry.Verified ? '<span style="color:#28a745;">&#10003;</span>' : '<span style="color:#ccc;">&#10007;</span>';
41
+ tmpHtml += '<tr>';
42
+ tmpHtml += '<td>' + (tmpEntry.IDSourceCatalogEntry || '') + '</td>';
43
+ tmpHtml += '<td>' + (tmpEntry.Agency || '') + '</td>';
44
+ tmpHtml += '<td>' + (tmpEntry.Name || '') + '</td>';
45
+ tmpHtml += '<td>' + (tmpEntry.Type || '') + '</td>';
46
+ tmpHtml += '<td>' + (tmpEntry.Category || '') + '</td>';
47
+ tmpHtml += '<td>' + (tmpEntry.Region || '') + '</td>';
48
+ tmpHtml += '<td style="text-align:center;">' + tmpVerified + '</td>';
49
+ tmpHtml += '<td>';
50
+ tmpHtml += '<button class="primary" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Catalog\'].viewEntry(' + tmpEntry.IDSourceCatalogEntry + ')">Datasets</button> ';
51
+ tmpHtml += '<button class="danger" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Catalog\'].deleteEntry(' + tmpEntry.IDSourceCatalogEntry + ')">Delete</button>';
52
+ tmpHtml += '</td>';
53
+ tmpHtml += '</tr>';
54
+ }
55
+ tmpHtml += '</tbody></table>';
56
+ tmpContainer.innerHTML = tmpHtml;
57
+ }
58
+
59
+ searchCatalog()
60
+ {
61
+ let tmpQuery = this.pict.providers.FactoUI.getVal('facto-catalog-search');
62
+ if (!tmpQuery)
63
+ {
64
+ // Reload all entries
65
+ this.pict.providers.Facto.loadCatalogEntries().then(
66
+ () =>
67
+ {
68
+ this.refreshList();
69
+ });
70
+ return;
71
+ }
72
+
73
+ this.pict.providers.Facto.searchCatalog(tmpQuery).then(
74
+ () =>
75
+ {
76
+ this.refreshList();
77
+ }).catch(
78
+ (pError) =>
79
+ {
80
+ this.pict.views['Pict-Section-Modal'].toast('Search error: ' + pError.message, {type: 'error'});
81
+ });
82
+ }
83
+
84
+ addEntry()
85
+ {
86
+ let tmpAgency = this.pict.providers.FactoUI.getVal('facto-catalog-agency');
87
+ let tmpName = this.pict.providers.FactoUI.getVal('facto-catalog-name');
88
+ let tmpType = this.pict.providers.FactoUI.getVal('facto-catalog-type');
89
+ let tmpURL = this.pict.providers.FactoUI.getVal('facto-catalog-url');
90
+ let tmpProtocol = this.pict.providers.FactoUI.getVal('facto-catalog-protocol');
91
+ let tmpCategory = this.pict.providers.FactoUI.getVal('facto-catalog-category');
92
+ let tmpRegion = this.pict.providers.FactoUI.getVal('facto-catalog-region');
93
+ let tmpUpdateFrequency = this.pict.providers.FactoUI.getVal('facto-catalog-frequency');
94
+ let tmpDescription = this.pict.providers.FactoUI.getVal('facto-catalog-description');
95
+
96
+ if (!tmpAgency && !tmpName)
97
+ {
98
+ this.pict.views['Pict-Section-Modal'].toast('Agency or Name is required', {type: 'warning'});
99
+ return;
100
+ }
101
+
102
+ this.pict.providers.Facto.createCatalogEntry(
103
+ {
104
+ Agency: tmpAgency,
105
+ Name: tmpName,
106
+ Type: tmpType,
107
+ URL: tmpURL,
108
+ Protocol: tmpProtocol,
109
+ Category: tmpCategory,
110
+ Region: tmpRegion,
111
+ UpdateFrequency: tmpUpdateFrequency,
112
+ Description: tmpDescription
113
+ }).then(
114
+ (pResponse) =>
115
+ {
116
+ if (pResponse && pResponse.Success)
117
+ {
118
+ this.pict.views['Pict-Section-Modal'].toast('Catalog entry created: ' + (tmpAgency || tmpName), {type: 'success'});
119
+ // Clear form
120
+ let tmpFields = ['agency', 'name', 'url', 'description'];
121
+ for (let i = 0; i < tmpFields.length; i++)
122
+ {
123
+ let tmpEl = document.getElementById('facto-catalog-' + tmpFields[i]);
124
+ if (tmpEl) tmpEl.value = '';
125
+ }
126
+ // Reload list
127
+ return this.pict.providers.Facto.loadCatalogEntries();
128
+ }
129
+ else
130
+ {
131
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + ((pResponse && pResponse.Error) || 'Unknown error'), {type: 'error'});
132
+ }
133
+ }).then(
134
+ () =>
135
+ {
136
+ this.refreshList();
137
+ }).catch(
138
+ (pError) =>
139
+ {
140
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
141
+ });
142
+ }
143
+
144
+ async deleteEntry(pIDEntry)
145
+ {
146
+ let tmpConfirmed = await this.pict.views['Pict-Section-Modal'].confirm('Remove this catalog entry?', { title: 'Remove Entry', confirmLabel: 'Remove', dangerous: true });
147
+ if (!tmpConfirmed) return;
148
+
149
+ this.pict.providers.Facto.deleteCatalogEntry(pIDEntry).then(
150
+ () =>
151
+ {
152
+ return this.pict.providers.Facto.loadCatalogEntries();
153
+ }).then(
154
+ () =>
155
+ {
156
+ this.refreshList();
157
+ this.pict.views['Pict-Section-Modal'].toast('Entry removed', {type: 'success'});
158
+ }).catch(
159
+ (pError) =>
160
+ {
161
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
162
+ });
163
+ }
164
+
165
+ viewEntry(pIDEntry)
166
+ {
167
+ let tmpDetailContainer = document.getElementById('facto-catalog-detail');
168
+ if (!tmpDetailContainer) return;
169
+
170
+ this.pict.providers.Facto.loadCatalogEntryDatasets(pIDEntry).then(
171
+ (pResponse) =>
172
+ {
173
+ let tmpDatasets = (pResponse && pResponse.Datasets) ? pResponse.Datasets : [];
174
+ let tmpHtml = '<h3 style="margin-bottom:8px; font-size:1em; color:#444;">Dataset Definitions for Entry #' + pIDEntry + '</h3>';
175
+
176
+ if (tmpDatasets.length === 0)
177
+ {
178
+ tmpHtml += '<p style="color:#888; font-style:italic; margin-bottom:8px;">No dataset definitions yet.</p>';
179
+ }
180
+ else
181
+ {
182
+ tmpHtml += '<table><thead><tr><th>ID</th><th>Name</th><th>Format</th><th>Endpoint URL</th><th>Version Policy</th><th>Status</th><th>Actions</th></tr></thead><tbody>';
183
+ for (let i = 0; i < tmpDatasets.length; i++)
184
+ {
185
+ let tmpDS = tmpDatasets[i];
186
+ let tmpStatusLabel = tmpDS.Provisioned
187
+ ? '<span style="color:#28a745;">Provisioned (Source #' + tmpDS.IDSource + ', Dataset #' + tmpDS.IDDataset + ')</span>'
188
+ : '<span style="color:#888;">Not provisioned</span>';
189
+ let tmpActionBtn = '';
190
+ if (tmpDS.Provisioned)
191
+ {
192
+ tmpActionBtn = '<button class="primary" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Catalog\'].fetchDataset(' + tmpDS.IDCatalogDatasetDefinition + ', ' + pIDEntry + ')">Fetch</button>';
193
+ }
194
+ else
195
+ {
196
+ tmpActionBtn = '<button class="success" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Catalog\'].provisionDataset(' + tmpDS.IDCatalogDatasetDefinition + ', ' + pIDEntry + ')">Provision</button>';
197
+ }
198
+ tmpHtml += '<tr>';
199
+ tmpHtml += '<td>' + (tmpDS.IDCatalogDatasetDefinition || '') + '</td>';
200
+ tmpHtml += '<td>' + (tmpDS.Name || '') + '</td>';
201
+ tmpHtml += '<td>' + (tmpDS.Format || '') + '</td>';
202
+ tmpHtml += '<td style="max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">' + (tmpDS.EndpointURL || '') + '</td>';
203
+ tmpHtml += '<td>' + (tmpDS.VersionPolicy || 'Append') + '</td>';
204
+ tmpHtml += '<td>' + tmpStatusLabel + '</td>';
205
+ tmpHtml += '<td>' + tmpActionBtn + '</td>';
206
+ tmpHtml += '</tr>';
207
+ }
208
+ tmpHtml += '</tbody></table>';
209
+ }
210
+
211
+ // Add dataset definition form
212
+ tmpHtml += '<h4 style="margin-top:12px; margin-bottom:8px; font-size:0.95em; color:#555;">Add Dataset Definition</h4>';
213
+ tmpHtml += '<div class="inline-group">';
214
+ tmpHtml += '<div><label for="facto-catds-name">Name</label><input type="text" id="facto-catds-name" placeholder="e.g. Monthly Earthquake Feed"></div>';
215
+ tmpHtml += '<div><label for="facto-catds-format">Format</label>';
216
+ tmpHtml += '<select id="facto-catds-format"><option value="csv">CSV</option><option value="json">JSON</option><option value="xml">XML</option><option value="geojson">GeoJSON</option><option value="other">Other</option></select></div>';
217
+ tmpHtml += '</div>';
218
+ tmpHtml += '<div class="inline-group">';
219
+ tmpHtml += '<div><label for="facto-catds-endpoint">Endpoint URL</label><input type="text" id="facto-catds-endpoint" placeholder="https://api.example.gov/data.csv"></div>';
220
+ tmpHtml += '<div><label for="facto-catds-versionpolicy">Version Policy</label>';
221
+ tmpHtml += '<select id="facto-catds-versionpolicy"><option value="Append">Append</option><option value="Replace">Replace</option></select></div>';
222
+ tmpHtml += '</div>';
223
+ tmpHtml += '<div><label for="facto-catds-description">Description</label><input type="text" id="facto-catds-description" placeholder="Description of the dataset"></div>';
224
+ tmpHtml += '<button class="primary" onclick="pict.views[\'Facto-Catalog\'].addDatasetDefinition(' + pIDEntry + ')">Add Dataset Definition</button>';
225
+ tmpHtml += '<button class="secondary" onclick="document.getElementById(\'facto-catalog-detail\').innerHTML=\'\';">Close</button>';
226
+
227
+ tmpDetailContainer.innerHTML = tmpHtml;
228
+ }).catch(
229
+ (pError) =>
230
+ {
231
+ tmpDetailContainer.innerHTML = '<p style="color:#dc3545;">Error loading datasets: ' + pError.message + '</p>';
232
+ });
233
+ }
234
+
235
+ addDatasetDefinition(pIDEntry)
236
+ {
237
+ let tmpName = this.pict.providers.FactoUI.getVal('facto-catds-name');
238
+ let tmpFormat = this.pict.providers.FactoUI.getVal('facto-catds-format');
239
+ let tmpEndpointURL = this.pict.providers.FactoUI.getVal('facto-catds-endpoint');
240
+ let tmpVersionPolicy = this.pict.providers.FactoUI.getVal('facto-catds-versionpolicy') || 'Append';
241
+ let tmpDescription = this.pict.providers.FactoUI.getVal('facto-catds-description');
242
+
243
+ if (!tmpName)
244
+ {
245
+ this.pict.views['Pict-Section-Modal'].toast('Dataset name is required', {type: 'warning'});
246
+ return;
247
+ }
248
+
249
+ this.pict.providers.Facto.addCatalogDataset(pIDEntry,
250
+ {
251
+ Name: tmpName,
252
+ Format: tmpFormat,
253
+ EndpointURL: tmpEndpointURL,
254
+ VersionPolicy: tmpVersionPolicy,
255
+ Description: tmpDescription
256
+ }).then(
257
+ (pResponse) =>
258
+ {
259
+ if (pResponse && pResponse.Success)
260
+ {
261
+ this.pict.views['Pict-Section-Modal'].toast('Dataset definition added: ' + tmpName, {type: 'success'});
262
+ this.viewEntry(pIDEntry);
263
+ }
264
+ else
265
+ {
266
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + ((pResponse && pResponse.Error) || 'Unknown error'), {type: 'error'});
267
+ }
268
+ }).catch(
269
+ (pError) =>
270
+ {
271
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
272
+ });
273
+ }
274
+
275
+ provisionDataset(pIDCatalogDataset, pIDEntry)
276
+ {
277
+ this.pict.views['Pict-Section-Modal'].toast('Provisioning...', {type: 'info'});
278
+
279
+ this.pict.providers.Facto.provisionCatalogDataset(pIDCatalogDataset).then(
280
+ (pResponse) =>
281
+ {
282
+ if (pResponse && pResponse.Success)
283
+ {
284
+ let tmpMsg = 'Provisioned! Source #' + pResponse.Source.IDSource + ', Dataset #' + pResponse.Dataset.IDDataset;
285
+ this.pict.views['Pict-Section-Modal'].toast(tmpMsg, {type: 'success'});
286
+ this.viewEntry(pIDEntry);
287
+ // Refresh sibling views via FactoUIProvider coordination
288
+ this.pict.providers.FactoUI.refreshDataViews(['sources', 'datasets']);
289
+ }
290
+ else
291
+ {
292
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + ((pResponse && pResponse.Error) || 'Unknown error'), {type: 'error'});
293
+ }
294
+ }).catch(
295
+ (pError) =>
296
+ {
297
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
298
+ });
299
+ }
300
+
301
+ fetchDataset(pIDCatalogDataset, pIDEntry)
302
+ {
303
+ this.pict.views['Pict-Section-Modal'].toast('Fetching data from endpoint...', {type: 'info'});
304
+
305
+ this.pict.providers.Facto.fetchCatalogDataset(pIDCatalogDataset).then(
306
+ (pResponse) =>
307
+ {
308
+ if (pResponse && pResponse.Success)
309
+ {
310
+ let tmpMsg = 'Fetched! ' + pResponse.Ingested + ' records ingested (v' + pResponse.DatasetVersion + ', ' + pResponse.Format + ')';
311
+ if (pResponse.IsDuplicate)
312
+ {
313
+ tmpMsg += ' [duplicate content detected]';
314
+ }
315
+ this.pict.views['Pict-Section-Modal'].toast(tmpMsg, {type: 'success'});
316
+ this.viewEntry(pIDEntry);
317
+ // Refresh records view via FactoUIProvider coordination
318
+ this.pict.providers.FactoUI.refreshDataViews(['records']);
319
+ }
320
+ else
321
+ {
322
+ this.pict.views['Pict-Section-Modal'].toast('Fetch error: ' + ((pResponse && pResponse.Error) || 'Unknown error'), {type: 'error'});
323
+ }
324
+ }).catch(
325
+ (pError) =>
326
+ {
327
+ this.pict.views['Pict-Section-Modal'].toast('Fetch error: ' + pError.message, {type: 'error'});
328
+ });
329
+ }
330
+
331
+ importCatalog()
332
+ {
333
+ let tmpTextArea = document.getElementById('facto-catalog-import-json');
334
+ if (!tmpTextArea || !tmpTextArea.value)
335
+ {
336
+ this.pict.views['Pict-Section-Modal'].toast('Paste JSON to import', {type: 'warning'});
337
+ return;
338
+ }
339
+
340
+ let tmpEntries;
341
+ try
342
+ {
343
+ tmpEntries = JSON.parse(tmpTextArea.value);
344
+ }
345
+ catch (pParseError)
346
+ {
347
+ this.pict.views['Pict-Section-Modal'].toast('Invalid JSON: ' + pParseError.message, {type: 'error'});
348
+ return;
349
+ }
350
+
351
+ this.pict.providers.Facto.importCatalog(tmpEntries).then(
352
+ (pResponse) =>
353
+ {
354
+ if (pResponse && pResponse.Success)
355
+ {
356
+ this.pict.views['Pict-Section-Modal'].toast('Imported ' + pResponse.EntriesCreated + ' entries with ' + pResponse.DatasetsCreated + ' datasets', {type: 'success'});
357
+ tmpTextArea.value = '';
358
+ return this.pict.providers.Facto.loadCatalogEntries();
359
+ }
360
+ else
361
+ {
362
+ let tmpError = (pResponse && pResponse.Error) || 'Unknown';
363
+ if (tmpError === 'Unknown')
364
+ {
365
+ try { tmpError = 'Unexpected response: ' + JSON.stringify(pResponse).substring(0, 300); }
366
+ catch(e) { /* ignore */ }
367
+ }
368
+ this.pict.views['Pict-Section-Modal'].toast('Import error: ' + tmpError, {type: 'error'});
369
+ }
370
+ }).then(
371
+ () =>
372
+ {
373
+ this.refreshList();
374
+ }).catch(
375
+ (pError) =>
376
+ {
377
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
378
+ });
379
+ }
380
+
381
+ exportCatalog()
382
+ {
383
+ this.pict.providers.Facto.exportCatalog().then(
384
+ (pResponse) =>
385
+ {
386
+ let tmpTextArea = document.getElementById('facto-catalog-import-json');
387
+ if (tmpTextArea)
388
+ {
389
+ tmpTextArea.value = JSON.stringify(pResponse && pResponse.Entries ? pResponse.Entries : pResponse, null, 2);
390
+ }
391
+ this.pict.views['Pict-Section-Modal'].toast('Catalog exported to JSON text area', {type: 'success'});
392
+ }).catch(
393
+ (pError) =>
394
+ {
395
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
396
+ });
397
+ }
398
+ }
399
+
400
+ module.exports = FactoCatalogView;
401
+
402
+ module.exports.default_configuration =
403
+ {
404
+ ViewIdentifier: 'Facto-Catalog',
405
+ DefaultRenderable: 'Facto-Catalog',
406
+ DefaultDestinationAddress: '#Facto-Section-Catalog',
407
+ Templates:
408
+ [
409
+ {
410
+ Hash: 'Facto-Catalog',
411
+ Template: /*html*/`
412
+ <div class="accordion-row">
413
+ <div class="accordion-number">0</div>
414
+ <div class="accordion-card" id="facto-card-catalog">
415
+ <div class="accordion-header" onclick="pict.views['Facto-Layout'].toggleSection('facto-card-catalog')">
416
+ <span class="accordion-title">Source Catalog</span>
417
+ <span class="accordion-preview">Research and catalog data sources</span>
418
+ <span class="accordion-toggle">&#9660;</span>
419
+ </div>
420
+ <div class="accordion-body">
421
+ <p style="margin-bottom:12px; color:#666; font-size:0.9em;">Research and catalog potential data sources before provisioning them as runtime Sources and Datasets.</p>
422
+
423
+ <!-- Search -->
424
+ <div class="inline-group" style="margin-bottom:12px;">
425
+ <div style="flex:3;">
426
+ <input type="text" id="facto-catalog-search" placeholder="Search catalog by name, agency, category, or description..." style="margin-bottom:0;">
427
+ </div>
428
+ <div style="flex:0 0 auto; display:flex; align-items:flex-end;">
429
+ <button class="primary" style="margin-bottom:0;" onclick="pict.views['Facto-Catalog'].searchCatalog()">Search</button>
430
+ </div>
431
+ </div>
432
+
433
+ <!-- Entries list -->
434
+ <div id="facto-catalog-list"></div>
435
+
436
+ <!-- Dataset definitions detail (appears when clicking "Datasets" on an entry) -->
437
+ <div id="facto-catalog-detail" style="margin-top:12px;"></div>
438
+
439
+ <!-- Add entry form -->
440
+ <h3 style="margin-top:16px; margin-bottom:8px; font-size:1em; color:#444;">Add Catalog Entry</h3>
441
+ <div class="inline-group">
442
+ <div>
443
+ <label for="facto-catalog-agency">Agency / Organization</label>
444
+ <input type="text" id="facto-catalog-agency" placeholder="e.g. US Geological Survey (USGS)">
445
+ </div>
446
+ <div>
447
+ <label for="facto-catalog-name">Portal Name</label>
448
+ <input type="text" id="facto-catalog-name" placeholder="e.g. USGS Water Services">
449
+ </div>
450
+ </div>
451
+ <div class="inline-group">
452
+ <div>
453
+ <label for="facto-catalog-type">Type</label>
454
+ <select id="facto-catalog-type">
455
+ <option value="API">API</option>
456
+ <option value="File">File</option>
457
+ <option value="FTP">FTP</option>
458
+ <option value="Web">Web</option>
459
+ <option value="Database">Database</option>
460
+ <option value="Other">Other</option>
461
+ </select>
462
+ </div>
463
+ <div>
464
+ <label for="facto-catalog-protocol">Protocol</label>
465
+ <select id="facto-catalog-protocol">
466
+ <option value="HTTPS">HTTPS</option>
467
+ <option value="HTTP">HTTP</option>
468
+ <option value="FTP">FTP</option>
469
+ <option value="SFTP">SFTP</option>
470
+ <option value="Local">Local</option>
471
+ </select>
472
+ </div>
473
+ <div>
474
+ <label for="facto-catalog-category">Category</label>
475
+ <input type="text" id="facto-catalog-category" placeholder="e.g. Science, Finance">
476
+ </div>
477
+ </div>
478
+ <div class="inline-group">
479
+ <div>
480
+ <label for="facto-catalog-url">Base URL</label>
481
+ <input type="text" id="facto-catalog-url" placeholder="https://api.example.gov">
482
+ </div>
483
+ <div>
484
+ <label for="facto-catalog-region">Region</label>
485
+ <input type="text" id="facto-catalog-region" placeholder="e.g. US, Global">
486
+ </div>
487
+ <div>
488
+ <label for="facto-catalog-frequency">Update Frequency</label>
489
+ <select id="facto-catalog-frequency">
490
+ <option value="Continuous">Continuous</option>
491
+ <option value="Daily">Daily</option>
492
+ <option value="Weekly">Weekly</option>
493
+ <option value="Monthly">Monthly</option>
494
+ <option value="Quarterly">Quarterly</option>
495
+ <option value="Yearly">Yearly</option>
496
+ <option value="Unknown">Unknown</option>
497
+ </select>
498
+ </div>
499
+ </div>
500
+ <div>
501
+ <label for="facto-catalog-description">Description</label>
502
+ <input type="text" id="facto-catalog-description" placeholder="Brief description of this data source">
503
+ </div>
504
+ <button class="primary" onclick="pict.views['Facto-Catalog'].addEntry()">Add Catalog Entry</button>
505
+
506
+ <!-- Import / Export -->
507
+ <h3 style="margin-top:16px; margin-bottom:8px; font-size:1em; color:#444;">Import / Export</h3>
508
+ <textarea id="facto-catalog-import-json" rows="4" style="width:100%; font-family:monospace; font-size:0.85em; padding:8px; border:1px solid #ccc; border-radius:4px; margin-bottom:8px;" placeholder="Paste JSON array of catalog entries here..."></textarea>
509
+ <button class="primary" onclick="pict.views['Facto-Catalog'].importCatalog()">Import JSON</button>
510
+ <button class="secondary" onclick="pict.views['Facto-Catalog'].exportCatalog()">Export Catalog</button>
511
+
512
+ </div>
513
+ </div>
514
+ </div>
515
+ `
516
+ }
517
+ ],
518
+ Renderables:
519
+ [
520
+ {
521
+ RenderableHash: 'Facto-Catalog',
522
+ TemplateHash: 'Facto-Catalog',
523
+ DestinationAddress: '#Facto-Section-Catalog'
524
+ }
525
+ ]
526
+ };