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.
Files changed (37) hide show
  1. package/.quackage-comprehension-loader.json +19 -0
  2. package/bin/retold-data-service-clone.js +4 -1
  3. package/generate-bookstore-comprehension.js +645 -0
  4. package/package.json +7 -7
  5. package/source/Retold-Data-Service.js +30 -2
  6. package/source/services/comprehension-loader/ComprehensionLoader-Command-Load.js +345 -0
  7. package/source/services/comprehension-loader/ComprehensionLoader-Command-Schema.js +97 -0
  8. package/source/services/comprehension-loader/ComprehensionLoader-Command-Session.js +221 -0
  9. package/source/services/comprehension-loader/ComprehensionLoader-Command-WebUI.js +57 -0
  10. package/source/services/comprehension-loader/Retold-Data-Service-ComprehensionLoader.js +536 -0
  11. package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader-Configuration.json +9 -0
  12. package/source/services/comprehension-loader/pict-app/Pict-Application-ComprehensionLoader.js +86 -0
  13. package/source/services/comprehension-loader/pict-app/Pict-ComprehensionLoader-Bundle.js +6 -0
  14. package/source/services/comprehension-loader/pict-app/providers/Pict-Provider-ComprehensionLoader.js +760 -0
  15. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Layout.js +360 -0
  16. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Load.js +472 -0
  17. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Schema.js +119 -0
  18. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Session.js +269 -0
  19. package/source/services/comprehension-loader/pict-app/views/PictView-ComprehensionLoader-Source.js +330 -0
  20. package/source/services/comprehension-loader/web/comprehension-loader.js +6794 -0
  21. package/source/services/comprehension-loader/web/comprehension-loader.js.map +1 -0
  22. package/source/services/comprehension-loader/web/comprehension-loader.min.js +2 -0
  23. package/source/services/comprehension-loader/web/comprehension-loader.min.js.map +1 -0
  24. package/source/services/comprehension-loader/web/index.html +17 -0
  25. package/source/services/data-cloner/DataCloner-Command-Schema.js +407 -15
  26. package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +59 -1
  27. package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +1 -0
  28. package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +125 -5
  29. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +18 -8
  30. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +104 -1
  31. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +1 -1
  32. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +12 -0
  33. package/source/services/data-cloner/web/data-cloner.js +201 -139
  34. package/source/services/data-cloner/web/data-cloner.js.map +1 -1
  35. package/source/services/data-cloner/web/data-cloner.min.js +1 -1
  36. package/source/services/data-cloner/web/data-cloner.min.js.map +1 -1
  37. package/test/RetoldDataService_tests.js +225 -0
@@ -83,7 +83,7 @@ class DataClonerProvider extends libPictProvider
83
83
  let tmpPreview1 = tmpProvider;
84
84
  if (tmpProvider === 'SQLite')
85
85
  {
86
- let tmpPath = document.getElementById('sqliteFilePath').value || 'data/cloned.sqlite';
86
+ let tmpPath = document.getElementById('sqliteFilePath').value || '~/headlight-liveconnect-local/cloned.sqlite';
87
87
  tmpPreview1 = 'SQLite at ' + tmpPath;
88
88
  }
89
89
  else if (tmpProvider === 'MySQL')
@@ -121,12 +121,12 @@ class DataClonerProvider extends libPictProvider
121
121
  }
122
122
  else if (tmpProvider === 'RocksDB')
123
123
  {
124
- let tmpFolder = document.getElementById('rocksdbFolder').value || 'data/rocksdb';
124
+ let tmpFolder = document.getElementById('rocksdbFolder').value || '~/headlight-liveconnect-local/rocksdb';
125
125
  tmpPreview1 = 'RocksDB at ' + tmpFolder;
126
126
  }
127
127
  else if (tmpProvider === 'Bibliograph')
128
128
  {
129
- let tmpFolder = document.getElementById('bibliographFolder').value || 'data/bibliograph';
129
+ let tmpFolder = document.getElementById('bibliographFolder').value || '~/headlight-liveconnect-local/bibliograph';
130
130
  tmpPreview1 = 'Bibliograph at ' + tmpFolder;
131
131
  }
132
132
  document.getElementById('preview1').textContent = tmpPreview1;
@@ -513,9 +513,11 @@ class DataClonerProvider extends libPictProvider
513
513
  }
514
514
  tmpProgressFill.style.width = Math.min(100, Math.round(tmpPct)) + '%';
