retold-data-service 2.0.18 → 2.0.20

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.
@@ -4892,7 +4892,7 @@
4892
4892
  StatusDetailTimer: null,
4893
4893
  StatusDetailData: null,
4894
4894
  LastLiveStatus: null,
4895
- PersistFields: ['serverURL', 'authMethod', 'authURI', 'checkURI', 'cookieName', 'cookieValueAddr', 'cookieValueTemplate', 'loginMarker', 'userName', 'password', 'schemaURL', 'pageSize', 'dateTimePrecisionMS', 'connProvider', 'sqliteFilePath', 'mysqlServer', 'mysqlPort', 'mysqlUser', 'mysqlPassword', 'mysqlDatabase', 'mysqlConnectionLimit', 'mssqlServer', 'mssqlPort', 'mssqlUser', 'mssqlPassword', 'mssqlDatabase', 'mssqlConnectionLimit', 'postgresqlHost', 'postgresqlPort', 'postgresqlUser', 'postgresqlPassword', 'postgresqlDatabase', 'postgresqlConnectionLimit', 'solrHost', 'solrPort', 'solrCore', 'solrPath', 'mongodbHost', 'mongodbPort', 'mongodbUser', 'mongodbPassword', 'mongodbDatabase', 'mongodbConnectionLimit', 'rocksdbFolder', 'bibliographFolder']
4895
+ PersistFields: ['serverURL', 'authMethod', 'authURI', 'checkURI', 'cookieName', 'cookieValueAddr', 'cookieValueTemplate', 'loginMarker', 'userName', 'password', 'schemaURL', 'pageSize', 'dateTimePrecisionMS', 'connProvider', 'sqliteFilePath', 'mysqlServer', 'mysqlPort', 'mysqlUser', 'mysqlPassword', 'mysqlDatabase', 'mysqlConnectionLimit', 'mssqlServer', 'mssqlPort', 'mssqlUser', 'mssqlPassword', 'mssqlDatabase', 'mssqlConnectionLimit', 'postgresqlHost', 'postgresqlPort', 'postgresqlUser', 'postgresqlPassword', 'postgresqlDatabase', 'postgresqlConnectionLimit', 'solrHost', 'solrPort', 'solrCore', 'solrPath', 'mongodbHost', 'mongodbPort', 'mongodbUser', 'mongodbPassword', 'mongodbDatabase', 'mongodbConnectionLimit', 'rocksdbFolder', 'bibliographFolder', 'syncMaxRecords']
4896
4896
  };
4897
4897
 
4898
4898
  // Make pict available for inline onclick handlers
@@ -5161,6 +5161,11 @@
5161
5161
  if (tmpSolrSecure !== null) {
5162
5162
  document.getElementById('solrSecure').checked = tmpSolrSecure === 'true';
5163
5163
  }
5164
+ // Restore advanced ID pagination checkbox
5165
+ let tmpAdvancedIDPagination = localStorage.getItem('dataCloner_syncAdvancedIDPagination');
5166
+ if (tmpAdvancedIDPagination !== null) {
5167
+ document.getElementById('syncAdvancedIDPagination').checked = tmpAdvancedIDPagination === 'true';
5168
+ }
5164
5169
  }
5165
5170
  initPersistence() {
5166
5171
  let tmpSelf = this;
@@ -5203,6 +5208,14 @@
5203
5208
  });
5204
5209
  }
5205
5210
 
5211
+ // Persist advanced ID pagination checkbox
5212
+ let tmpAdvancedIDPaginationEl = document.getElementById('syncAdvancedIDPagination');
5213
+ if (tmpAdvancedIDPaginationEl) {
5214
+ tmpAdvancedIDPaginationEl.addEventListener('change', function () {
5215
+ localStorage.setItem('dataCloner_syncAdvancedIDPagination', this.checked);
5216
+ });
5217
+ }
5218
+
5206
5219
  // Persist auto-process checkboxes
5207
5220
  let tmpAutoIds = ['auto1', 'auto2', 'auto3', 'auto4', 'auto5'];
5208
5221
  for (let a = 0; a < tmpAutoIds.length; a++) {
@@ -5287,7 +5300,8 @@
5287
5300
  tmpMetaParts.push('<span class="live-status-meta-item">' + tmpGrandTotal + ' records to sync</span>');
5288
5301
  }
