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
|
@@ -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 || '
|
|
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 || '
|
|
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 || '
|
|
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
|
-
|
|
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() || '
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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="
|
|
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
|
-
|
|
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() || '
|
|
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; }
|