515
515
 
516
- // Auto-expand the detail view when sync starts so users see counting progress
517
- if ((pData.Phase === 'syncing' || pData.Phase === 'stopping') && !this.pict.AppData.DataCloner.StatusDetailExpanded)
516
+ // Auto-expand the detail view once when sync first starts so users see counting progress.
517
+ // Only expand once per sync run if the user collapses it, respect that choice.
518
+ if ((pData.Phase === 'syncing' || pData.Phase === 'stopping') && !this.pict.AppData.DataCloner.StatusDetailExpanded && !this.pict.AppData.DataCloner.StatusDetailAutoExpanded)
518
519
  {
520
+ this.pict.AppData.DataCloner.StatusDetailAutoExpanded = true;
519
521
  let tmpLayoutView = this.pict.views['DataCloner-Layout'];
520
522
  if (tmpLayoutView && typeof tmpLayoutView.toggleStatusDetail === 'function')
521
523
  {
@@ -523,6 +525,13 @@ class DataClonerProvider extends libPictProvider
523
525
  }
524
526
  }
525
527
 
528
+ // Reset the auto-expand flag when the sync is no longer running,
529
+ // so the next sync run will auto-expand again.
530
+ if (pData.Phase !== 'syncing' && pData.Phase !== 'stopping')
531
+ {
532
+ this.pict.AppData.DataCloner.StatusDetailAutoExpanded = false;
533
+ }
534
+
526
535
  // If the detail view is expanded, re-render it with fresh data
527
536
  if (this.pict.AppData.DataCloner.StatusDetailExpanded)
528
537
  {
@@ -662,6 +671,12 @@ class DataClonerProvider extends libPictProvider
662
671
  && tmpLiveStatus.PreCountProgress.Counted < tmpLiveStatus.PreCountProgress.TotalTables)
663
672
  {
664
673
  this.renderCountingPhaseDetail(tmpContainer, tmpLiveStatus.PreCountProgress);
674
+ // Prepend the summary banner above the counting detail
675
+ let tmpSummaryBanner = this.buildStatusSummaryHtml();
676
+ if (tmpSummaryBanner)
677
+ {
678
+ tmpContainer.innerHTML = tmpSummaryBanner + tmpContainer.innerHTML;
679
+ }
665
680
  // Hide histogram during counting
666
681
  let tmpHistContainer = document.getElementById('DataCloner-Throughput-Histogram');
667
682
  if (tmpHistContainer) tmpHistContainer.style.display = 'none';
@@ -732,6 +747,9 @@ class DataClonerProvider extends libPictProvider
732
747
 
733
748
  let tmpHtml = '';
734
749
 
750
+ // === Summary Banner (mirrors collapsed bar counters) ===
751
+ tmpHtml += this.buildStatusSummaryHtml();
752
+
735
753
  // === Section 1: Running Operations ===
736
754
  if (tmpRunning.length > 0 || tmpPending.length > 0)
737
755
  {
@@ -875,6 +893,108 @@ class DataClonerProvider extends libPictProvider
875
893
  }
876
894
  }
877
895
 