5289
5302
  if (pData.PreCountProgress && pData.PreCountProgress.Counted < pData.PreCountProgress.TotalTables) {
5290
- tmpMetaParts.push('<span class="live-status-meta-item">counting: ' + pData.PreCountProgress.Counted + ' / ' + pData.PreCountProgress.TotalTables + '</span>');
5303
+ let tmpCountedSoFar = pData.PreCountGrandTotal > 0 ? ' (' + pData.PreCountGrandTotal.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' records found)' : '';
5304
+ tmpMetaParts.push('<span class="live-status-meta-item">counting: ' + pData.PreCountProgress.Counted + ' / ' + pData.PreCountProgress.TotalTables + ' tables' + tmpCountedSoFar + '</span>');
5291
5305
  }
5292
5306
  if (pData.Errors > 0) {
5293
5307
  tmpMetaParts.push('<span class="live-status-meta-item" style="color:#dc3545"><strong>' + pData.Errors + '</strong> error' + (pData.Errors === 1 ? '' : 's') + '</span>');
@@ -5302,7 +5316,10 @@
5302
5316
 
5303
5317
  // Update progress bar
5304
5318
  let tmpPct = 0;
5305
- if (pData.Phase === 'syncing' && pData.PreCountGrandTotal > 0 && pData.TotalSynced > 0) {
5319
+ if (pData.Phase === 'syncing' && pData.PreCountProgress && pData.PreCountProgress.Counted < pData.PreCountProgress.TotalTables) {
5320
+ // During counting phase, show table counting progress
5321
+ tmpPct = Math.min(pData.PreCountProgress.Counted / pData.PreCountProgress.TotalTables * 100, 99);
5322
+ } else if (pData.Phase === 'syncing' && pData.PreCountGrandTotal > 0 && pData.TotalSynced > 0) {
5306
5323
  tmpPct = Math.min(pData.TotalSynced / pData.PreCountGrandTotal * 100, 99.9);
5307
5324
  } else if (pData.Phase === 'syncing' && pData.TotalTables > 0) {
5308
5325
  let tmpTablePct = pData.Completed / pData.TotalTables * 100;
@@ -5317,6 +5334,14 @@
5317
5334
  }
5318
5335
  tmpProgressFill.style.width = Math.min(100, Math.round(tmpPct)) + '%';
5319
5336
 
5337
+ // Auto-expand the detail view when sync starts so users see counting progress
5338
+ if ((pData.Phase === 'syncing' || pData.Phase === 'stopping') && !this.pict.AppData.DataCloner.StatusDetailExpanded) {
5339
+ let tmpLayoutView = this.pict.views['DataCloner-Layout'];
5340
+ if (tmpLayoutView && typeof tmpLayoutView.toggleStatusDetail === 'function') {
5341
+ tmpLayoutView.toggleStatusDetail();
5342
+ }
5343
+ }
5344
+
5320
5345
  // If the detail view is expanded, re-render it with fresh data
5321
5346
  if (this.pict.AppData.DataCloner.StatusDetailExpanded) {
5322
5347
  this.renderStatusDetail();
@@ -5370,6 +5395,49 @@
5370
5395
  tmpSelf.renderStatusDetail();
5371
5396
  }).catch(function () {/* ignore poll errors */});
5372
5397
  }
