retold-data-service 2.0.16 → 2.0.18
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/.claude/launch.json +2 -2
- package/.quackage.json +19 -0
- package/package.json +13 -6
- package/source/services/data-cloner/DataCloner-Command-Sync.js +83 -50
- package/source/services/data-cloner/DataCloner-Command-WebUI.js +27 -10
- package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +281 -4
- package/source/services/data-cloner/pict-app/Pict-Application-DataCloner-Configuration.json +9 -0
- package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +102 -0
- package/source/services/data-cloner/pict-app/Pict-DataCloner-Bundle.js +6 -0
- package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +998 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +407 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +126 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +483 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +390 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Schema.js +241 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Session.js +268 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Sync.js +575 -0
- package/source/services/data-cloner/pict-app/views/PictView-DataCloner-ViewData.js +176 -0
- package/source/services/data-cloner/web/data-cloner.js +7952 -0
- package/source/services/data-cloner/web/data-cloner.js.map +1 -0
- package/source/services/data-cloner/web/data-cloner.min.js +2 -0
- package/source/services/data-cloner/web/data-cloner.min.js.map +1 -0
- package/source/services/data-cloner/web/index.html +17 -0
- package/test/DataCloner-Integration_tests.js +1205 -0
- package/test/DataCloner-Puppeteer_tests.js +502 -0
- package/test/integration-report.json +311 -0
- package/test/run-integration-tests.js +501 -0
- package/source/services/data-cloner/data-cloner-web.html +0 -2706
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class DataClonerViewDataView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
populateViewTableDropdown()
|
|
11
|
+
{
|
|
12
|
+
let tmpSelect = document.getElementById('viewTable');
|
|
13
|
+
if (!tmpSelect) return;
|
|
14
|
+
let tmpCurrentValue = tmpSelect.value;
|
|
15
|
+
|
|
16
|
+
tmpSelect.innerHTML = '';
|
|
17
|
+
|
|
18
|
+
let tmpDeployedTables = this.pict.AppData.DataCloner.DeployedTables;
|
|
19
|
+
|
|
20
|
+
if (!tmpDeployedTables || tmpDeployedTables.length === 0)
|
|
21
|
+
{
|
|
22
|
+
let tmpOpt = document.createElement('option');
|
|
23
|
+
tmpOpt.value = '';
|
|
24
|
+
tmpOpt.textContent = '\u2014 deploy tables first \u2014';
|
|
25
|
+
tmpSelect.appendChild(tmpOpt);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < tmpDeployedTables.length; i++)
|
|
30
|
+
{
|
|
31
|
+
let tmpOpt = document.createElement('option');
|
|
32
|
+
tmpOpt.value = tmpDeployedTables[i];
|
|
33
|
+
tmpOpt.textContent = tmpDeployedTables[i];
|
|
34
|
+
tmpSelect.appendChild(tmpOpt);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Restore previous selection if it exists
|
|
38
|
+
if (tmpCurrentValue)
|
|
39
|
+
{
|
|
40
|
+
tmpSelect.value = tmpCurrentValue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
loadTableData()
|
|
45
|
+
{
|
|
46
|
+
let tmpTable = document.getElementById('viewTable').value;
|
|
47
|
+
let tmpLimit = parseInt(document.getElementById('viewLimit').value, 10) || 100;
|
|
48
|
+
|
|
49
|
+
if (!tmpTable)
|
|
50
|
+
{
|
|
51
|
+
this.pict.providers.DataCloner.setStatus('viewStatus', 'Select a table first.', 'error');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.pict.providers.DataCloner.setStatus('viewStatus', 'Loading ' + tmpTable + '...', 'info');
|
|
56
|
+
document.getElementById('viewDataContainer').innerHTML = '';
|
|
57
|
+
|
|
58
|
+
let tmpSelf = this;
|
|
59
|
+
// Use the standard Meadow CRUD list endpoint: /1.0/{Entity}s/0/{Cap}
|
|
60
|
+
this.pict.providers.DataCloner.api('GET', '/1.0/' + tmpTable + 's/0/' + tmpLimit)
|
|
61
|
+
.then(function(pData)
|
|
62
|
+
{
|
|
63
|
+
if (!Array.isArray(pData))
|
|
64
|
+
{
|
|
65
|
+
tmpSelf.pict.providers.DataCloner.setStatus('viewStatus', 'Unexpected response (not an array). The table may not be deployed yet.', 'error');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
tmpSelf.pict.providers.DataCloner.setStatus('viewStatus', pData.length + ' row(s) returned' + (pData.length >= tmpLimit ? ' (limit reached \u2014 increase Max Rows to see more)' : '') + '.', 'ok');
|
|
70
|
+
tmpSelf.renderDataTable(pData);
|
|
71
|
+
})
|
|
72
|
+
.catch(function(pError)
|
|
73
|
+
{
|
|
74
|
+
tmpSelf.pict.providers.DataCloner.setStatus('viewStatus', 'Request failed: ' + pError.message, 'error');
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
renderDataTable(pRows)
|
|
79
|
+
{
|
|
80
|
+
let tmpContainer = document.getElementById('viewDataContainer');
|
|
81
|
+
|
|
82
|
+
if (!pRows || pRows.length === 0)
|
|
83
|
+
{
|
|
84
|
+
tmpContainer.innerHTML = '<p style="color:#666; font-size:0.9em; padding:8px">No rows.</p>';
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Collect all column names from the first row
|
|
89
|
+
let tmpColumns = Object.keys(pRows[0]);
|
|
90
|
+
|
|
91
|
+
let tmpHtml = '<table class="data-table">';
|
|
92
|
+
tmpHtml += '<thead><tr>';
|
|
93
|
+
for (let c = 0; c < tmpColumns.length; c++)
|
|
94
|
+
{
|
|
95
|
+
tmpHtml += '<th>' + this.pict.providers.DataCloner.escapeHtml(tmpColumns[c]) + '</th>';
|
|
96
|
+
}
|
|
97
|
+
tmpHtml += '</tr></thead>';
|
|
98
|
+
|
|
99
|
+
tmpHtml += '<tbody>';
|
|
100
|
+
for (let r = 0; r < pRows.length; r++)
|
|
101
|
+
{
|
|
102
|
+
tmpHtml += '<tr>';
|
|
103
|
+
for (let c = 0; c < tmpColumns.length; c++)
|
|
104
|
+
{
|
|
105
|
+
let tmpVal = pRows[r][tmpColumns[c]];
|
|
106
|
+
let tmpDisplay = (tmpVal === null || tmpVal === undefined) ? '' : String(tmpVal);
|
|
107
|
+
tmpHtml += '<td title="' + this.pict.providers.DataCloner.escapeHtml(tmpDisplay) + '">' + this.pict.providers.DataCloner.escapeHtml(tmpDisplay) + '</td>';
|
|
108
|
+
}
|
|
109
|
+
tmpHtml += '</tr>';
|
|
110
|
+
}
|
|
111
|
+
tmpHtml += '</tbody></table>';
|
|
112
|
+
|
|
113
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = DataClonerViewDataView;
|
|
118
|
+
|
|
119
|
+
module.exports.default_configuration =
|
|
120
|
+
{
|
|
121
|
+
ViewIdentifier: 'DataCloner-ViewData',
|
|
122
|
+
DefaultRenderable: 'DataCloner-ViewData',
|
|
123
|
+
DefaultDestinationAddress: '#DataCloner-Section-ViewData',
|
|
124
|
+
CSS: /*css*/`
|
|
125
|
+
.data-table { width: 100%; border-collapse: collapse; font-size: 0.8em; font-family: monospace; }
|
|
126
|
+
.data-table th { background: #f8f9fa; font-weight: 600; text-align: left; padding: 4px 8px; border: 1px solid #ddd; white-space: nowrap; position: sticky; top: 0; }
|
|
127
|
+
.data-table td { padding: 4px 8px; border: 1px solid #eee; white-space: nowrap; max-width: 300px; overflow: hidden; text-overflow: ellipsis; }
|
|
128
|
+
.data-table tr:nth-child(even) { background: #fafafa; }
|
|
129
|
+
.data-table tr:hover { background: #f0f7ff; }
|
|
130
|
+
`,
|
|
131
|
+
Templates:
|
|
132
|
+
[
|
|
133
|
+
{
|
|
134
|
+
Hash: 'DataCloner-ViewData',
|
|
135
|
+
Template: /*html*/`
|
|
136
|
+
<div class="accordion-row">
|
|
137
|
+
<div class="accordion-number">7</div>
|
|
138
|
+
<div class="accordion-card" id="section7" data-section="7">
|
|
139
|
+
<div class="accordion-header" onclick="pict.views['DataCloner-Layout'].toggleSection('section7')">
|
|
140
|
+
<div class="accordion-title">View Data</div>
|
|
141
|
+
<div class="accordion-preview" id="preview7">Browse synced table data</div>
|
|
142
|
+
<div class="accordion-toggle">▼</div>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="accordion-body">
|
|
145
|
+
<div class="inline-group">
|
|
146
|
+
<div style="flex:1">
|
|
147
|
+
<label for="viewTable">Table</label>
|
|
148
|
+
<select id="viewTable">
|
|
149
|
+
<option value="">\u2014 deploy tables first \u2014</option>
|
|
150
|
+
</select>
|
|
151
|
+
</div>
|
|
152
|
+
<div style="flex:0 0 120px">
|
|
153
|
+
<label for="viewLimit">Max Rows</label>
|
|
154
|
+
<input type="number" id="viewLimit" value="100" min="1" max="10000">
|
|
155
|
+
</div>
|
|
156
|
+
<div style="flex:0 0 auto; display:flex; align-items:flex-end">
|
|
157
|
+
<button class="primary" onclick="pict.views['DataCloner-ViewData'].loadTableData()">Load</button>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
<div id="viewStatus"></div>
|
|
161
|
+
<div id="viewDataContainer" style="overflow-x:auto; margin-top:10px"></div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
`
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
Renderables:
|
|
169
|
+
[
|
|
170
|
+
{
|
|
171
|
+
RenderableHash: 'DataCloner-ViewData',
|
|
172
|
+
TemplateHash: 'DataCloner-ViewData',
|
|
173
|
+
DestinationAddress: '#DataCloner-Section-ViewData'
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
};
|