896
+ buildStatusSummaryHtml()
897
+ {
898
+ let tmpLiveStatus = this.pict.AppData.DataCloner.LastLiveStatus;
899
+ let tmpReport = this.pict.AppData.DataCloner.LastReport;
900
+
901
+ let tmpParts = [];
902
+ let tmpMessage = '';
903
+
904
+ if (tmpLiveStatus && (tmpLiveStatus.Phase === 'syncing' || tmpLiveStatus.Phase === 'stopping'))
905
+ {
906
+ tmpMessage = tmpLiveStatus.Message || '';
907
+ if (tmpLiveStatus.Elapsed)
908
+ {
909
+ tmpParts.push('<span class="live-status-meta-item">\u23F1 ' + this.escapeHtml(tmpLiveStatus.Elapsed) + '</span>');
910
+ }
911
+ if (tmpLiveStatus.ETA)
912
+ {
913
+ tmpParts.push('<span class="live-status-meta-item">~' + this.escapeHtml(tmpLiveStatus.ETA) + ' remaining</span>');
914
+ }
915
+ if (tmpLiveStatus.TotalTables > 0)
916
+ {
917
+ tmpParts.push('<span class="live-status-meta-item"><strong>' + tmpLiveStatus.Completed + '</strong> / ' + tmpLiveStatus.TotalTables + ' tables</span>');
918
+ }
919
+ if (tmpLiveStatus.TotalSynced > 0)
920
+ {
921
+ let tmpSynced = this.formatNumber(tmpLiveStatus.TotalSynced);
922
+ if (tmpLiveStatus.PreCountGrandTotal > 0)
923
+ {
924
+ let tmpGrandTotal = this.formatNumber(tmpLiveStatus.PreCountGrandTotal);
925
+ tmpParts.push('<span class="live-status-meta-item"><strong>' + tmpSynced + '</strong> / ' + tmpGrandTotal + ' records</span>');
926
+ }
927
+ else
928
+ {
929
+ tmpParts.push('<span class="live-status-meta-item"><strong>' + tmpSynced + '</strong> records</span>');
930
+ }
931
+ }
932
+ else if (tmpLiveStatus.PreCountGrandTotal > 0)
933
+ {
934
+ let tmpGrandTotal = this.formatNumber(tmpLiveStatus.PreCountGrandTotal);
935
+ tmpParts.push('<span class="live-status-meta-item">' + tmpGrandTotal + ' records to sync</span>');
936
+ }
937
+ if (tmpLiveStatus.PreCountProgress && tmpLiveStatus.PreCountProgress.Counted < tmpLiveStatus.PreCountProgress.TotalTables)
938
+ {
939
+ let tmpCountedSoFar = tmpLiveStatus.PreCountGrandTotal > 0
940
+ ? ' (' + this.formatNumber(tmpLiveStatus.PreCountGrandTotal) + ' records found)'
941
+ : '';
942
+ tmpParts.push('<span class="live-status-meta-item">counting: ' + tmpLiveStatus.PreCountProgress.Counted + ' / ' + tmpLiveStatus.PreCountProgress.TotalTables + ' tables' + tmpCountedSoFar + '</span>');
943
+ }
944
+ if (tmpLiveStatus.Errors > 0)
945
+ {
946
+ tmpParts.push('<span class="live-status-meta-item" style="color:#dc3545"><strong>' + tmpLiveStatus.Errors + '</strong> error' + (tmpLiveStatus.Errors === 1 ? '' : 's') + '</span>');
947
+ }
948
+ }
949
+ else if (tmpLiveStatus && tmpLiveStatus.Phase === 'complete')
950
+ {
951
+ tmpMessage = tmpLiveStatus.Message || 'Sync complete';
952
+ if (tmpLiveStatus.Elapsed)
953
+ {
954
+ tmpParts.push('<span class="live-status-meta-item">\u23F1 ' + this.escapeHtml(tmpLiveStatus.Elapsed) + '</span>');
955
+ }
956
+ if (tmpLiveStatus.TotalSynced > 0)
957
+ {
958
+ let tmpSynced = this.formatNumber(tmpLiveStatus.TotalSynced);
959
+ tmpParts.push('<span class="live-status-meta-item"><strong>' + tmpSynced + '</strong> records synced</span>');
960
+ }
961
+ }
962
+ else if (tmpReport && tmpReport.ReportVersion)
963
+ {
964
+ tmpMessage = 'Sync ' + (tmpReport.Outcome || 'complete').toLowerCase();
965
+ if (tmpReport.RunTimestamps && tmpReport.RunTimestamps.DurationSeconds)
966
+ {
967
+ tmpParts.push('<span class="live-status-meta-item">\u23F1 ' + this.formatElapsed(tmpReport.RunTimestamps.DurationSeconds) + '</span>');
968
+ }
969
+ if (tmpReport.Summary)
970
+ {
971
+ if (tmpReport.Summary.TotalSynced > 0)
972
+ {
973
+ tmpParts.push('<span class="live-status-meta-item"><strong>' + this.formatNumber(tmpReport.Summary.TotalSynced) + '</strong> records synced</span>');
974
+ }
975
+ tmpParts.push('<span class="live-status-meta-item"><strong>' + tmpReport.Summary.TotalTables + '</strong> tables</span>');
976
+ if (tmpReport.Summary.TotalErrors > 0)
977
+ {
978
+ tmpParts.push('<span class="live-status-meta-item" style="color:#dc3545"><strong>' + tmpReport.Summary.TotalErrors + '</strong> error' + (tmpReport.Summary.TotalErrors === 1 ? '' : 's') + '</span>');
979
+ }
980
+ }
981
+ }
982
+
983
+ if (!tmpMessage && tmpParts.length === 0) return '';
984
+
985
+ let tmpHtml = '<div class="status-detail-summary">';
986
+ if (tmpMessage)
987
+ {
988
+ tmpHtml += '<div class="status-detail-summary-message">' + this.escapeHtml(tmpMessage) + '</div>';
989
+ }
990
+ if (tmpParts.length > 0)
991
+ {
992
+ tmpHtml += '<div class="status-detail-summary-counters">' + tmpParts.join('') + '</div>';
993
+ }
994
+ tmpHtml += '</div>';
995
+ return tmpHtml;
996
+ }
997
+
878
998
  formatElapsed(pSec)