5398
+ renderCountingPhaseDetail(pContainer, pPreCountProgress) {
5399
+ let tmpTables = pPreCountProgress.Tables || [];
5400
+ let tmpCounted = pPreCountProgress.Counted || 0;
5401
+ let tmpTotal = pPreCountProgress.TotalTables || 0;
5402
+ let tmpRunningTotal = 0;
5403
+ let tmpHtml = '<div class="status-detail-section">';
5404
+ tmpHtml += '<div class="status-detail-section-title">Counting Records (' + tmpCounted + ' / ' + tmpTotal + ' tables)</div>';
5405
+ if (tmpTables.length > 0) {
5406
+ tmpHtml += '<table class="precount-table">';
5407
+ tmpHtml += '<thead><tr><th>Table</th><th style="text-align:right">Records</th><th style="text-align:right">Time</th></tr></thead>';
5408
+ tmpHtml += '<tbody>';
5409
+ for (let i = 0; i < tmpTables.length; i++) {
5410
+ let tmpT = tmpTables[i];
5411
+ tmpRunningTotal += tmpT.Count;
5412
+ let tmpCountFmt = this.formatNumber(tmpT.Count);
5413
+ let tmpTimeFmt = tmpT.ElapsedMs < 1000 ? tmpT.ElapsedMs + 'ms' : (tmpT.ElapsedMs / 1000).toFixed(1) + 's';
5414
+ let tmpRowClass = tmpT.Error ? ' class="precount-error"' : '';
5415
+ tmpHtml += '<tr' + tmpRowClass + '>';
5416
+ tmpHtml += '<td>' + this.escapeHtml(tmpT.Name) + '</td>';
5417
+ tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums">' + tmpCountFmt + '</td>';
5418
+ tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums; color:#888">' + tmpTimeFmt + '</td>';
5419
+ tmpHtml += '</tr>';
5420
+ }
5421
+ tmpHtml += '</tbody>';
5422
+ tmpHtml += '<tfoot><tr>';
5423
+ tmpHtml += '<td><strong>Total</strong></td>';
5424
+ tmpHtml += '<td style="text-align:right; font-variant-numeric:tabular-nums"><strong>' + this.formatNumber(tmpRunningTotal) + '</strong></td>';
5425
+ tmpHtml += '<td></td>';
5426
+ tmpHtml += '</tr></tfoot>';
5427
+ tmpHtml += '</table>';
5428
+ }
5429
+
5430
+ // Show pending indicator for remaining tables
5431
+ let tmpRemaining = tmpTotal - tmpCounted;
5432
+ if (tmpRemaining > 0) {
5433
+ tmpHtml += '<div class="precount-pending">';
5434
+ tmpHtml += '<span class="precount-spinner"></span> ';
5435
+ tmpHtml += tmpRemaining + ' table' + (tmpRemaining === 1 ? '' : 's') + ' remaining…';
5436
+ tmpHtml += '</div>';
5437
+ }
5438
+ tmpHtml += '</div>';
5439
+ pContainer.innerHTML = tmpHtml;
5440
+ }
5373
5441
  renderStatusDetail() {
5374
5442
  let tmpContainer = document.getElementById('DataCloner-StatusDetail-Container');
5375
5443
  if (!tmpContainer) return;
@@ -5378,6 +5446,15 @@
5378
5446
  let tmpStatusData = tmpAppData.StatusDetailData;
5379
5447
  let tmpReport = tmpAppData.LastReport;
5380
5448
 
5449
+ // During the counting phase, show per-table counts as they arrive
5450
+ if (tmpLiveStatus && tmpLiveStatus.PreCountProgress && tmpLiveStatus.PreCountProgress.Tables && tmpLiveStatus.Phase === 'syncing' && tmpLiveStatus.PreCountProgress.Counted < tmpLiveStatus.PreCountProgress.TotalTables) {
5451
+ this.renderCountingPhaseDetail(tmpContainer, tmpLiveStatus.PreCountProgress);
5452
+ // Hide histogram during counting
5453
+ let tmpHistContainer = document.getElementById('DataCloner-Throughput-Histogram');
5454
+ if (tmpHistContainer) tmpHistContainer.style.display = 'none';
5455
+ return;
5456
+ }
5457
+
5381
5458
  // Determine data source: live during sync, report after sync
5382
5459
  let tmpTables = {};
5383
5460
  let tmpThroughputSamples = [];
@@ -6266,6 +6343,7 @@
6266
6343
  if (!isNaN(tmpPrecision) && tmpPrecision !== 1000) tmpConfig.Sync.DateTimePrecisionMS = tmpPrecision;
6267
6344
  let tmpMaxRecords = parseInt(document.getElementById('syncMaxRecords').value, 10);
6268
6345
  if (tmpMaxRecords > 0) tmpConfig.Sync.MaxRecords = tmpMaxRecords;
6346
+ if (document.getElementById('syncAdvancedIDPagination').checked) tmpConfig.Sync.UseAdvancedIDPagination = true;
6269
6347
  return tmpConfig;
6270
6348
  }
