retold-data-service 2.0.21 → 2.0.23
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/.quackage-comprehension-loader.json +19 -0
- package/bin/retold-data-service-clone.js +4 -1
- package/generate-bookstore-comprehension.js +645 -0
- package/package.json +7 -7
- package/source/Retold-Data-Service.js +30 -2
- package/source/services/comprehension-loader/ComprehensionLoader-Command-Load.js +345 -0
- package/source/services/comprehension-loader/ComprehensionLoader-Command-Schema.js +97 -0
- package/source/services/comprehension-loader/ComprehensionLoader-Command-Session.js +221 -0
- package/source/services/comprehension-loader/ComprehensionLoader-Command-WebUI.js +57 -0
- package/source/services/comprehension-loader/Retold-Data-Service-ComprehensionLoader.js +536 -0
- package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader-Configuration.json +9 -0
- package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader.js +86 -0
- package/source/services/comprehension-loader/pict-app/Pict-ComprehensionLoader-Bundle.js +6 -0
- package/source/services/comprehension-loader/pict-app/providers/Pict-Provider-ComprehensionLoader.js +760 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Layout.js +360 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js +472 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Schema.js +119 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Session.js +269 -0
- package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Source.js +330 -0
- package/source/services/comprehension-loader/web/comprehension-loader.js +6794 -0
- package/source/services/comprehension-loader/web/comprehension-loader.js.map +1 -0
- package/source/services/comprehension-loader/web/comprehension-loader.min.js +2 -0
- package/source/services/comprehension-loader/web/comprehension-loader.min.js.map +1 -0
- package/source/services/comprehension-loader/web/index.html +17 -0
- package/source/services/data-cloner/DataCloner-Command-Schema.js +407 -15
- package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +59 -1
- package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +1 -0
- package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +125 -5
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +18 -8
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +104 -1
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +1 -1
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +12 -0
- package/source/services/data-cloner/web/data-cloner.js +201 -139
- package/source/services/data-cloner/web/data-cloner.js.map +1 -1
- package/source/services/data-cloner/web/data-cloner.min.js +1 -1
- package/source/services/data-cloner/web/data-cloner.min.js.map +1 -1
- package/test/RetoldDataService_tests.js +225 -0
package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class ComprehensionLoaderLoadView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
startLoad()
|
|
11
|
+
{
|
|
12
|
+
this.pict.providers.ComprehensionLoader.setSectionPhase(4, 'busy');
|
|
13
|
+
this.pict.providers.ComprehensionLoader.setStatus('loadStatus', 'Starting load...', 'info');
|
|
14
|
+
|
|
15
|
+
// Clear previous report
|
|
16
|
+
this.pict.AppData.ComprehensionLoader.LastReport = null;
|
|
17
|
+
|
|
18
|
+
let tmpSelf = this;
|
|
19
|
+
this.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/load/start')
|
|
20
|
+
.then(function(pData)
|
|
21
|
+
{
|
|
22
|
+
if (pData.Success)
|
|
23
|
+
{
|
|
24
|
+
let tmpMsg = 'Load started for ' + pData.Entities.length + ' entities (' + tmpSelf.pict.providers.ComprehensionLoader.formatNumber(pData.TotalRecords) + ' records).';
|
|
25
|
+
tmpSelf.pict.providers.ComprehensionLoader.setStatus('loadStatus', tmpMsg, 'ok');
|
|
26
|
+
tmpSelf.startPolling();
|
|
27
|
+
}
|
|
28
|
+
else
|
|
29
|
+
{
|
|
30
|
+
tmpSelf.pict.providers.ComprehensionLoader.setStatus('loadStatus', 'Load start failed: ' + (pData.Error || 'Unknown error'), 'error');
|
|
31
|
+
tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(4, 'error');
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.catch(function(pError)
|
|
35
|
+
{
|
|
36
|
+
tmpSelf.pict.providers.ComprehensionLoader.setStatus('loadStatus', 'Request failed: ' + pError.message, 'error');
|
|
37
|
+
tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(4, 'error');
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
stopLoad()
|
|
42
|
+
{
|
|
43
|
+
let tmpSelf = this;
|
|
44
|
+
this.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/load/stop')
|
|
45
|
+
.then(function(pData)
|
|
46
|
+
{
|
|
47
|
+
tmpSelf.pict.providers.ComprehensionLoader.setStatus('loadStatus', 'Load stop requested.', 'warn');
|
|
48
|
+
})
|
|
49
|
+
.catch(function(pError)
|
|
50
|
+
{
|
|
51
|
+
tmpSelf.pict.providers.ComprehensionLoader.setStatus('loadStatus', 'Request failed: ' + pError.message, 'error');
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
startPolling()
|
|
56
|
+
{
|
|
57
|
+
if (this.pict.AppData.ComprehensionLoader.LoadPollTimer) clearInterval(this.pict.AppData.ComprehensionLoader.LoadPollTimer);
|
|
58
|
+
let tmpSelf = this;
|
|
59
|
+
this.pict.AppData.ComprehensionLoader.LoadPollTimer = setInterval(function() { tmpSelf.pollLoadStatus(); }, 2000);
|
|
60
|
+
this.pollLoadStatus();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
stopPolling()
|
|
64
|
+
{
|
|
65
|
+
if (this.pict.AppData.ComprehensionLoader.LoadPollTimer)
|
|
66
|
+
{
|
|
67
|
+
clearInterval(this.pict.AppData.ComprehensionLoader.LoadPollTimer);
|
|
68
|
+
this.pict.AppData.ComprehensionLoader.LoadPollTimer = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pollLoadStatus()
|
|
73
|
+
{
|
|
74
|
+
let tmpSelf = this;
|
|
75
|
+
this.pict.providers.ComprehensionLoader.api('GET', '/comprehension_load/load/status')
|
|
76
|
+
.then(function(pData)
|
|
77
|
+
{
|
|
78
|
+
tmpSelf.renderLoadProgress(pData);
|
|
79
|
+
|
|
80
|
+
if (!pData.Running)
|
|
81
|
+
{
|
|
82
|
+
tmpSelf.stopPolling();
|
|
83
|
+
let tmpEntities = pData.Entities || {};
|
|
84
|
+
let tmpNames = Object.keys(tmpEntities);
|
|
85
|
+
if (tmpNames.length > 0)
|
|
86
|
+
{
|
|
87
|
+
let tmpHasErrors = false;
|
|
88
|
+
for (let i = 0; i < tmpNames.length; i++)
|
|
89
|
+
{
|
|
90
|
+
if (tmpEntities[tmpNames[i]].Status === 'Error') tmpHasErrors = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (tmpHasErrors)
|
|
94
|
+
{
|
|
95
|
+
tmpSelf.pict.providers.ComprehensionLoader.setStatus('loadStatus', 'Load finished with errors.', 'error');
|
|
96
|
+
tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(4, 'error');
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
{
|
|
100
|
+
tmpSelf.pict.providers.ComprehensionLoader.setStatus('loadStatus', 'Load complete.', 'ok');
|
|
101
|
+
tmpSelf.pict.providers.ComprehensionLoader.setSectionPhase(4, 'ok');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Fetch the structured report
|
|
105
|
+
tmpSelf.fetchLoadReport();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
.catch(function(pError)
|
|
110
|
+
{
|
|
111
|
+
// Silently ignore poll errors
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fetchLoadReport()
|
|
116
|
+
{
|
|
117
|
+
let tmpSelf = this;
|
|
118
|
+
this.pict.providers.ComprehensionLoader.api('GET', '/comprehension_load/load/report')
|
|
119
|
+
.then(function(pData)
|
|
120
|
+
{
|
|
121
|
+
if (pData && pData.ReportVersion)
|
|
122
|
+
{
|
|
123
|
+
tmpSelf.pict.AppData.ComprehensionLoader.LastReport = pData;
|
|
124
|
+
tmpSelf.renderLoadReport(pData);
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
.catch(function(pError)
|
|
128
|
+
{
|
|
129
|
+
// Ignore report fetch errors
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
renderLoadReport(pReport)
|
|
134
|
+
{
|
|
135
|
+
let tmpSection = document.getElementById('loadReportSection');
|
|
136
|
+
if (!tmpSection) return;
|
|
137
|
+
tmpSection.style.display = '';
|
|
138
|
+
|
|
139
|
+
let tmpCardsContainer = document.getElementById('reportSummaryCards');
|
|
140
|
+
let tmpOutcomeClass = 'outcome-' + pReport.Outcome.toLowerCase();
|
|
141
|
+
let tmpOutcomeColor = { Success: '#28a745', Partial: '#ffc107', Error: '#dc3545', Stopped: '#6c757d' }[pReport.Outcome] || '#666';
|
|
142
|
+
|
|
143
|
+
let tmpDurationSec = pReport.RunTimestamps.DurationSeconds || 0;
|
|
144
|
+
let tmpDurationStr = tmpDurationSec < 60 ? tmpDurationSec + 's' : Math.floor(tmpDurationSec / 60) + 'm ' + (tmpDurationSec % 60) + 's';
|
|
145
|
+
|
|
146
|
+
let tmpTotalPushed = this.pict.providers.ComprehensionLoader.formatNumber(pReport.Summary.TotalPushed);
|
|
147
|
+
let tmpTotalRecords = this.pict.providers.ComprehensionLoader.formatNumber(pReport.Summary.TotalRecords);
|
|
148
|
+
|
|
149
|
+
tmpCardsContainer.innerHTML = ''
|
|
150
|
+
+ '<div class="report-card ' + tmpOutcomeClass + '">'
|
|
151
|
+
+ ' <div class="card-label">Outcome</div>'
|
|
152
|
+
+ ' <div class="card-value" style="color:' + tmpOutcomeColor + '">' + pReport.Outcome + '</div>'
|
|
153
|
+
+ '</div>'
|
|
154
|
+
+ '<div class="report-card">'
|
|
155
|
+
+ ' <div class="card-label">Duration</div>'
|
|
156
|
+
+ ' <div class="card-value">' + tmpDurationStr + '</div>'
|
|
157
|
+
+ '</div>'
|
|
158
|
+
+ '<div class="report-card">'
|
|
159
|
+
+ ' <div class="card-label">Entities</div>'
|
|
160
|
+
+ ' <div class="card-value">' + pReport.Summary.Complete + ' / ' + pReport.Summary.TotalEntities + '</div>'
|
|
161
|
+
+ '</div>'
|
|
162
|
+
+ '<div class="report-card">'
|
|
163
|
+
+ ' <div class="card-label">Records Pushed</div>'
|
|
164
|
+
+ ' <div class="card-value">' + tmpTotalPushed + '</div>'
|
|
165
|
+
+ ' <div style="font-size:0.75em; color:#888">of ' + tmpTotalRecords + '</div>'
|
|
166
|
+
+ '</div>';
|
|
167
|
+
|
|
168
|
+
// Anomalies
|
|
169
|
+
let tmpAnomalyContainer = document.getElementById('reportAnomalies');
|
|
170
|
+
if (pReport.Anomalies.length === 0)
|
|
171
|
+
{
|
|
172
|
+
tmpAnomalyContainer.innerHTML = '<div style="color:#28a745; font-weight:600; font-size:0.9em">No anomalies detected.</div>';
|
|
173
|
+
}
|
|
174
|
+
else
|
|
175
|
+
{
|
|
176
|
+
let tmpHtml = '<h4 style="margin:0 0 8px; color:#dc3545; font-size:0.95em">Anomalies (' + pReport.Anomalies.length + ')</h4>';
|
|
177
|
+
tmpHtml += '<table class="progress-table">';
|
|
178
|
+
tmpHtml += '<tr><th>Entity</th><th>Type</th><th>Message</th></tr>';
|
|
179
|
+
for (let i = 0; i < pReport.Anomalies.length; i++)
|
|
180
|
+
{
|
|
181
|
+
let tmpAnomaly = pReport.Anomalies[i];
|
|
182
|
+
let tmpTypeColor = tmpAnomaly.Type === 'Error' ? '#dc3545' : '#6c757d';
|
|
183
|
+
tmpHtml += '<tr>';
|
|
184
|
+
tmpHtml += '<td><strong>' + this.pict.providers.ComprehensionLoader.escapeHtml(tmpAnomaly.Entity) + '</strong></td>';
|
|
185
|
+
tmpHtml += '<td style="color:' + tmpTypeColor + '">' + tmpAnomaly.Type + '</td>';
|
|
186
|
+
tmpHtml += '<td>' + this.pict.providers.ComprehensionLoader.escapeHtml(tmpAnomaly.Message) + '</td>';
|
|
187
|
+
tmpHtml += '</tr>';
|
|
188
|
+
}
|
|
189
|
+
tmpHtml += '</table>';
|
|
190
|
+
tmpAnomalyContainer.innerHTML = tmpHtml;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Entity details
|
|
194
|
+
let tmpEntityContainer = document.getElementById('reportEntityDetails');
|
|
195
|
+
if (pReport.Entities && pReport.Entities.length > 0)
|
|
196
|
+
{
|
|
197
|
+
let tmpHtml = '<h4 style="margin:0 0 8px; font-size:0.95em; color:#444">Entity Details</h4>';
|
|
198
|
+
tmpHtml += '<table class="progress-table">';
|
|
199
|
+
tmpHtml += '<tr><th>Entity</th><th>Duration</th><th>Records</th><th>Status</th></tr>';
|
|
200
|
+
for (let i = 0; i < pReport.Entities.length; i++)
|
|
201
|
+
{
|
|
202
|
+
let tmpEntity = pReport.Entities[i];
|
|
203
|
+
let tmpDur = tmpEntity.DurationSeconds < 60 ? tmpEntity.DurationSeconds + 's' : Math.floor(tmpEntity.DurationSeconds / 60) + 'm ' + (tmpEntity.DurationSeconds % 60) + 's';
|
|
204
|
+
let tmpRecs = this.pict.providers.ComprehensionLoader.formatNumber(tmpEntity.Pushed);
|
|
205
|
+
let tmpStatusColor = { Complete: '#28a745', Error: '#dc3545' }[tmpEntity.Status] || '#666';
|
|
206
|
+
tmpHtml += '<tr>';
|
|
207
|
+
tmpHtml += '<td><strong>' + this.pict.providers.ComprehensionLoader.escapeHtml(tmpEntity.Name) + '</strong></td>';
|
|
208
|
+
tmpHtml += '<td>' + tmpDur + '</td>';
|
|
209
|
+
tmpHtml += '<td>' + tmpRecs + '</td>';
|
|
210
|
+
tmpHtml += '<td style="color:' + tmpStatusColor + '">' + tmpEntity.Status + '</td>';
|
|
211
|
+
tmpHtml += '</tr>';
|
|
212
|
+
}
|
|
213
|
+
tmpHtml += '</table>';
|
|
214
|
+
tmpEntityContainer.innerHTML = tmpHtml;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
downloadReport()
|
|
219
|
+
{
|
|
220
|
+
if (!this.pict.AppData.ComprehensionLoader.LastReport)
|
|
221
|
+
{
|
|
222
|
+
this.pict.providers.ComprehensionLoader.setStatus('reportStatus', 'No report available.', 'warn');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
let tmpJson = JSON.stringify(this.pict.AppData.ComprehensionLoader.LastReport, null, '\t');
|
|
226
|
+
let tmpBlob = new Blob([tmpJson], { type: 'application/json' });
|
|
227
|
+
let tmpAnchor = document.createElement('a');
|
|
228
|
+
tmpAnchor.href = URL.createObjectURL(tmpBlob);
|
|
229
|
+
let tmpTimestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
230
|
+
tmpAnchor.download = 'ComprehensionLoader-Report-' + tmpTimestamp + '.json';
|
|
231
|
+
tmpAnchor.click();
|
|
232
|
+
URL.revokeObjectURL(tmpAnchor.href);
|
|
233
|
+
this.pict.providers.ComprehensionLoader.setStatus('reportStatus', 'Report downloaded.', 'ok');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
copyReport()
|
|
237
|
+
{
|
|
238
|
+
if (!this.pict.AppData.ComprehensionLoader.LastReport)
|
|
239
|
+
{
|
|
240
|
+
this.pict.providers.ComprehensionLoader.setStatus('reportStatus', 'No report available.', 'warn');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
let tmpJson = JSON.stringify(this.pict.AppData.ComprehensionLoader.LastReport, null, '\t');
|
|
244
|
+
let tmpSelf = this;
|
|
245
|
+
navigator.clipboard.writeText(tmpJson).then(function()
|
|
246
|
+
{
|
|
247
|
+
tmpSelf.pict.providers.ComprehensionLoader.setStatus('reportStatus', 'Report copied to clipboard.', 'ok');
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
renderLoadProgress(pData)
|
|
252
|
+
{
|
|
253
|
+
let tmpContainer = document.getElementById('loadProgress');
|
|
254
|
+
let tmpEntities = pData.Entities || {};
|
|
255
|
+
let tmpEntityNames = Object.keys(tmpEntities);
|
|
256
|
+
|
|
257
|
+
if (tmpEntityNames.length === 0)
|
|
258
|
+
{
|
|
259
|
+
tmpContainer.innerHTML = '';
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let tmpPushing = [];
|
|
264
|
+
let tmpPending = [];
|
|
265
|
+
let tmpCompleted = [];
|
|
266
|
+
let tmpErrors = [];
|
|
267
|
+
|
|
268
|
+
for (let i = 0; i < tmpEntityNames.length; i++)
|
|
269
|
+
{
|
|
270
|
+
let tmpName = tmpEntityNames[i];
|
|
271
|
+
let tmpEntity = tmpEntities[tmpName];
|
|
272
|
+
|
|
273
|
+
if (tmpEntity.Status === 'Pushing')
|
|
274
|
+
{
|
|
275
|
+
tmpPushing.push({ Name: tmpName, Data: tmpEntity });
|
|
276
|
+
}
|
|
277
|
+
else if (tmpEntity.Status === 'Pending')
|
|
278
|
+
{
|
|
279
|
+
tmpPending.push({ Name: tmpName, Data: tmpEntity });
|
|
280
|
+
}
|
|
281
|
+
else if (tmpEntity.Status === 'Complete')
|
|
282
|
+
{
|
|
283
|
+
tmpCompleted.push({ Name: tmpName, Data: tmpEntity });
|
|
284
|
+
}
|
|
285
|
+
else
|
|
286
|
+
{
|
|
287
|
+
tmpErrors.push({ Name: tmpName, Data: tmpEntity });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let tmpHtml = '';
|
|
292
|
+
let tmpSelf = this;
|
|
293
|
+
|
|
294
|
+
let fRenderRow = function(pName, pEntity)
|
|
295
|
+
{
|
|
296
|
+
let tmpPct = 0;
|
|
297
|
+
if (pEntity.Total === 0 && pEntity.Status === 'Complete')
|
|
298
|
+
{
|
|
299
|
+
tmpPct = 100;
|
|
300
|
+
}
|
|
301
|
+
else if (pEntity.Total > 0)
|
|
302
|
+
{
|
|
303
|
+
tmpPct = Math.round((pEntity.Pushed / pEntity.Total) * 100);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let tmpBarColor = '#28a745';
|
|
307
|
+
if (pEntity.Status === 'Error') tmpBarColor = '#dc3545';
|
|
308
|
+
else if (pEntity.Status === 'Pushing') tmpBarColor = '#4a90d9';
|
|
309
|
+
else if (pEntity.Status === 'Pending') tmpBarColor = '#adb5bd';
|
|
310
|
+
|
|
311
|
+
let tmpRow = '<tr>';
|
|
312
|
+
tmpRow += '<td><strong>' + pName + '</strong></td>';
|
|
313
|
+
tmpRow += '<td>' + pEntity.Status + '</td>';
|
|
314
|
+
tmpRow += '<td>';
|
|
315
|
+
tmpRow += '<div class="progress-bar-container"><div class="progress-bar-fill" style="width:' + tmpPct + '%; background:' + tmpBarColor + '"></div></div>';
|
|
316
|
+
tmpRow += ' ' + tmpPct + '%';
|
|
317
|
+
tmpRow += '</td>';
|
|
318
|
+
tmpRow += '<td>' + (pEntity.Pushed || 0) + ' / ' + (pEntity.Total || 0) + '</td>';
|
|
319
|
+
tmpRow += '</tr>';
|
|
320
|
+
return tmpRow;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
if (tmpPushing.length > 0)
|
|
324
|
+
{
|
|
325
|
+
tmpHtml += '<div class="sync-section-header">Pushing</div>';
|
|
326
|
+
tmpHtml += '<table class="progress-table">';
|
|
327
|
+
tmpHtml += '<tr><th>Entity</th><th>Status</th><th>Progress</th><th>Pushed</th></tr>';
|
|
328
|
+
for (let i = 0; i < tmpPushing.length; i++)
|
|
329
|
+
{
|
|
330
|
+
tmpHtml += fRenderRow(tmpPushing[i].Name, tmpPushing[i].Data);
|
|
331
|
+
}
|
|
332
|
+
tmpHtml += '</table>';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (tmpPending.length > 0)
|
|
336
|
+
{
|
|
337
|
+
tmpHtml += '<div class="sync-section-header">Next Up <span class="sync-section-count">' + tmpPending.length + '</span></div>';
|
|
338
|
+
let tmpShowCount = Math.min(8, tmpPending.length);
|
|
339
|
+
tmpHtml += '<table class="progress-table progress-table-muted">';
|
|
340
|
+
for (let i = 0; i < tmpShowCount; i++)
|
|
341
|
+
{
|
|
342
|
+
tmpHtml += '<tr><td>' + tmpPending[i].Name + '</td>';
|
|
343
|
+
if (tmpPending[i].Data.Total > 0)
|
|
344
|
+
{
|
|
345
|
+
tmpHtml += '<td class="sync-pending-count">' + tmpSelf.pict.providers.ComprehensionLoader.formatNumber(tmpPending[i].Data.Total) + ' records</td>';
|
|
346
|
+
}
|
|
347
|
+
else
|
|
348
|
+
{
|
|
349
|
+
tmpHtml += '<td class="sync-pending-count">\u2014</td>';
|
|
350
|
+
}
|
|
351
|
+
tmpHtml += '</tr>';
|
|
352
|
+
}
|
|
353
|
+
tmpHtml += '</table>';
|
|
354
|
+
if (tmpPending.length > tmpShowCount)
|
|
355
|
+
{
|
|
356
|
+
tmpHtml += '<div class="sync-section-overflow">+ ' + (tmpPending.length - tmpShowCount) + ' more</div>';
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (tmpErrors.length > 0)
|
|
361
|
+
{
|
|
362
|
+
tmpHtml += '<div class="sync-section-header sync-section-header-error">Errors <span class="sync-section-count">' + tmpErrors.length + '</span></div>';
|
|
363
|
+
tmpHtml += '<table class="progress-table">';
|
|
364
|
+
tmpHtml += '<tr><th>Entity</th><th>Status</th><th>Progress</th><th>Pushed</th></tr>';
|
|
365
|
+
for (let i = 0; i < tmpErrors.length; i++)
|
|
366
|
+
{
|
|
367
|
+
tmpHtml += fRenderRow(tmpErrors[i].Name, tmpErrors[i].Data);
|
|
368
|
+
}
|
|
369
|
+
tmpHtml += '</table>';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (tmpCompleted.length > 0)
|
|
373
|
+
{
|
|
374
|
+
tmpHtml += '<div class="sync-section-header sync-section-header-ok">Completed <span class="sync-section-count">' + tmpCompleted.length + '</span></div>';
|
|
375
|
+
tmpHtml += '<table class="progress-table">';
|
|
376
|
+
tmpHtml += '<tr><th>Entity</th><th>Status</th><th>Progress</th><th>Pushed</th></tr>';
|
|
377
|
+
for (let i = 0; i < tmpCompleted.length; i++)
|
|
378
|
+
{
|
|
379
|
+
tmpHtml += fRenderRow(tmpCompleted[i].Name, tmpCompleted[i].Data);
|
|
380
|
+
}
|
|
381
|
+
tmpHtml += '</table>';
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
module.exports = ComprehensionLoaderLoadView;
|
|
389
|
+
|
|
390
|
+
module.exports.default_configuration =
|
|
391
|
+
{
|
|
392
|
+
ViewIdentifier: 'ComprehensionLoader-Load',
|
|
393
|
+
DefaultRenderable: 'ComprehensionLoader-Load',
|
|
394
|
+
DefaultDestinationAddress: '#ComprehensionLoader-Section-Load',
|
|
395
|
+
CSS: /*css*/`
|
|
396
|
+
.progress-table { width: 100%; border-collapse: collapse; margin-top: 4px; margin-bottom: 4px; }
|
|
397
|
+
.progress-table th, .progress-table td { text-align: left; padding: 6px 12px; border-bottom: 1px solid #eee; font-size: 0.9em; }
|
|
398
|
+
.progress-table th { background: #f8f9fa; font-weight: 600; }
|
|
399
|
+
.progress-table-muted td { color: #888; padding: 3px 12px; font-size: 0.85em; border-bottom: 1px solid #f4f5f6; }
|
|
400
|
+
.progress-bar-container { width: 120px; height: 16px; background: #e9ecef; border-radius: 8px; overflow: hidden; display: inline-block; vertical-align: middle; }
|
|
401
|
+
.progress-bar-fill { height: 100%; background: #28a745; transition: width 0.3s; }
|
|
402
|
+
.sync-section-header { font-size: 0.8em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: #4a90d9; padding: 10px 0 2px 0; margin-top: 6px; border-top: 1px solid #e0e0e0; }
|
|
403
|
+
.sync-section-header:first-child { border-top: none; margin-top: 10px; }
|
|
404
|
+
.sync-section-header-error { color: #dc3545; }
|
|
405
|
+
.sync-section-header-ok { color: #28a745; }
|
|
406
|
+
.sync-section-count { font-weight: 400; color: #999; font-size: 0.95em; }
|
|
407
|
+
.sync-section-overflow { font-size: 0.8em; color: #aaa; padding: 2px 12px 6px; }
|
|
408
|
+
.sync-pending-count { text-align: right; color: #aaa; font-size: 0.85em; }
|
|
409
|
+
.report-card { background: #f8f9fa; border-radius: 8px; padding: 12px 16px; min-width: 140px; text-align: center; border: 1px solid #e9ecef; }
|
|
410
|
+
.report-card .card-label { font-size: 0.8em; color: #666; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
|
|
411
|
+
.report-card .card-value { font-size: 1.4em; font-weight: 700; }
|
|
412
|
+
.report-card.outcome-success { border-left: 4px solid #28a745; }
|
|
413
|
+
.report-card.outcome-partial { border-left: 4px solid #ffc107; }
|
|
414
|
+
.report-card.outcome-error { border-left: 4px solid #dc3545; }
|
|
415
|
+
.report-card.outcome-stopped { border-left: 4px solid #6c757d; }
|
|
416
|
+
`,
|
|
417
|
+
Templates:
|
|
418
|
+
[
|
|
419
|
+
{
|
|
420
|
+
Hash: 'ComprehensionLoader-Load',
|
|
421
|
+
Template: /*html*/`
|
|
422
|
+
<div class="accordion-row">
|
|
423
|
+
<div class="accordion-number">4</div>
|
|
424
|
+
<div class="accordion-card" id="section4" data-section="4">
|
|
425
|
+
<div class="accordion-header" onclick="pict.views['ComprehensionLoader-Layout'].toggleSection('section4')">
|
|
426
|
+
<div class="accordion-title">Load</div>
|
|
427
|
+
<span class="accordion-phase" id="phase4"></span>
|
|
428
|
+
<div class="accordion-preview" id="preview4">Push comprehension data to the remote server</div>
|
|
429
|
+
<div class="accordion-actions">
|
|
430
|
+
<span class="accordion-go" onclick="event.stopPropagation(); pict.views['ComprehensionLoader-Load'].startLoad()">go</span>
|
|
431
|
+
<label class="accordion-auto" onclick="event.stopPropagation()"><input type="checkbox" id="auto4"> <span class="auto-label">auto</span></label>
|
|
432
|
+
</div>
|
|
433
|
+
<div class="accordion-toggle">▼</div>
|
|
434
|
+
</div>
|
|
435
|
+
<div class="accordion-body">
|
|
436
|
+
<div style="display:flex; gap:8px; margin-bottom:10px">
|
|
437
|
+
<button class="success" style="margin:0" onclick="pict.views['ComprehensionLoader-Load'].startLoad()">Start Load</button>
|
|
438
|
+
<button class="danger" style="margin:0" onclick="pict.views['ComprehensionLoader-Load'].stopLoad()">Stop Load</button>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<div id="loadStatus"></div>
|
|
442
|
+
<div id="loadProgress"></div>
|
|
443
|
+
|
|
444
|
+
<!-- Load Report (appears after load completes) -->
|
|
445
|
+
<div id="loadReportSection" style="display:none; margin-top:16px; padding-top:16px; border-top:2px solid #ddd">
|
|
446
|
+
<h3 style="margin:0 0 12px; font-size:1.1em">Load Report</h3>
|
|
447
|
+
|
|
448
|
+
<div id="reportSummaryCards" style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:16px"></div>
|
|
449
|
+
<div id="reportAnomalies" style="margin-bottom:16px"></div>
|
|
450
|
+
<div id="reportEntityDetails" style="margin-bottom:16px"></div>
|
|
451
|
+
|
|
452
|
+
<div style="display:flex; gap:8px">
|
|
453
|
+
<button class="secondary" onclick="pict.views['ComprehensionLoader-Load'].downloadReport()">Download Report JSON</button>
|
|
454
|
+
<button class="secondary" onclick="pict.views['ComprehensionLoader-Load'].copyReport()">Copy Report</button>
|
|
455
|
+
</div>
|
|
456
|
+
<div id="reportStatus"></div>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
`
|
|
462
|
+
}
|
|
463
|
+
],
|
|
464
|
+
Renderables:
|
|
465
|
+
[
|
|
466
|
+
{
|
|
467
|
+
RenderableHash: 'ComprehensionLoader-Load',
|
|
468
|
+
TemplateHash: 'ComprehensionLoader-Load',
|
|
469
|
+
DestinationAddress: '#ComprehensionLoader-Section-Load'
|
|
470
|
+
}
|
|
471
|
+
]
|
|
472
|
+
};
|
package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Schema.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class ComprehensionLoaderSchemaView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fetchSchema()
|
|
11
|
+
{
|
|
12
|
+
let tmpSchemaURL = document.getElementById('schemaURL').value.trim();
|
|
13
|
+
let tmpBody = {};
|
|
14
|
+
if (tmpSchemaURL)
|
|
15
|
+
{
|
|
16
|
+
tmpBody.SchemaURL = tmpSchemaURL;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.pict.providers.ComprehensionLoader.setSectionPhase(2, 'busy');
|
|
20
|
+
this.pict.providers.ComprehensionLoader.setStatus('schemaStatus', 'Fetching schema...', 'info');
|
|
21
|
+
|
|
22
|
+
this.pict.providers.ComprehensionLoader.api('POST', '/comprehension_load/schema/fetch', tmpBody)
|
|
23
|
+
.then(
|
|
24
|
+
(pData) =>
|
|
25
|
+
{
|
|
26
|
+
if (pData.Success)
|
|
27
|
+
{
|
|
28
|
+
this.pict.AppData.ComprehensionLoader.FetchedEntities = pData.Entities || [];
|
|
29
|
+
this.pict.providers.ComprehensionLoader.setStatus('schemaStatus', 'Fetched schema with ' + pData.EntityCount + ' entities from ' + pData.SchemaURL, 'ok');
|
|
30
|
+
this.pict.providers.ComprehensionLoader.setSectionPhase(2, 'ok');
|
|
31
|
+
this.renderEntityList();
|
|
32
|
+
this.pict.providers.ComprehensionLoader.updateAllPreviews();
|
|
33
|
+
}
|
|
34
|
+
else
|
|
35
|
+
{
|
|
36
|
+
this.pict.providers.ComprehensionLoader.setStatus('schemaStatus', 'Fetch failed: ' + (pData.Error || 'Unknown error'), 'error');
|
|
37
|
+
this.pict.providers.ComprehensionLoader.setSectionPhase(2, 'error');
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
.catch(
|
|
41
|
+
(pError) =>
|
|
42
|
+
{
|
|
43
|
+
this.pict.providers.ComprehensionLoader.setStatus('schemaStatus', 'Request failed: ' + pError.message, 'error');
|
|
44
|
+
this.pict.providers.ComprehensionLoader.setSectionPhase(2, 'error');
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
renderEntityList()
|
|
49
|
+
{
|
|
50
|
+
let tmpEntities = this.pict.AppData.ComprehensionLoader.FetchedEntities || [];
|
|
51
|
+
let tmpContainer = document.getElementById('entityList');
|
|
52
|
+
if (!tmpContainer) return;
|
|
53
|
+
|
|
54
|
+
if (tmpEntities.length === 0)
|
|
55
|
+
{
|
|
56
|
+
tmpContainer.innerHTML = '<div style="color:#888; font-size:0.9em">No entities found.</div>';
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let tmpHtml = '<div style="font-size:0.9em; color:#555">';
|
|
61
|
+
for (let i = 0; i < tmpEntities.length; i++)
|
|
62
|
+
{
|
|
63
|
+
tmpHtml += '<span style="display:inline-block; background:#f0f0f0; border-radius:4px; padding:2px 8px; margin:2px 4px 2px 0; font-size:0.9em">';
|
|
64
|
+
tmpHtml += this.pict.providers.ComprehensionLoader.escapeHtml(tmpEntities[i]);
|
|
65
|
+
tmpHtml += '</span>';
|
|
66
|
+
}
|
|
67
|
+
tmpHtml += '</div>';
|
|
68
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = ComprehensionLoaderSchemaView;
|
|
73
|
+
|
|
74
|
+
module.exports.default_configuration =
|
|
75
|
+
{
|
|
76
|
+
ViewIdentifier: 'ComprehensionLoader-Schema',
|
|
77
|
+
DefaultRenderable: 'ComprehensionLoader-Schema',
|
|
78
|
+
DefaultDestinationAddress: '#ComprehensionLoader-Section-Schema',
|
|
79
|
+
Templates:
|
|
80
|
+
[
|
|
81
|
+
{
|
|
82
|
+
Hash: 'ComprehensionLoader-Schema',
|
|
83
|
+
Template: /*html*/`
|
|
84
|
+
<div class="accordion-row">
|
|
85
|
+
<div class="accordion-number">2</div>
|
|
86
|
+
<div class="accordion-card" id="section2" data-section="2">
|
|
87
|
+
<div class="accordion-header" onclick="pict.views['ComprehensionLoader-Layout'].toggleSection('section2')">
|
|
88
|
+
<div class="accordion-title">Remote Schema</div>
|
|
89
|
+
<span class="accordion-phase" id="phase2"></span>
|
|
90
|
+
<div class="accordion-preview" id="preview2">Fetch entity schema from the remote server</div>
|
|
91
|
+
<div class="accordion-actions">
|
|
92
|
+
<span class="accordion-go" onclick="event.stopPropagation(); pict.views['ComprehensionLoader-Schema'].fetchSchema()">go</span>
|
|
93
|
+
<label class="accordion-auto" onclick="event.stopPropagation()"><input type="checkbox" id="auto2"> <span class="auto-label">auto</span></label>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="accordion-toggle">▼</div>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="accordion-body">
|
|
98
|
+
<label for="schemaURL">Schema URL (leave blank for default: ServerURL + Retold/Models)</label>
|
|
99
|
+
<input type="text" id="schemaURL" placeholder="http://remote-server:8086/1.0/Retold/Models">
|
|
100
|
+
|
|
101
|
+
<button class="primary" onclick="pict.views['ComprehensionLoader-Schema'].fetchSchema()">Fetch Schema</button>
|
|
102
|
+
<div id="schemaStatus"></div>
|
|
103
|
+
|
|
104
|
+
<div id="entityList" style="margin-top:12px"></div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
`
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
Renderables:
|
|
112
|
+
[
|
|
113
|
+
{
|
|
114
|
+
RenderableHash: 'ComprehensionLoader-Schema',
|
|
115
|
+
TemplateHash: 'ComprehensionLoader-Schema',
|
|
116
|
+
DestinationAddress: '#ComprehensionLoader-Section-Schema'
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
};
|