879
999
  {
880
1000
  if (pSec < 60) return pSec + 's';
@@ -29,7 +29,7 @@ class DataClonerConnectionView extends libPictView
29
29
 
30
30
  if (tmpProvider === 'SQLite')
31
31
  {
32
- tmpConfig.SQLiteFilePath = document.getElementById('sqliteFilePath').value.trim() || 'data/cloned.sqlite';
32
+ tmpConfig.SQLiteFilePath = document.getElementById('sqliteFilePath').value.trim() || '~/headlight-liveconnect-local/cloned.sqlite';
33
33
  }
34
34
  else if (tmpProvider === 'MySQL')
35
35
  {
@@ -89,31 +89,41 @@ class DataClonerConnectionView extends libPictView
89
89
 
90
90
  connectProvider()
91
91
  {
92
+ // Guard against re-entrant calls (e.g. rapid auto-connect polling)
93
+ if (this._connectInFlight)
94
+ {
95
+ return;
96
+ }
97
+ this._connectInFlight = true;
98
+
92
99
  let tmpConnInfo = this.getProviderConfig();
93
100
 
94
101
  this.pict.providers.DataCloner.setSectionPhase(1, 'busy');
95
102
  this.pict.providers.DataCloner.setStatus('connectionStatus', 'Connecting to ' + tmpConnInfo.Provider + '...', 'info');
96
103
 
104
+ let tmpSelf = this;
97
105
  this.pict.providers.DataCloner.api('POST', '/clone/connection/configure', tmpConnInfo)
98
106
  .then(
99
107
  (pData) =>
100
108
  {
109
+ tmpSelf._connectInFlight = false;
101
110
  if (pData.Success)
102
111
  {
103
- this.pict.providers.DataCloner.setStatus('connectionStatus', pData.Message, 'ok');
104
- this.pict.providers.DataCloner.setSectionPhase(1, 'ok');
112
+ tmpSelf.pict.providers.DataCloner.setStatus('connectionStatus', pData.Message, 'ok');
113
+ tmpSelf.pict.providers.DataCloner.setSectionPhase(1, 'ok');
105
114
  }
106
115
  else
107
116
  {
108
- this.pict.providers.DataCloner.setStatus('connectionStatus', 'Connection failed: ' + (pData.Error || 'Unknown error'), 'error');
109
- this.pict.providers.DataCloner.setSectionPhase(1, 'error');
117
+ tmpSelf.pict.providers.DataCloner.setStatus('connectionStatus', 'Connection failed: ' + (pData.Error || 'Unknown error'), 'error');
118
+ tmpSelf.pict.providers.DataCloner.setSectionPhase(1, 'error');
110
119
  }
111
120
  })
112
121
  .catch(
113
122
  (pError) =>
114
123
  {
115
- this.pict.providers.DataCloner.setStatus('connectionStatus', 'Request failed: ' + pError.message, 'error');
116
- this.pict.providers.DataCloner.setSectionPhase(1, 'error');
124
+ tmpSelf._connectInFlight = false;
125
+ tmpSelf.pict.providers.DataCloner.setStatus('connectionStatus', 'Request failed: ' + pError.message, 'error');
126
+ tmpSelf.pict.providers.DataCloner.setSectionPhase(1, 'error');
117
127
  });
118
128
  }
119
129
 
@@ -214,7 +224,7 @@ module.exports.default_configuration =
214
224
  <!-- SQLite Config -->
215
225
  <div id="configSQLite">
216
226
  <label for="sqliteFilePath">SQLite File Path</label>
217
- <input type="text" id="sqliteFilePath" placeholder="data/cloned.sqlite" value="data/cloned.sqlite">
227
+ <input type="text" id="sqliteFilePath" placeholder="~/headlight-liveconnect-local/cloned.sqlite" value="~/headlight-liveconnect-local/cloned.sqlite">
218
228
  </div>
219
229
 
220
230
  <!-- MySQL Config -->
@@ -27,7 +27,19 @@ class DataClonerDeployView extends libPictView
27
27
  {
28
28
  if (pData.Success)
29
29
  {
30
- tmpSelf.pict.providers.DataCloner.setStatus('deployStatus', pData.Message, 'ok');
30
+ let tmpStatusMsg = pData.Message;
31
+
32
+ // Append migration details if schema deltas were applied
33
+ if (Array.isArray(pData.MigrationsApplied) && pData.MigrationsApplied.length > 0)
34
+ {
35
+ let tmpDetails = pData.MigrationsApplied.map(function(pM)
36
+ {
37
+ return pM.Table + ': +' + pM.ColumnsAdded.join(', +');
38
+ });
39
+ tmpStatusMsg += '\nMigrations: ' + tmpDetails.join('; ');
40
+ }
41
+
42
+ tmpSelf.pict.providers.DataCloner.setStatus('deployStatus', tmpStatusMsg, 'ok');
31
43
  tmpSelf.pict.providers.DataCloner.setSectionPhase(4, 'ok');
32
44
  tmpSelf.pict.AppData.DataCloner.DeployedTables = pData.TablesDeployed || tmpSelectedTables;
33
45
  tmpSelf.pict.providers.DataCloner.saveDeployedTables();
@@ -47,6 +59,95 @@ class DataClonerDeployView extends libPictView
47
59
  });
48
60
  }
49
61
 
62
+ auditGUIDIndices()
63
+ {
64
+ let tmpReportEl = document.getElementById('guidIndexReport');
65
+ if (tmpReportEl) tmpReportEl.innerHTML = '<span style="color:#888">Checking GUID indices...</span>';
66
+
67
+ let tmpSelf = this;
68
+ this.pict.providers.DataCloner.api('GET', '/clone/schema/guid-index-audit')
69
+ .then(function(pData)
70
+ {
71
+ if (!tmpReportEl) return;
72
+
73
+ if (!pData.Success)
74
+ {
75
+ tmpReportEl.innerHTML = '<span style="color:red">' + (pData.Error || 'Audit failed') + '</span>';
76
+ return;
77
+ }
78
+
79
+ if (pData.MissingCount === 0)
80
+ {
81
+ tmpReportEl.innerHTML = '<span style="color:green">All GUID columns have indices.</span>';
82
+ return;
83
+ }
84
+
85
+ let tmpHTML = '<div style="margin-top:6px"><strong>' + pData.Message + '</strong></div>';
86
+ tmpHTML += '<table style="font-size:0.85em; margin:6px 0; border-collapse:collapse; width:100%">';
87
+ tmpHTML += '<tr style="text-align:left; border-bottom:1px solid #ccc"><th style="padding:3px 8px">Table</th><th style="padding:3px 8px">GUID Column</th><th style="padding:3px 8px">Index</th></tr>';
88
+
89
+ for (let t = 0; t < pData.Tables.length; t++)
90
+ {
91
+ let tmpTable = pData.Tables[t];
92
+ for (let c = 0; c < tmpTable.GUIDColumns.length; c++)
93
+ {
94
+ let tmpCol = tmpTable.GUIDColumns[c];
95
+ let tmpStatus = tmpCol.HasIndex
96
+ ? '<span style="color:green">' + tmpCol.IndexName + '</span>'
97
+ : '<span style="color:red">MISSING</span>';
98
+ tmpHTML += '<tr style="border-bottom:1px solid #eee"><td style="padding:3px 8px">' + tmpTable.Table + '</td><td style="padding:3px 8px">' + tmpCol.Column + '</td><td style="padding:3px 8px">' + tmpStatus + '</td></tr>';
99
+ }
100
+ }
101
+ tmpHTML += '</table>';
102
+ tmpHTML += '<button class="primary" style="margin-top:4px" onclick="pict.views[\'DataCloner-Deploy\'].createMissingGUIDIndices()">Create Missing Indices</button>';
103
+
104
+ tmpReportEl.innerHTML = tmpHTML;
105
+ })
106
+ .catch(function(pError)
107
+ {
108
+ if (tmpReportEl) tmpReportEl.innerHTML = '<span style="color:red">Request failed: ' + pError.message + '</span>';
109
+ });
110
+ }
111
+
112
+ createMissingGUIDIndices()
113
+ {
114
+ let tmpReportEl = document.getElementById('guidIndexReport');
115
+ if (tmpReportEl) tmpReportEl.innerHTML = '<span style="color:#888">Creating GUID indices...</span>';
116
+
117
+ let tmpSelf = this;
118
+ this.pict.providers.DataCloner.api('POST', '/clone/schema/guid-index-create')
119
+ .then(function(pData)
120
+ {
121
+ if (!tmpReportEl) return;
122
+
123
+ if (!pData.Success)
124
+ {
125
+ tmpReportEl.innerHTML = '<span style="color:red">' + (pData.Error || 'Index creation failed') + '</span>';
126
+ return;
127
+ }
128
+
129
+ let tmpHTML = '<div style="margin-top:6px; color:green"><strong>' + pData.Message + '</strong></div>';
130
+
131
+ if (pData.IndicesCreated && pData.IndicesCreated.length > 0)
132
+ {
133
+ tmpHTML += '<ul style="font-size:0.85em; margin:4px 0">';
134
+ for (let i = 0; i < pData.IndicesCreated.length; i++)
135
+ {
136
+ let tmpIdx = pData.IndicesCreated[i];
137
+ tmpHTML += '<li>' + tmpIdx.Table + ': ' + tmpIdx.IndexName + '</li>';
138
+ }
139
+ tmpHTML += '</ul>';
140
+ }
141
+
142
+ tmpHTML += '<button style="margin-top:4px" onclick="pict.views[\'DataCloner-Deploy\'].auditGUIDIndices()">Re-check</button>';
143
+ tmpReportEl.innerHTML = tmpHTML;
144
+ })
145
+ .catch(function(pError)
146
+ {
147
+ if (tmpReportEl) tmpReportEl.innerHTML = '<span style="color:red">Request failed: ' + pError.message + '</span>';
148
+ });
149
+ }
150
+
50
151
  resetDatabase()
51
152
  {
52
153
  if (!confirm('This will delete ALL data in the local SQLite database. Continue?'))
@@ -107,8 +208,10 @@ module.exports.default_configuration =
107
208
  <div class="accordion-body">
108
209
  <p style="font-size:0.9em; color:#666; margin-bottom:10px">Creates the selected tables in the local database and sets up CRUD endpoints (e.g. GET /1.0/Documents).</p>
109
210
  <button class="primary" onclick="pict.views['DataCloner-Deploy'].deploySchema()">Deploy Selected Tables</button>
211
+ <button onclick="pict.views['DataCloner-Deploy'].auditGUIDIndices()">Check GUID Indices</button>
110
212
  <button class="danger" onclick="pict.views['DataCloner-Deploy'].resetDatabase()">Reset Database</button>
111
213
  <div id="deployStatus"></div>
214
+ <div id="guidIndexReport"></div>
112
215
  </div>
113
216
  </div>
114
217
  </div>
@@ -18,7 +18,7 @@ class DataClonerExportView extends libPictView
18
18
 
19
19
  if (tmpProvider === 'SQLite')
20
20
  {
21
- tmpDbConfig.SQLiteFilePath = document.getElementById('sqliteFilePath').value.trim() || 'data/cloned.sqlite';
21
+ tmpDbConfig.SQLiteFilePath = document.getElementById('sqliteFilePath').value.trim() || '~/headlight-liveconnect-local/cloned.sqlite';
22
22
  }
23
23
  else if (tmpProvider === 'MySQL')
24
24
  {
@@ -275,6 +275,18 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
275
275
  padding: 12px 20px 16px; max-height: 60vh; overflow-y: auto;
276
276
  }
277
277
 
278
+ /* Status Detail Summary Banner */
279
+ .status-detail-summary {
280
+ display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
281
+ padding: 8px 0 12px; margin-bottom: 12px; border-bottom: 1px solid #e9ecef;
282
+ }
283
+ .status-detail-summary-message {
284
+ font-size: 0.92em; color: #333; font-weight: 600;
285
+ }
286
+ .status-detail-summary-counters {
287
+ display: flex; gap: 16px; flex-wrap: wrap; font-size: 0.82em; color: #666;
288
+ }
289
+
278
290
  /* Status Detail Sections */
279
291
  .status-detail-section { margin-bottom: 14px; }
280
292
  .status-detail-section:last-child { margin-bottom: 0; }