6271
6349
  buildMeadowIntegrationConfig() {
@@ -6332,6 +6410,7 @@
6332
6410
  let tmpSelectedTables = this.pict.views['DataCloner-Schema'].getSelectedTables();
6333
6411
  tmpConfig.Sync.SyncEntityList = tmpSelectedTables.length > 0 ? tmpSelectedTables : [];
6334
6412
  tmpConfig.Sync.SyncEntityOptions = {};
6413
+ if (document.getElementById('syncAdvancedIDPagination').checked) tmpConfig.Sync.UseAdvancedIDPagination = true;
6335
6414
 
6336
6415
  // ---- SessionManager ----
6337
6416
  tmpConfig.SessionManager = {
@@ -6909,6 +6988,34 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
6909
6988
  font-size: 0.78em; color: #888; margin-top: 4px; padding-left: 18px;
6910
6989
  font-family: monospace; max-height: 80px; overflow-y: auto;
6911
6990
  }
6991
+
6992
+ /* Pre-count Table */
6993
+ .precount-table {
6994
+ width: 100%; border-collapse: collapse; font-size: 0.88em;
6995
+ }
6996
+ .precount-table thead th {
6997
+ text-align: left; font-weight: 600; color: #555; font-size: 0.85em;
6998
+ text-transform: uppercase; letter-spacing: 0.3px;
6999
+ padding: 4px 8px 6px; border-bottom: 2px solid #e9ecef;
7000
+ }
7001
+ .precount-table tbody td {
7002
+ padding: 4px 8px; border-bottom: 1px solid #f0f0f0;
7003
+ }
7004
+ .precount-table tbody tr:last-child td { border-bottom: 1px solid #e9ecef; }
7005
+ .precount-table tfoot td {
7006
+ padding: 6px 8px 2px; font-size: 0.95em;
7007
+ }
7008
+ .precount-error td { color: #dc3545; }
7009
+ .precount-pending {
7010
+ color: #888; font-size: 0.85em; font-style: italic; padding: 8px 0 2px;
7011
+ display: flex; align-items: center; gap: 6px;
7012
+ }
7013
+ .precount-spinner {
7014
+ display: inline-block; width: 12px; height: 12px;
7015
+ border: 2px solid #ddd; border-top-color: #4a90d9;
7016
+ border-radius: 50%; animation: precount-spin 0.8s linear infinite;
7017
+ }
7018
+ @keyframes precount-spin { to { transform: rotate(360deg); } }
6912
7019
  `,
6913
7020
  Templates: [{
6914
7021
  Hash: 'DataCloner-Layout',
@@ -7353,6 +7460,7 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
7353
7460
  let tmpSyncMode = document.querySelector('input[name="syncMode"]:checked').value;
7354
7461
  let tmpMaxRecords = parseInt(document.getElementById('syncMaxRecords').value, 10) || 0;
7355
7462
  let tmpLogToFile = document.getElementById('syncLogFile').checked;
7463
+ let tmpAdvancedIDPagination = document.getElementById('syncAdvancedIDPagination').checked;
7356
7464
  if (tmpSelectedTables.length === 0) {
7357
7465
  this.pict.providers.DataCloner.setStatus('syncStatus', 'No tables selected for sync.', 'error');
7358
7466
  this.pict.providers.DataCloner.setSectionPhase(5, 'error');
@@ -7370,6 +7478,7 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
7370
7478
  };
7371
7479
  if (tmpMaxRecords > 0) tmpPostBody.MaxRecordsPerEntity = tmpMaxRecords;
7372
7480
  if (tmpLogToFile) tmpPostBody.LogToFile = true;
7481
+ if (tmpAdvancedIDPagination) tmpPostBody.UseAdvancedIDPagination = true;
7373
7482
  this.pict.providers.DataCloner.api('POST', '/clone/sync/start', tmpPostBody).then(function (pData) {
7374
7483
  if (pData.Success) {
7375
7484
  let tmpMsg = pData.SyncMode + ' sync started for ' + pData.Tables.length + ' tables.';
@@ -7760,6 +7869,11 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
7760
7869
  <label for="syncDeletedRecords">Sync deleted records (fetch records marked Deleted=1 on source and mirror locally)</label>
7761
7870
  </div>
7762
7871
 
7872
+ <div class="checkbox-row">
7873
+ <input type="checkbox" id="syncAdvancedIDPagination">
7874
+ <label for="syncAdvancedIDPagination">Use advanced ID pagination (faster for large tables; uses keyset pagination instead of OFFSET)</label>
7875
+ </div>
7876
+
7763
7877
  <div class="inline-group" style="margin-top:8px; margin-bottom:4px">
7764
7878
  <div style="flex:0 0 200px">
7765
7879
  <label for="syncMaxRecords">Max Records per Entity</label>