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.
- package/.claude/launch.json +11 -0
- package/.dockerignore +8 -0
- package/.quackage.json +19 -0
- package/Dockerfile +26 -0
- package/bin/retold-facto.js +909 -0
- package/examples/facto-government-data.sqlite +0 -0
- package/examples/government-data-catalog.json +137 -0
- package/examples/government-data-loader.js +1432 -0
- package/package.json +91 -0
- package/scripts/facto-download.js +425 -0
- package/source/Retold-Facto.js +1042 -0
- package/source/services/Retold-Facto-BeaconProvider.js +511 -0
- package/source/services/Retold-Facto-CatalogManager.js +1252 -0
- package/source/services/Retold-Facto-DataLakeService.js +1642 -0
- package/source/services/Retold-Facto-DatasetManager.js +417 -0
- package/source/services/Retold-Facto-IngestEngine.js +1315 -0
- package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
- package/source/services/Retold-Facto-RecordManager.js +360 -0
- package/source/services/Retold-Facto-SchemaManager.js +1110 -0
- package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
- package/source/services/Retold-Facto-SourceManager.js +730 -0
- package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
- package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
- package/source/services/web-app/codemirror-entry.js +7 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
- package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
- package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
- package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
- package/source/services/web-app/web/chart.min.js +20 -0
- package/source/services/web-app/web/codemirror-bundle.js +30099 -0
- package/source/services/web-app/web/css/facto-themes.css +467 -0
- package/source/services/web-app/web/css/facto.css +502 -0
- package/source/services/web-app/web/index.html +28 -0
- package/source/services/web-app/web/retold-facto.js +12138 -0
- package/source/services/web-app/web/retold-facto.js.map +1 -0
- package/source/services/web-app/web/retold-facto.min.js +2 -0
- package/source/services/web-app/web/retold-facto.min.js.map +1 -0
- package/source/services/web-app/web/simple/index.html +17 -0
- package/test/Facto_Browser_Integration_tests.js +798 -0
- package/test/RetoldFacto_tests.js +4117 -0
- package/test/fixtures/weather-readings.csv +17 -0
- package/test/fixtures/weather-stations.csv +9 -0
- package/test/model/MeadowModel-Extended.json +8497 -0
- package/test/model/MeadowModel-PICT.json +1 -0
- package/test/model/MeadowModel.json +1355 -0
- package/test/model/ddl/Facto.ddl +225 -0
- 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;">✓</span>' : '<span style="color:#ccc;">✗</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">▼</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
|
+
};
|