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,173 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ class FactoDatasetsView extends libPictView
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+ }
9
+
10
+ onAfterRender()
11
+ {
12
+ this.pict.providers.Facto.loadDatasets().then(
13
+ () =>
14
+ {
15
+ this.refreshList();
16
+ }).catch(
17
+ (pError) =>
18
+ {
19
+ this.pict.views['Pict-Section-Modal'].toast('Error loading datasets: ' + pError.message, {type: 'error'});
20
+ });
21
+ }
22
+
23
+ refreshList()
24
+ {
25
+ let tmpContainer = document.getElementById('facto-datasets-list');
26
+ if (!tmpContainer) return;
27
+
28
+ let tmpDatasets = this.pict.AppData.Facto.Datasets;
29
+ if (!tmpDatasets || tmpDatasets.length === 0)
30
+ {
31
+ tmpContainer.innerHTML = '<p style="color:#888; font-style:italic;">No datasets created yet.</p>';
32
+ return;
33
+ }
34
+
35
+ let tmpBadgeClass = { Raw: 'badge-raw', Compositional: 'badge-compositional', Projection: 'badge-projection', Derived: 'badge-derived' };
36
+
37
+ let tmpHtml = '<table><thead><tr><th>ID</th><th>Name</th><th>Type</th><th>Description</th><th>Actions</th></tr></thead><tbody>';
38
+ for (let i = 0; i < tmpDatasets.length; i++)
39
+ {
40
+ let tmpDataset = tmpDatasets[i];
41
+ let tmpBadge = tmpBadgeClass[tmpDataset.Type] || 'badge-raw';
42
+ tmpHtml += '<tr>';
43
+ tmpHtml += '<td>' + (tmpDataset.IDDataset || '') + '</td>';
44
+ tmpHtml += '<td>' + (tmpDataset.Name || '') + '</td>';
45
+ tmpHtml += '<td><span class="badge ' + tmpBadge + '">' + (tmpDataset.Type || '') + '</span></td>';
46
+ tmpHtml += '<td style="max-width:300px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">' + (tmpDataset.Description || '') + '</td>';
47
+ tmpHtml += '<td><button class="secondary" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Datasets\'].viewStats(' + tmpDataset.IDDataset + ')">Stats</button></td>';
48
+ tmpHtml += '</tr>';
49
+ }
50
+ tmpHtml += '</tbody></table>';
51
+ tmpContainer.innerHTML = tmpHtml;
52
+ }
53
+
54
+ viewStats(pIDDataset)
55
+ {
56
+ this.pict.providers.Facto.loadDatasetStats(pIDDataset).then(
57
+ (pResponse) =>
58
+ {
59
+ let tmpMsg = 'Dataset: ' + (pResponse.Dataset ? pResponse.Dataset.Name : '#' + pIDDataset);
60
+ tmpMsg += ' | Records: ' + (pResponse.RecordCount || 0);
61
+ tmpMsg += ' | Linked Sources: ' + (pResponse.SourceCount || 0);
62
+ this.pict.views['Pict-Section-Modal'].toast(tmpMsg, {type: 'info'});
63
+ }).catch(
64
+ (pError) =>
65
+ {
66
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
67
+ });
68
+ }
69
+
70
+ addDataset()
71
+ {
72
+ let tmpName = this.pict.providers.FactoUI.getVal('facto-dataset-name');
73
+ let tmpType = this.pict.providers.FactoUI.getVal('facto-dataset-type') || 'Raw';
74
+ let tmpDescription = this.pict.providers.FactoUI.getVal('facto-dataset-desc');
75
+
76
+ if (!tmpName)
77
+ {
78
+ this.pict.views['Pict-Section-Modal'].toast('Name is required', {type: 'warning'});
79
+ return;
80
+ }
81
+
82
+ this.pict.providers.Facto.createDataset(
83
+ {
84
+ Name: tmpName,
85
+ Type: tmpType,
86
+ Description: tmpDescription
87
+ }).then(
88
+ (pResponse) =>
89
+ {
90
+ if (pResponse && pResponse.IDDataset)
91
+ {
92
+ this.pict.views['Pict-Section-Modal'].toast('Dataset created: ' + pResponse.Name, {type: 'success'});
93
+ if (document.getElementById('facto-dataset-name')) document.getElementById('facto-dataset-name').value = '';
94
+ if (document.getElementById('facto-dataset-desc')) document.getElementById('facto-dataset-desc').value = '';
95
+ return this.pict.providers.Facto.loadDatasets();
96
+ }
97
+ else
98
+ {
99
+ this.pict.views['Pict-Section-Modal'].toast('Error creating dataset', {type: 'error'});
100
+ }
101
+ }).then(
102
+ () =>
103
+ {
104
+ this.refreshList();
105
+ }).catch(
106
+ (pError) =>
107
+ {
108
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
109
+ });
110
+ }
111
+ }
112
+
113
+ module.exports = FactoDatasetsView;
114
+
115
+ module.exports.default_configuration =
116
+ {
117
+ ViewIdentifier: 'Facto-Datasets',
118
+ DefaultRenderable: 'Facto-Datasets',
119
+ DefaultDestinationAddress: '#Facto-Section-Datasets',
120
+ Templates:
121
+ [
122
+ {
123
+ Hash: 'Facto-Datasets',
124
+ Template: /*html*/`
125
+ <div class="accordion-row">
126
+ <div class="accordion-number">2</div>
127
+ <div class="accordion-card open" id="facto-card-datasets">
128
+ <div class="accordion-header" onclick="pict.views['Facto-Layout'].toggleSection('facto-card-datasets')">
129
+ <span class="accordion-title">Datasets</span>
130
+ <span class="accordion-preview">Raw, Compositional, Projection, Derived</span>
131
+ <span class="accordion-toggle">&#9660;</span>
132
+ </div>
133
+ <div class="accordion-body">
134
+ <p style="margin-bottom:12px; color:#666; font-size:0.9em;">Datasets are named collections of records. Types: Raw (ingested), Compositional (merged), Projection (flattened), Derived (computed).</p>
135
+ <div id="facto-datasets-list"></div>
136
+
137
+ <h3 style="margin-top:16px; margin-bottom:8px; font-size:1em; color:#444;">Create Dataset</h3>
138
+ <div class="inline-group">
139
+ <div>
140
+ <label for="facto-dataset-name">Name</label>
141
+ <input type="text" id="facto-dataset-name" placeholder="e.g. Census Population 2020">
142
+ </div>
143
+ <div>
144
+ <label for="facto-dataset-type">Type</label>
145
+ <select id="facto-dataset-type">
146
+ <option value="Raw">Raw</option>
147
+ <option value="Compositional">Compositional</option>
148
+ <option value="Projection">Projection</option>
149
+ <option value="Derived">Derived</option>
150
+ </select>
151
+ </div>
152
+ </div>
153
+ <div>
154
+ <label for="facto-dataset-desc">Description</label>
155
+ <input type="text" id="facto-dataset-desc" placeholder="Brief description of the dataset">
156
+ </div>
157
+ <button class="primary" onclick="pict.views['Facto-Datasets'].addDataset()">Create Dataset</button>
158
+
159
+ </div>
160
+ </div>
161
+ </div>
162
+ `
163
+ }
164
+ ],
165
+ Renderables:
166
+ [
167
+ {
168
+ RenderableHash: 'Facto-Datasets',
169
+ TemplateHash: 'Facto-Datasets',
170
+ DestinationAddress: '#Facto-Section-Datasets'
171
+ }
172
+ ]
173
+ };
@@ -0,0 +1,259 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ class FactoIngestView extends libPictView
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+ }
9
+
10
+ onAfterRender()
11
+ {
12
+ this.pict.providers.Facto.loadIngestJobs().then(
13
+ () =>
14
+ {
15
+ this.refreshList();
16
+ }).catch(
17
+ (pError) =>
18
+ {
19
+ this.pict.views['Pict-Section-Modal'].toast('Error loading jobs: ' + pError.message, {type: 'error'});
20
+ });
21
+ }
22
+
23
+ refreshList()
24
+ {
25
+ let tmpContainer = document.getElementById('facto-ingest-list');
26
+ if (!tmpContainer) return;
27
+
28
+ let tmpJobs = this.pict.AppData.Facto.IngestJobs;
29
+ if (!tmpJobs || tmpJobs.length === 0)
30
+ {
31
+ tmpContainer.innerHTML = '<p style="color:#888; font-style:italic;">No ingest jobs yet.</p>';
32
+ return;
33
+ }
34
+
35
+ let tmpStatusColors = { Pending: '#ffc107', Running: '#17a2b8', Completed: '#28a745', Failed: '#dc3545', Cancelled: '#6c757d' };
36
+
37
+ let tmpHtml = '<table><thead><tr><th>ID</th><th>Status</th><th>Source</th><th>Dataset</th><th>Processed</th><th>Created</th><th>Errors</th><th>Start</th><th>Actions</th></tr></thead><tbody>';
38
+ for (let i = 0; i < tmpJobs.length; i++)
39
+ {
40
+ let tmpJob = tmpJobs[i];
41
+ let tmpColor = tmpStatusColors[tmpJob.Status] || '#888';
42
+ tmpHtml += '<tr>';
43
+ tmpHtml += '<td>' + (tmpJob.IDIngestJob || '') + '</td>';
44
+ tmpHtml += '<td><span style="color:' + tmpColor + '; font-weight:600;">' + (tmpJob.Status || '') + '</span></td>';
45
+ tmpHtml += '<td>' + (tmpJob.IDSource || '') + '</td>';
46
+ tmpHtml += '<td>' + (tmpJob.IDDataset || '') + '</td>';
47
+ tmpHtml += '<td>' + (tmpJob.RecordsProcessed || 0) + '</td>';
48
+ tmpHtml += '<td>' + (tmpJob.RecordsCreated || 0) + '</td>';
49
+ tmpHtml += '<td>' + (tmpJob.RecordsErrored || 0) + '</td>';
50
+ tmpHtml += '<td>' + (tmpJob.StartDate || '-') + '</td>';
51
+ tmpHtml += '<td>';
52
+ if (tmpJob.Status === 'Pending')
53
+ {
54
+ tmpHtml += '<button class="success" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Ingest\'].startJob(' + tmpJob.IDIngestJob + ')">Start</button>';
55
+ }
56
+ tmpHtml += '<button class="secondary" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Ingest\'].viewLog(' + tmpJob.IDIngestJob + ')">Log</button>';
57
+ tmpHtml += '</td>';
58
+ tmpHtml += '</tr>';
59
+ }
60
+ tmpHtml += '</tbody></table>';
61
+ tmpContainer.innerHTML = tmpHtml;
62
+ }
63
+
64
+ startJob(pIDIngestJob)
65
+ {
66
+ this.pict.providers.Facto.startIngestJob(pIDIngestJob).then(
67
+ () =>
68
+ {
69
+ this.pict.views['Pict-Section-Modal'].toast('Job #' + pIDIngestJob + ' started', {type: 'success'});
70
+ return this.pict.providers.Facto.loadIngestJobs();
71
+ }).then(
72
+ () =>
73
+ {
74
+ this.refreshList();
75
+ }).catch(
76
+ (pError) =>
77
+ {
78
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
79
+ });
80
+ }
81
+
82
+ viewLog(pIDIngestJob)
83
+ {
84
+ this.pict.providers.Facto.loadIngestJobDetails(pIDIngestJob).then(
85
+ (pResponse) =>
86
+ {
87
+ if (pResponse && pResponse.Job)
88
+ {
89
+ let tmpLog = pResponse.Job.Log || '(empty)';
90
+ let tmpLogContainer = document.getElementById('facto-ingest-log');
91
+ if (tmpLogContainer)
92
+ {
93
+ tmpLogContainer.textContent = 'Job #' + pIDIngestJob + ' Log:\n' + tmpLog;
94
+ tmpLogContainer.style.display = 'block';
95
+ }
96
+ }
97
+ }).catch(
98
+ (pError) =>
99
+ {
100
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
101
+ });
102
+ }
103
+
104
+ ingestPastedContent()
105
+ {
106
+ let tmpIDDataset = parseInt(this.pict.providers.FactoUI.getVal('facto-ingest-paste-dataset'), 10) || 0;
107
+ let tmpIDSource = parseInt(this.pict.providers.FactoUI.getVal('facto-ingest-paste-source'), 10) || 0;
108
+ let tmpFormat = this.pict.providers.FactoUI.getVal('facto-ingest-paste-format') || 'Auto';
109
+ let tmpType = this.pict.providers.FactoUI.getVal('facto-ingest-paste-type') || 'data';
110
+ let tmpContent = this.pict.providers.FactoUI.getVal('facto-ingest-paste-content');
111
+
112
+ if (!tmpContent.trim())
113
+ {
114
+ this.pict.views['Pict-Section-Modal'].toast('Content is required', {type: 'warning'});
115
+ return;
116
+ }
117
+
118
+ this.pict.views['Pict-Section-Modal'].toast('Ingesting...', {type: 'info'});
119
+
120
+ this.pict.providers.Facto.ingestFileContent(tmpIDDataset, tmpIDSource, tmpContent, tmpFormat, tmpType).then(
121
+ (pResponse) =>
122
+ {
123
+ if (pResponse && pResponse.Success)
124
+ {
125
+ this.pict.views['Pict-Section-Modal'].toast('Ingested ' + (pResponse.RecordsCreated || 0) + ' records', {type: 'success'});
126
+ if (document.getElementById('facto-ingest-paste-content'))
127
+ {
128
+ document.getElementById('facto-ingest-paste-content').value = '';
129
+ }
130
+ }
131
+ else
132
+ {
133
+ this.pict.views['Pict-Section-Modal'].toast('Ingest error: ' + ((pResponse && pResponse.Error) || 'Unknown'), {type: 'error'});
134
+ }
135
+ }).catch(
136
+ (pError) =>
137
+ {
138
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
139
+ });
140
+ }
141
+
142
+ createJob()
143
+ {
144
+ let tmpIDSource = parseInt(this.pict.providers.FactoUI.getVal('facto-ingest-source'), 10) || 0;
145
+ let tmpIDDataset = parseInt(this.pict.providers.FactoUI.getVal('facto-ingest-dataset'), 10) || 0;
146
+
147
+ if (!tmpIDSource || !tmpIDDataset)
148
+ {
149
+ this.pict.views['Pict-Section-Modal'].toast('Source ID and Dataset ID are required', {type: 'warning'});
150
+ return;
151
+ }
152
+
153
+ this.pict.providers.Facto.createIngestJob(tmpIDSource, tmpIDDataset).then(
154
+ (pResponse) =>
155
+ {
156
+ if (pResponse && pResponse.Success)
157
+ {
158
+ this.pict.views['Pict-Section-Modal'].toast('Ingest job created: #' + pResponse.Job.IDIngestJob, {type: 'success'});
159
+ if (document.getElementById('facto-ingest-source')) document.getElementById('facto-ingest-source').value = '';
160
+ if (document.getElementById('facto-ingest-dataset')) document.getElementById('facto-ingest-dataset').value = '';
161
+ return this.pict.providers.Facto.loadIngestJobs();
162
+ }
163
+ else
164
+ {
165
+ this.pict.views['Pict-Section-Modal'].toast('Error creating job', {type: 'error'});
166
+ }
167
+ }).then(
168
+ () =>
169
+ {
170
+ this.refreshList();
171
+ }).catch(
172
+ (pError) =>
173
+ {
174
+ this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
175
+ });
176
+ }
177
+ }
178
+
179
+ module.exports = FactoIngestView;
180
+
181
+ module.exports.default_configuration =
182
+ {
183
+ ViewIdentifier: 'Facto-Ingest',
184
+ DefaultRenderable: 'Facto-Ingest',
185
+ DefaultDestinationAddress: '#Facto-Section-Ingest',
186
+ Templates:
187
+ [
188
+ {
189
+ Hash: 'Facto-Ingest',
190
+ Template: /*html*/`
191
+ <div class="accordion-row">
192
+ <div class="accordion-number">4</div>
193
+ <div class="accordion-card" id="facto-card-ingest">
194
+ <div class="accordion-header" onclick="pict.views['Facto-Layout'].toggleSection('facto-card-ingest')">
195
+ <span class="accordion-title">Ingest Jobs</span>
196
+ <span class="accordion-preview">Download, parse, and store datasets</span>
197
+ <span class="accordion-toggle">&#9660;</span>
198
+ </div>
199
+ <div class="accordion-body">
200
+ <p style="margin-bottom:12px; color:#666; font-size:0.9em;">Track data ingest operations from configured sources into datasets.</p>
201
+ <div id="facto-ingest-list"></div>
202
+
203
+ <h3 style="margin-top:16px; margin-bottom:8px; font-size:1em; color:#444;">Create Ingest Job</h3>
204
+ <div class="inline-group">
205
+ <div>
206
+ <label for="facto-ingest-source">Source ID</label>
207
+ <input type="number" id="facto-ingest-source" placeholder="1">
208
+ </div>
209
+ <div>
210
+ <label for="facto-ingest-dataset">Dataset ID</label>
211
+ <input type="number" id="facto-ingest-dataset" placeholder="1">
212
+ </div>
213
+ </div>
214
+ <button class="primary" onclick="pict.views['Facto-Ingest'].createJob()">Create Job</button>
215
+
216
+ <pre id="facto-ingest-log" style="display:none; margin-top:12px; padding:12px; background:#f8f9fa; border:1px solid #e9ecef; border-radius:4px; font-size:0.85em; max-height:200px; overflow:auto; white-space:pre-wrap;"></pre>
217
+
218
+ <h3 style="margin-top:20px; margin-bottom:8px; font-size:1em; color:#444;">Paste &amp; Ingest</h3>
219
+ <p style="margin-bottom:8px; color:#666; font-size:0.85em;">Paste CSV or JSON content directly to ingest records.</p>
220
+ <div class="inline-group">
221
+ <div>
222
+ <label for="facto-ingest-paste-dataset">Dataset ID</label>
223
+ <input type="number" id="facto-ingest-paste-dataset" placeholder="1">
224
+ </div>
225
+ <div>
226
+ <label for="facto-ingest-paste-source">Source ID</label>
227
+ <input type="number" id="facto-ingest-paste-source" placeholder="1">
228
+ </div>
229
+ <div>
230
+ <label for="facto-ingest-paste-format">Format</label>
231
+ <select id="facto-ingest-paste-format">
232
+ <option value="Auto">Auto-Detect</option>
233
+ <option value="CSV">CSV</option>
234
+ <option value="JSON">JSON</option>
235
+ </select>
236
+ </div>
237
+ <div>
238
+ <label for="facto-ingest-paste-type">Record Type</label>
239
+ <input type="text" id="facto-ingest-paste-type" placeholder="data">
240
+ </div>
241
+ </div>
242
+ <textarea id="facto-ingest-paste-content" rows="6" style="width:100%; padding:8px 12px; border:1px solid #ccc; border-radius:4px; font-size:0.9em; font-family:monospace; margin-bottom:10px;" placeholder="Paste CSV or JSON data here..."></textarea>
243
+ <button class="primary" onclick="pict.views['Facto-Ingest'].ingestPastedContent()">Ingest</button>
244
+
245
+ </div>
246
+ </div>
247
+ </div>
248
+ `
249
+ }
250
+ ],
251
+ Renderables:
252
+ [
253
+ {
254
+ RenderableHash: 'Facto-Ingest',
255
+ TemplateHash: 'Facto-Ingest',
256
+ DestinationAddress: '#Facto-Section-Ingest'
257
+ }
258
+ ]
259
+ };
@@ -0,0 +1,191 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ class FactoLayoutView extends libPictView
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+ }
9
+
10
+ onAfterRender()
11
+ {
12
+ // Render all section views into their containers
13
+ if (this.pict.views['Facto-Throughput']) this.pict.views['Facto-Throughput'].render();
14
+ this.pict.views['Facto-Scanner'].render();
15
+ this.pict.views['Facto-Catalog'].render();
16
+ this.pict.views['Facto-Sources'].render();
17
+ this.pict.views['Facto-Datasets'].render();
18
+ this.pict.views['Facto-Records'].render();
19
+ this.pict.views['Facto-Ingest'].render();
20
+ this.pict.views['Facto-Projections'].render();
21
+
22
+ this.pict.CSSMap.injectCSS();
23
+ }
24
+
25
+ toggleSection(pSectionId)
26
+ {
27
+ let tmpCard = document.getElementById(pSectionId);
28
+ if (!tmpCard) return;
29
+ tmpCard.classList.toggle('open');
30
+ }
31
+
32
+ expandAllSections()
33
+ {
34
+ let tmpCards = document.querySelectorAll('.accordion-card');
35
+ for (let i = 0; i < tmpCards.length; i++)
36
+ {
37
+ tmpCards[i].classList.add('open');
38
+ }
39
+ }
40
+
41
+ collapseAllSections()
42
+ {
43
+ let tmpCards = document.querySelectorAll('.accordion-card');
44
+ for (let i = 0; i < tmpCards.length; i++)
45
+ {
46
+ tmpCards[i].classList.remove('open');
47
+ }
48
+ }
49
+ }
50
+
51
+ module.exports = FactoLayoutView;
52
+
53
+ module.exports.default_configuration =
54
+ {
55
+ ViewIdentifier: 'Facto-Layout',
56
+ DefaultRenderable: 'Facto-Layout',
57
+ DefaultDestinationAddress: '#Facto-Application-Container',
58
+ CSS: /*css*/`
59
+ * { box-sizing: border-box; margin: 0; padding: 0; }
60
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 20px; }
61
+ h1 { margin-bottom: 20px; color: #1a1a1a; }
62
+ h2 { margin-bottom: 12px; color: #444; font-size: 1.2em; border-bottom: 2px solid #ddd; padding-bottom: 6px; }
63
+
64
+ .section { background: #fff; border-radius: 8px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
65
+
66
+ /* Accordion layout */
67
+ .accordion-row { display: flex; gap: 0; margin-bottom: 16px; align-items: stretch; }
68
+ .accordion-number {
69
+ flex: 0 0 48px; display: flex; align-items: flex-start; justify-content: center;
70
+ padding-top: 16px; font-size: 1.6em; font-weight: 700; color: #4a90d9;
71
+ user-select: none;
72
+ }
73
+ .accordion-card {
74
+ flex: 1; background: #fff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);
75
+ overflow: hidden; min-width: 0;
76
+ }
77
+ .accordion-header {
78
+ display: flex; align-items: center; padding: 14px 20px; cursor: pointer;
79
+ user-select: none; gap: 12px; transition: background 0.15s; line-height: 1.4;
80
+ }
81
+ .accordion-header:hover { background: #fafafa; }
82
+ .accordion-title { font-weight: 600; color: #333; font-size: 1.05em; white-space: nowrap; }
83
+ .accordion-preview { flex: 1; font-style: italic; color: #888; font-size: 0.9em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
84
+ .accordion-toggle {
85
+ flex: 0 0 20px; display: flex; align-items: center; justify-content: center;
86
+ border-radius: 4px; transition: background 0.15s, transform 0.25s; font-size: 0.7em; color: #888;
87
+ }
88
+ .accordion-header:hover .accordion-toggle { background: #eee; color: #555; }
89
+ .accordion-card.open .accordion-toggle { transform: rotate(180deg); }
90
+ .accordion-body { padding: 0 20px 20px; display: none; }
91
+ .accordion-card.open .accordion-body { display: block; }
92
+ .accordion-card.open .accordion-header { border-bottom: 1px solid #eee; }
93
+ .accordion-card.open .accordion-preview { display: none; }
94
+
95
+ .accordion-controls {
96
+ display: flex; gap: 8px; margin-bottom: 12px; justify-content: flex-end;
97
+ }
98
+ .accordion-controls button {
99
+ padding: 4px 10px; font-size: 0.82em; font-weight: 500; background: none;
100
+ border: 1px solid #ccc; border-radius: 4px; color: #666; cursor: pointer; margin: 0;
101
+ }
102
+ .accordion-controls button:hover { background: #f0f0f0; border-color: #aaa; color: #333; }
103
+
104
+ label { display: block; font-weight: 600; margin-bottom: 4px; font-size: 0.9em; }
105
+ input[type="text"], input[type="password"], input[type="number"] {
106
+ width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px;
107
+ font-size: 0.95em; margin-bottom: 10px;
108
+ }
109
+ input[type="text"]:focus, input[type="password"]:focus, input[type="number"]:focus {
110
+ outline: none; border-color: #4a90d9;
111
+ }
112
+
113
+ button {
114
+ padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer;
115
+ font-size: 0.9em; font-weight: 600; margin-right: 8px; margin-bottom: 8px;
116
+ }
117
+ button.primary { background: #4a90d9; color: #fff; }
118
+ button.primary:hover { background: #357abd; }
119
+ button.secondary { background: #6c757d; color: #fff; }
120
+ button.secondary:hover { background: #5a6268; }
121
+ button.danger { background: #dc3545; color: #fff; }
122
+ button.danger:hover { background: #c82333; }
123
+ button.success { background: #28a745; color: #fff; }
124
+ button.success:hover { background: #218838; }
125
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
126
+
127
+ .status { padding: 8px 12px; border-radius: 4px; margin-top: 10px; font-size: 0.9em; }
128
+ .status.ok { background: #d4edda; color: #155724; }
129
+ .status.error { background: #f8d7da; color: #721c24; }
130
+ .status.info { background: #d1ecf1; color: #0c5460; }
131
+ .status.warn { background: #fff3cd; color: #856404; }
132
+
133
+ .inline-group { display: flex; gap: 8px; align-items: flex-end; margin-bottom: 10px; }
134
+ .inline-group > div { flex: 1; }
135
+
136
+ a { color: #4a90d9; }
137
+
138
+ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 0.95em; margin-bottom: 10px; }
139
+
140
+ table { width: 100%; border-collapse: collapse; font-size: 0.9em; }
141
+ th { text-align: left; font-weight: 600; padding: 8px; border-bottom: 2px solid #ddd; color: #555; }
142
+ td { padding: 8px; border-bottom: 1px solid #eee; }
143
+ tr:hover { background: #fafafa; }
144
+
145
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 0.8em; font-weight: 600; }
146
+ .badge-raw { background: #d1ecf1; color: #0c5460; }
147
+ .badge-compositional { background: #d4edda; color: #155724; }
148
+ .badge-projection { background: #fff3cd; color: #856404; }
149
+ .badge-derived { background: #e2d5f1; color: #4a2d73; }
150
+
151
+ .certainty-bar { display: inline-block; width: 60px; height: 8px; background: #e9ecef; border-radius: 4px; overflow: hidden; vertical-align: middle; }
152
+ .certainty-fill { height: 100%; border-radius: 4px; }
153
+ .certainty-low { background: #dc3545; }
154
+ .certainty-mid { background: #ffc107; }
155
+ .certainty-high { background: #28a745; }
156
+ `,
157
+ Templates:
158
+ [
159
+ {
160
+ Hash: 'Facto-Layout',
161
+ Template: /*html*/`
162
+ <h1>Retold Facto</h1>
163
+ <p style="color:#666; margin-bottom:20px; font-size:0.95em;">Data Warehouse &amp; Knowledge Graph Storage</p>
164
+
165
+ <!-- Expand / Collapse All -->
166
+ <div class="accordion-controls">
167
+ <button onclick="pict.views['Facto-Layout'].expandAllSections()">Expand All</button>
168
+ <button onclick="pict.views['Facto-Layout'].collapseAllSections()">Collapse All</button>
169
+ </div>
170
+
171
+ <!-- Section containers -->
172
+ <div id="Facto-Section-Throughput"></div>
173
+ <div id="Facto-Section-Scanner"></div>
174
+ <div id="Facto-Section-Catalog"></div>
175
+ <div id="Facto-Section-Sources"></div>
176
+ <div id="Facto-Section-Datasets"></div>
177
+ <div id="Facto-Section-Records"></div>
178
+ <div id="Facto-Section-Ingest"></div>
179
+ <div id="Facto-Section-Projections"></div>
180
+ `
181
+ }
182
+ ],
183
+ Renderables:
184
+ [
185
+ {
186
+ RenderableHash: 'Facto-Layout',
187
+ TemplateHash: 'Facto-Layout',
188
+ DestinationAddress: '#Facto-Application-Container'
189
+ }
190
+ ]
191
+ };