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,483 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class DataClonerExportView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
buildConfigObject()
|
|
11
|
+
{
|
|
12
|
+
let tmpProvider = document.getElementById('connProvider').value;
|
|
13
|
+
let tmpConfig = {};
|
|
14
|
+
|
|
15
|
+
// ---- Local Database ----
|
|
16
|
+
tmpConfig.LocalDatabase = { Provider: tmpProvider, Config: {} };
|
|
17
|
+
let tmpDbConfig = tmpConfig.LocalDatabase.Config;
|
|
18
|
+
|
|
19
|
+
if (tmpProvider === 'SQLite')
|
|
20
|
+
{
|
|
21
|
+
tmpDbConfig.SQLiteFilePath = document.getElementById('sqliteFilePath').value.trim() || 'data/cloned.sqlite';
|
|
22
|
+
}
|
|
23
|
+
else if (tmpProvider === 'MySQL')
|
|
24
|
+
{
|
|
25
|
+
tmpDbConfig.host = document.getElementById('mysqlServer').value.trim() || '127.0.0.1';
|
|
26
|
+
tmpDbConfig.port = parseInt(document.getElementById('mysqlPort').value, 10) || 3306;
|
|
27
|
+
tmpDbConfig.user = document.getElementById('mysqlUser').value.trim() || 'root';
|
|
28
|
+
tmpDbConfig.password = document.getElementById('mysqlPassword').value;
|
|
29
|
+
tmpDbConfig.database = document.getElementById('mysqlDatabase').value.trim();
|
|
30
|
+
tmpDbConfig.connectionLimit = parseInt(document.getElementById('mysqlConnectionLimit').value, 10) || 20;
|
|
31
|
+
}
|
|
32
|
+
else if (tmpProvider === 'MSSQL')
|
|
33
|
+
{
|
|
34
|
+
tmpDbConfig.server = document.getElementById('mssqlServer').value.trim() || '127.0.0.1';
|
|
35
|
+
tmpDbConfig.port = parseInt(document.getElementById('mssqlPort').value, 10) || 1433;
|
|
36
|
+
tmpDbConfig.user = document.getElementById('mssqlUser').value.trim() || 'sa';
|
|
37
|
+
tmpDbConfig.password = document.getElementById('mssqlPassword').value;
|
|
38
|
+
tmpDbConfig.database = document.getElementById('mssqlDatabase').value.trim();
|
|
39
|
+
tmpDbConfig.connectionLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
|
|
40
|
+
}
|
|
41
|
+
else if (tmpProvider === 'PostgreSQL')
|
|
42
|
+
{
|
|
43
|
+
tmpDbConfig.host = document.getElementById('postgresqlHost').value.trim() || '127.0.0.1';
|
|
44
|
+
tmpDbConfig.port = parseInt(document.getElementById('postgresqlPort').value, 10) || 5432;
|
|
45
|
+
tmpDbConfig.user = document.getElementById('postgresqlUser').value.trim() || 'postgres';
|
|
46
|
+
tmpDbConfig.password = document.getElementById('postgresqlPassword').value;
|
|
47
|
+
tmpDbConfig.database = document.getElementById('postgresqlDatabase').value.trim();
|
|
48
|
+
tmpDbConfig.max = parseInt(document.getElementById('postgresqlConnectionLimit').value, 10) || 10;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---- Remote Session ----
|
|
52
|
+
tmpConfig.RemoteSession = {};
|
|
53
|
+
let tmpServerURL = document.getElementById('serverURL').value.trim();
|
|
54
|
+
if (tmpServerURL) tmpConfig.RemoteSession.ServerURL = tmpServerURL + '/1.0/';
|
|
55
|
+
|
|
56
|
+
let tmpAuthMethod = document.getElementById('authMethod').value.trim();
|
|
57
|
+
if (tmpAuthMethod) tmpConfig.RemoteSession.AuthenticationMethod = tmpAuthMethod;
|
|
58
|
+
|
|
59
|
+
let tmpAuthURI = document.getElementById('authURI').value.trim();
|
|
60
|
+
if (tmpAuthURI) tmpConfig.RemoteSession.AuthenticationURITemplate = tmpAuthURI;
|
|
61
|
+
|
|
62
|
+
let tmpCheckURI = document.getElementById('checkURI').value.trim();
|
|
63
|
+
if (tmpCheckURI) tmpConfig.RemoteSession.CheckSessionURITemplate = tmpCheckURI;
|
|
64
|
+
|
|
65
|
+
let tmpCookieName = document.getElementById('cookieName').value.trim();
|
|
66
|
+
if (tmpCookieName) tmpConfig.RemoteSession.CookieName = tmpCookieName;
|
|
67
|
+
|
|
68
|
+
let tmpCookieValueAddr = document.getElementById('cookieValueAddr').value.trim();
|
|
69
|
+
if (tmpCookieValueAddr) tmpConfig.RemoteSession.CookieValueAddress = tmpCookieValueAddr;
|
|
70
|
+
|
|
71
|
+
let tmpCookieValueTemplate = document.getElementById('cookieValueTemplate').value.trim();
|
|
72
|
+
if (tmpCookieValueTemplate) tmpConfig.RemoteSession.CookieValueTemplate = tmpCookieValueTemplate;
|
|
73
|
+
|
|
74
|
+
let tmpLoginMarker = document.getElementById('loginMarker').value.trim();
|
|
75
|
+
if (tmpLoginMarker) tmpConfig.RemoteSession.CheckSessionLoginMarker = tmpLoginMarker;
|
|
76
|
+
|
|
77
|
+
// ---- Credentials ----
|
|
78
|
+
let tmpUserName = document.getElementById('userName').value.trim();
|
|
79
|
+
let tmpPassword = document.getElementById('password').value;
|
|
80
|
+
if (tmpUserName || tmpPassword)
|
|
81
|
+
{
|
|
82
|
+
tmpConfig.Credentials = {};
|
|
83
|
+
if (tmpUserName) tmpConfig.Credentials.UserName = tmpUserName;
|
|
84
|
+
if (tmpPassword) tmpConfig.Credentials.Password = tmpPassword;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---- Schema ----
|
|
88
|
+
let tmpSchemaURL = document.getElementById('schemaURL').value.trim();
|
|
89
|
+
if (tmpSchemaURL) tmpConfig.SchemaURL = tmpSchemaURL;
|
|
90
|
+
|
|
91
|
+
// ---- Tables ----
|
|
92
|
+
let tmpSelectedTables = this.pict.views['DataCloner-Schema'].getSelectedTables();
|
|
93
|
+
if (tmpSelectedTables.length > 0) tmpConfig.Tables = tmpSelectedTables;
|
|
94
|
+
|
|
95
|
+
// ---- Sync Options ----
|
|
96
|
+
tmpConfig.Sync = {};
|
|
97
|
+
tmpConfig.Sync.Mode = document.querySelector('input[name="syncMode"]:checked').value;
|
|
98
|
+
tmpConfig.Sync.PageSize = parseInt(document.getElementById('pageSize').value, 10) || 100;
|
|
99
|
+
tmpConfig.Sync.SyncDeletedRecords = document.getElementById('syncDeletedRecords').checked;
|
|
100
|
+
let tmpPrecision = parseInt(document.getElementById('dateTimePrecisionMS').value, 10);
|
|
101
|
+
if (!isNaN(tmpPrecision) && tmpPrecision !== 1000) tmpConfig.Sync.DateTimePrecisionMS = tmpPrecision;
|
|
102
|
+
let tmpMaxRecords = parseInt(document.getElementById('syncMaxRecords').value, 10);
|
|
103
|
+
if (tmpMaxRecords > 0) tmpConfig.Sync.MaxRecords = tmpMaxRecords;
|
|
104
|
+
|
|
105
|
+
return tmpConfig;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
buildMeadowIntegrationConfig()
|
|
109
|
+
{
|
|
110
|
+
let tmpProvider = document.getElementById('connProvider').value;
|
|
111
|
+
let tmpConfig = {};
|
|
112
|
+
|
|
113
|
+
// ---- Source ----
|
|
114
|
+
let tmpServerURL = document.getElementById('serverURL').value.trim();
|
|
115
|
+
tmpConfig.Source = { ServerURL: tmpServerURL ? tmpServerURL + '/1.0/' : 'https://localhost:8080/1.0/' };
|
|
116
|
+
// When SessionManager handles auth, Source credentials are not needed
|
|
117
|
+
tmpConfig.Source.UserID = false;
|
|
118
|
+
tmpConfig.Source.Password = false;
|
|
119
|
+
|
|
120
|
+
// ---- Destination ----
|
|
121
|
+
// meadow-integration clone supports MySQL and MSSQL
|
|
122
|
+
tmpConfig.Destination = {};
|
|
123
|
+
if (tmpProvider === 'MySQL')
|
|
124
|
+
{
|
|
125
|
+
tmpConfig.Destination.Provider = 'MySQL';
|
|
126
|
+
tmpConfig.Destination.MySQL = {};
|
|
127
|
+
tmpConfig.Destination.MySQL.server = document.getElementById('mysqlServer').value.trim() || '127.0.0.1';
|
|
128
|
+
tmpConfig.Destination.MySQL.port = parseInt(document.getElementById('mysqlPort').value, 10) || 3306;
|
|
129
|
+
tmpConfig.Destination.MySQL.user = document.getElementById('mysqlUser').value.trim() || 'root';
|
|
130
|
+
tmpConfig.Destination.MySQL.password = document.getElementById('mysqlPassword').value || '';
|
|
131
|
+
tmpConfig.Destination.MySQL.database = document.getElementById('mysqlDatabase').value.trim() || 'meadow';
|
|
132
|
+
tmpConfig.Destination.MySQL.connectionLimit = parseInt(document.getElementById('mysqlConnectionLimit').value, 10) || 20;
|
|
133
|
+
}
|
|
134
|
+
else if (tmpProvider === 'MSSQL')
|
|
135
|
+
{
|
|
136
|
+
tmpConfig.Destination.Provider = 'MSSQL';
|
|
137
|
+
tmpConfig.Destination.MSSQL = {};
|
|
138
|
+
tmpConfig.Destination.MSSQL.server = document.getElementById('mssqlServer').value.trim() || '127.0.0.1';
|
|
139
|
+
tmpConfig.Destination.MSSQL.port = parseInt(document.getElementById('mssqlPort').value, 10) || 1433;
|
|
140
|
+
tmpConfig.Destination.MSSQL.user = document.getElementById('mssqlUser').value.trim() || 'sa';
|
|
141
|
+
tmpConfig.Destination.MSSQL.password = document.getElementById('mssqlPassword').value || '';
|
|
142
|
+
tmpConfig.Destination.MSSQL.database = document.getElementById('mssqlDatabase').value.trim() || 'meadow';
|
|
143
|
+
tmpConfig.Destination.MSSQL.ConnectionPoolLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
|
|
144
|
+
}
|
|
145
|
+
else
|
|
146
|
+
{
|
|
147
|
+
// Default to MySQL placeholder for unsupported providers
|
|
148
|
+
tmpConfig.Destination.Provider = 'MySQL';
|
|
149
|
+
tmpConfig.Destination.MySQL = { server: '127.0.0.1', port: 3306, user: 'root', password: '', database: 'meadow', connectionLimit: 20 };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---- Schema ----
|
|
153
|
+
let tmpSchemaURL = document.getElementById('schemaURL').value.trim();
|
|
154
|
+
if (tmpSchemaURL)
|
|
155
|
+
{
|
|
156
|
+
tmpConfig.SchemaURL = tmpSchemaURL;
|
|
157
|
+
}
|
|
158
|
+
else
|
|
159
|
+
{
|
|
160
|
+
tmpConfig.SchemaPath = './schema/Model-Extended.json';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---- Sync ----
|
|
164
|
+
tmpConfig.Sync = {};
|
|
165
|
+
tmpConfig.Sync.DefaultSyncMode = document.querySelector('input[name="syncMode"]:checked').value;
|
|
166
|
+
tmpConfig.Sync.PageSize = parseInt(document.getElementById('pageSize').value, 10) || 100;
|
|
167
|
+
let tmpMdwintPrecision = parseInt(document.getElementById('dateTimePrecisionMS').value, 10);
|
|
168
|
+
if (!isNaN(tmpMdwintPrecision)) tmpConfig.Sync.DateTimePrecisionMS = tmpMdwintPrecision;
|
|
169
|
+
let tmpSelectedTables = this.pict.views['DataCloner-Schema'].getSelectedTables();
|
|
170
|
+
tmpConfig.Sync.SyncEntityList = tmpSelectedTables.length > 0 ? tmpSelectedTables : [];
|
|
171
|
+
tmpConfig.Sync.SyncEntityOptions = {};
|
|
172
|
+
|
|
173
|
+
// ---- SessionManager ----
|
|
174
|
+
tmpConfig.SessionManager = { Sessions: {} };
|
|
175
|
+
|
|
176
|
+
let tmpSessionConfig = {};
|
|
177
|
+
tmpSessionConfig.Type = 'Cookie';
|
|
178
|
+
|
|
179
|
+
// Authentication method
|
|
180
|
+
let tmpAuthMethod = document.getElementById('authMethod').value.trim() || 'get';
|
|
181
|
+
tmpSessionConfig.AuthenticationMethod = tmpAuthMethod;
|
|
182
|
+
|
|
183
|
+
// Build the authentication URI template
|
|
184
|
+
let tmpAuthURI = document.getElementById('authURI').value.trim();
|
|
185
|
+
if (tmpAuthURI)
|
|
186
|
+
{
|
|
187
|
+
// If the URI is a relative path, prepend the server URL
|
|
188
|
+
if (tmpAuthURI.charAt(0) === '/')
|
|
189
|
+
{
|
|
190
|
+
tmpSessionConfig.AuthenticationURITemplate = (tmpServerURL || '') + tmpAuthURI;
|
|
191
|
+
}
|
|
192
|
+
else
|
|
193
|
+
{
|
|
194
|
+
tmpSessionConfig.AuthenticationURITemplate = tmpAuthURI;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (tmpServerURL)
|
|
198
|
+
{
|
|
199
|
+
// Default: Meadow-style GET authentication
|
|
200
|
+
if (tmpAuthMethod === 'post')
|
|
201
|
+
{
|
|
202
|
+
tmpSessionConfig.AuthenticationURITemplate = tmpServerURL + '/1.0/Authenticate';
|
|
203
|
+
tmpSessionConfig.AuthenticationRequestBody = {
|
|
204
|
+
UserName: '{~D:Record.UserName~}',
|
|
205
|
+
Password: '{~D:Record.Password~}'
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
else
|
|
209
|
+
{
|
|
210
|
+
tmpSessionConfig.AuthenticationURITemplate = tmpServerURL + '/1.0/Authenticate/{~D:Record.UserName~}/{~D:Record.Password~}';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check session URI
|
|
215
|
+
let tmpCheckURI = document.getElementById('checkURI').value.trim();
|
|
216
|
+
if (tmpCheckURI)
|
|
217
|
+
{
|
|
218
|
+
tmpSessionConfig.CheckSessionURITemplate = tmpCheckURI.charAt(0) === '/' ? (tmpServerURL || '') + tmpCheckURI : tmpCheckURI;
|
|
219
|
+
}
|
|
220
|
+
else if (tmpServerURL)
|
|
221
|
+
{
|
|
222
|
+
tmpSessionConfig.CheckSessionURITemplate = tmpServerURL + '/1.0/CheckSession';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Login marker
|
|
226
|
+
let tmpLoginMarker = document.getElementById('loginMarker').value.trim();
|
|
227
|
+
tmpSessionConfig.CheckSessionLoginMarkerType = 'boolean';
|
|
228
|
+
tmpSessionConfig.CheckSessionLoginMarker = tmpLoginMarker || 'LoggedIn';
|
|
229
|
+
|
|
230
|
+
// Domain match — extract from server URL for auto-injection
|
|
231
|
+
if (tmpServerURL)
|
|
232
|
+
{
|
|
233
|
+
try
|
|
234
|
+
{
|
|
235
|
+
let tmpUrlObj = new URL(tmpServerURL);
|
|
236
|
+
tmpSessionConfig.DomainMatch = tmpUrlObj.host;
|
|
237
|
+
}
|
|
238
|
+
catch (pError)
|
|
239
|
+
{
|
|
240
|
+
tmpSessionConfig.DomainMatch = tmpServerURL;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Cookie injection
|
|
245
|
+
let tmpCookieName = document.getElementById('cookieName').value.trim();
|
|
246
|
+
tmpSessionConfig.CookieName = tmpCookieName || 'SessionID';
|
|
247
|
+
|
|
248
|
+
let tmpCookieValueAddr = document.getElementById('cookieValueAddr').value.trim();
|
|
249
|
+
if (tmpCookieValueAddr) tmpSessionConfig.CookieValueAddress = tmpCookieValueAddr;
|
|
250
|
+
|
|
251
|
+
let tmpCookieValueTemplate = document.getElementById('cookieValueTemplate').value.trim();
|
|
252
|
+
if (tmpCookieValueTemplate) tmpSessionConfig.CookieValueTemplate = tmpCookieValueTemplate;
|
|
253
|
+
|
|
254
|
+
// Credentials
|
|
255
|
+
let tmpUserName = document.getElementById('userName').value.trim();
|
|
256
|
+
let tmpPassword = document.getElementById('password').value;
|
|
257
|
+
tmpSessionConfig.Credentials = {};
|
|
258
|
+
if (tmpUserName) tmpSessionConfig.Credentials.UserName = tmpUserName;
|
|
259
|
+
if (tmpPassword) tmpSessionConfig.Credentials.Password = tmpPassword;
|
|
260
|
+
|
|
261
|
+
tmpConfig.SessionManager.Sessions.SourceAPI = tmpSessionConfig;
|
|
262
|
+
|
|
263
|
+
return tmpConfig;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
generateConfig()
|
|
267
|
+
{
|
|
268
|
+
let tmpConfig = this.buildConfigObject();
|
|
269
|
+
let tmpJson = JSON.stringify(tmpConfig, null, '\t');
|
|
270
|
+
|
|
271
|
+
let tmpTextarea = document.getElementById('configOutput');
|
|
272
|
+
tmpTextarea.value = tmpJson;
|
|
273
|
+
tmpTextarea.style.display = '';
|
|
274
|
+
|
|
275
|
+
// Build CLI flags from export options
|
|
276
|
+
let tmpLogFlag = document.getElementById('syncLogFile').checked ? ' --log' : '';
|
|
277
|
+
let tmpMaxFlag = '';
|
|
278
|
+
let tmpExportMax = parseInt(document.getElementById('syncMaxRecords').value, 10);
|
|
279
|
+
if (tmpExportMax > 0) tmpMaxFlag = ' --max ' + tmpExportMax;
|
|
280
|
+
|
|
281
|
+
// Build CLI command (with config file)
|
|
282
|
+
let tmpCliDiv = document.getElementById('cliCommand');
|
|
283
|
+
tmpCliDiv.style.display = '';
|
|
284
|
+
tmpCliDiv.querySelector('div').textContent = 'npx retold-data-service-clone --config clone-config.json --run' + tmpLogFlag + tmpMaxFlag;
|
|
285
|
+
|
|
286
|
+
// Build one-liner (no config file needed) using --config-json
|
|
287
|
+
let tmpOneShotDiv = document.getElementById('cliOneShot');
|
|
288
|
+
tmpOneShotDiv.style.display = '';
|
|
289
|
+
let tmpCompactJSON = JSON.stringify(tmpConfig);
|
|
290
|
+
// Escape single quotes for shell wrapping
|
|
291
|
+
let tmpEscapedJSON = tmpCompactJSON.replace(/'/g, "'\\''");
|
|
292
|
+
let tmpOneShot = "npx retold-data-service-clone --config-json '" + tmpEscapedJSON + "' --run" + tmpLogFlag + tmpMaxFlag;
|
|
293
|
+
tmpOneShotDiv.querySelector('div').textContent = tmpOneShot;
|
|
294
|
+
|
|
295
|
+
// ---- meadow-integration (mdwint clone) config ----
|
|
296
|
+
let tmpMdwintConfig = this.buildMeadowIntegrationConfig();
|
|
297
|
+
let tmpMdwintJSON = JSON.stringify(tmpMdwintConfig, null, '\t');
|
|
298
|
+
|
|
299
|
+
let tmpMdwintDiv = document.getElementById('mdwintExport');
|
|
300
|
+
tmpMdwintDiv.style.display = '';
|
|
301
|
+
|
|
302
|
+
let tmpMdwintTextarea = document.getElementById('mdwintConfigOutput');
|
|
303
|
+
tmpMdwintTextarea.value = tmpMdwintJSON;
|
|
304
|
+
|
|
305
|
+
// Build the mdwint CLI command
|
|
306
|
+
let tmpMdwintCLI = 'mdwint clone --schema_path ./schema/Model-Extended.json';
|
|
307
|
+
let tmpMdwintCLIDiv = document.getElementById('mdwintCLICommand');
|
|
308
|
+
tmpMdwintCLIDiv.querySelector('div').textContent = tmpMdwintCLI;
|
|
309
|
+
|
|
310
|
+
// Provider compatibility note
|
|
311
|
+
let tmpProvider = document.getElementById('connProvider').value;
|
|
312
|
+
if (tmpProvider !== 'MySQL' && tmpProvider !== 'MSSQL')
|
|
313
|
+
{
|
|
314
|
+
this.pict.providers.DataCloner.setStatus('mdwintConfigStatus', 'Note: mdwint clone only supports MySQL and MSSQL destinations. The config defaults to MySQL; update the Destination section for your target database.', 'warn');
|
|
315
|
+
}
|
|
316
|
+
else
|
|
317
|
+
{
|
|
318
|
+
this.pict.providers.DataCloner.setStatus('mdwintConfigStatus', '', '');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
this.pict.providers.DataCloner.setStatus('configExportStatus', 'Config generated. Save as clone-config.json or copy the one-liner below.', 'ok');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
copyConfig()
|
|
325
|
+
{
|
|
326
|
+
let tmpTextarea = document.getElementById('configOutput');
|
|
327
|
+
if (!tmpTextarea.value)
|
|
328
|
+
{
|
|
329
|
+
this.pict.providers.DataCloner.setStatus('configExportStatus', 'Generate a config first.', 'warn');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
let tmpSelf = this;
|
|
333
|
+
navigator.clipboard.writeText(tmpTextarea.value).then(function()
|
|
334
|
+
{
|
|
335
|
+
tmpSelf.pict.providers.DataCloner.setStatus('configExportStatus', 'Config copied to clipboard.', 'ok');
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
copyCLI()
|
|
340
|
+
{
|
|
341
|
+
let tmpCmd = document.getElementById('cliCommand').querySelector('div').textContent;
|
|
342
|
+
let tmpSelf = this;
|
|
343
|
+
navigator.clipboard.writeText(tmpCmd).then(function()
|
|
344
|
+
{
|
|
345
|
+
tmpSelf.pict.providers.DataCloner.setStatus('configExportStatus', 'CLI command copied to clipboard.', 'ok');
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
copyOneShot()
|
|
350
|
+
{
|
|
351
|
+
let tmpCmd = document.getElementById('cliOneShot').querySelector('div').textContent;
|
|
352
|
+
let tmpSelf = this;
|
|
353
|
+
navigator.clipboard.writeText(tmpCmd).then(function()
|
|
354
|
+
{
|
|
355
|
+
tmpSelf.pict.providers.DataCloner.setStatus('configExportStatus', 'One-liner copied to clipboard.', 'ok');
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
downloadConfig()
|
|
360
|
+
{
|
|
361
|
+
let tmpTextarea = document.getElementById('configOutput');
|
|
362
|
+
if (!tmpTextarea.value)
|
|
363
|
+
{
|
|
364
|
+
this.generateConfig();
|
|
365
|
+
}
|
|
366
|
+
let tmpBlob = new Blob([tmpTextarea.value], { type: 'application/json' });
|
|
367
|
+
let tmpAnchor = document.createElement('a');
|
|
368
|
+
tmpAnchor.href = URL.createObjectURL(tmpBlob);
|
|
369
|
+
tmpAnchor.download = 'clone-config.json';
|
|
370
|
+
tmpAnchor.click();
|
|
371
|
+
URL.revokeObjectURL(tmpAnchor.href);
|
|
372
|
+
this.pict.providers.DataCloner.setStatus('configExportStatus', 'Config downloaded as clone-config.json.', 'ok');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
copyMdwintConfig()
|
|
376
|
+
{
|
|
377
|
+
let tmpTextarea = document.getElementById('mdwintConfigOutput');
|
|
378
|
+
if (!tmpTextarea.value)
|
|
379
|
+
{
|
|
380
|
+
this.pict.providers.DataCloner.setStatus('mdwintConfigStatus', 'Generate a config first.', 'warn');
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
let tmpSelf = this;
|
|
384
|
+
navigator.clipboard.writeText(tmpTextarea.value).then(function()
|
|
385
|
+
{
|
|
386
|
+
tmpSelf.pict.providers.DataCloner.setStatus('mdwintConfigStatus', '.meadow.config.json copied to clipboard.', 'ok');
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
copyMdwintCLI()
|
|
391
|
+
{
|
|
392
|
+
let tmpCmd = document.getElementById('mdwintCLICommand').querySelector('div').textContent;
|
|
393
|
+
let tmpSelf = this;
|
|
394
|
+
navigator.clipboard.writeText(tmpCmd).then(function()
|
|
395
|
+
{
|
|
396
|
+
tmpSelf.pict.providers.DataCloner.setStatus('mdwintConfigStatus', 'mdwint CLI command copied to clipboard.', 'ok');
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
downloadMdwintConfig()
|
|
401
|
+
{
|
|
402
|
+
let tmpTextarea = document.getElementById('mdwintConfigOutput');
|
|
403
|
+
if (!tmpTextarea.value)
|
|
404
|
+
{
|
|
405
|
+
this.generateConfig();
|
|
406
|
+
}
|
|
407
|
+
let tmpBlob = new Blob([tmpTextarea.value], { type: 'application/json' });
|
|
408
|
+
let tmpAnchor = document.createElement('a');
|
|
409
|
+
tmpAnchor.href = URL.createObjectURL(tmpBlob);
|
|
410
|
+
tmpAnchor.download = '.meadow.config.json';
|
|
411
|
+
tmpAnchor.click();
|
|
412
|
+
URL.revokeObjectURL(tmpAnchor.href);
|
|
413
|
+
this.pict.providers.DataCloner.setStatus('mdwintConfigStatus', 'Config downloaded as .meadow.config.json.', 'ok');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
module.exports = DataClonerExportView;
|
|
418
|
+
|
|
419
|
+
module.exports.default_configuration =
|
|
420
|
+
{
|
|
421
|
+
ViewIdentifier: 'DataCloner-Export',
|
|
422
|
+
DefaultRenderable: 'DataCloner-Export',
|
|
423
|
+
DefaultDestinationAddress: '#DataCloner-Section-Export',
|
|
424
|
+
Templates:
|
|
425
|
+
[
|
|
426
|
+
{
|
|
427
|
+
Hash: 'DataCloner-Export',
|
|
428
|
+
Template: /*html*/`
|
|
429
|
+
<div class="accordion-row">
|
|
430
|
+
<div class="accordion-number">6</div>
|
|
431
|
+
<div class="accordion-card" id="section6" data-section="6">
|
|
432
|
+
<div class="accordion-header" onclick="pict.views['DataCloner-Layout'].toggleSection('section6')">
|
|
433
|
+
<div class="accordion-title">Export Configuration</div>
|
|
434
|
+
<div class="accordion-preview" id="preview6">Generate JSON config for headless cloning</div>
|
|
435
|
+
<div class="accordion-toggle">▼</div>
|
|
436
|
+
</div>
|
|
437
|
+
<div class="accordion-body">
|
|
438
|
+
<p style="font-size:0.9em; color:#666; margin-bottom:10px">Generate a JSON config file from your current settings. Use it to run headless clones from the command line.</p>
|
|
439
|
+
<div style="display:flex; gap:8px; margin-bottom:10px">
|
|
440
|
+
<button class="primary" onclick="pict.views['DataCloner-Export'].generateConfig()">Generate Config</button>
|
|
441
|
+
<button class="secondary" onclick="pict.views['DataCloner-Export'].copyConfig()">Copy to Clipboard</button>
|
|
442
|
+
<button class="secondary" onclick="pict.views['DataCloner-Export'].downloadConfig()">Download JSON</button>
|
|
443
|
+
</div>
|
|
444
|
+
<div id="configExportStatus"></div>
|
|
445
|
+
<div id="cliCommand" style="display:none; margin-bottom:10px">
|
|
446
|
+
<label style="margin-bottom:4px">CLI Command <span style="color:#888; font-weight:normal">(with config file)</span></label>
|
|
447
|
+
<div style="background:#1a1a1a; color:#4fc3f7; padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer" onclick="pict.views['DataCloner-Export'].copyCLI()" title="Click to copy"></div>
|
|
448
|
+
</div>
|
|
449
|
+
<div id="cliOneShot" style="display:none; margin-bottom:10px">
|
|
450
|
+
<label style="margin-bottom:4px">One-liner <span style="color:#888; font-weight:normal">(no config file needed)</span></label>
|
|
451
|
+
<div style="background:#1a1a1a; color:#4fc3f7; padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer; white-space:pre-wrap" onclick="pict.views['DataCloner-Export'].copyOneShot()" title="Click to copy"></div>
|
|
452
|
+
</div>
|
|
453
|
+
<textarea id="configOutput" style="display:none; width:100%; min-height:300px; font-family:monospace; font-size:0.85em; padding:10px; border:1px solid #ccc; border-radius:4px; background:#fafafa; tab-size:4; resize:vertical" readonly></textarea>
|
|
454
|
+
|
|
455
|
+
<div id="mdwintExport" style="display:none; margin-top:16px; padding-top:16px; border-top:1px solid #eee">
|
|
456
|
+
<h3 style="margin:0 0 8px; font-size:1em">meadow-integration CLI <span style="color:#888; font-weight:normal; font-size:0.85em">(mdwint clone)</span></h3>
|
|
457
|
+
<p style="font-size:0.85em; color:#666; margin-bottom:8px">Save as <code>.meadow.config.json</code> in your project root, then run the command below. Requires a local Meadow extended schema JSON file.</p>
|
|
458
|
+
<div style="display:flex; gap:8px; margin-bottom:10px">
|
|
459
|
+
<button class="secondary" onclick="pict.views['DataCloner-Export'].copyMdwintConfig()">Copy Config</button>
|
|
460
|
+
<button class="secondary" onclick="pict.views['DataCloner-Export'].downloadMdwintConfig()">Download .meadow.config.json</button>
|
|
461
|
+
</div>
|
|
462
|
+
<div id="mdwintCLICommand" style="margin-bottom:10px">
|
|
463
|
+
<label style="margin-bottom:4px">CLI Command</label>
|
|
464
|
+
<div style="background:#1a1a1a; color:#4fc3f7; padding:10px 14px; border-radius:4px; font-family:monospace; font-size:0.9em; word-break:break-all; cursor:pointer" onclick="pict.views['DataCloner-Export'].copyMdwintCLI()" title="Click to copy"></div>
|
|
465
|
+
</div>
|
|
466
|
+
<div id="mdwintConfigStatus"></div>
|
|
467
|
+
<textarea id="mdwintConfigOutput" style="width:100%; min-height:250px; font-family:monospace; font-size:0.85em; padding:10px; border:1px solid #ccc; border-radius:4px; background:#fafafa; tab-size:4; resize:vertical" readonly></textarea>
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
`
|
|
473
|
+
}
|
|
474
|
+
],
|
|
475
|
+
Renderables:
|
|
476
|
+
[
|
|
477
|
+
{
|
|
478
|
+
RenderableHash: 'DataCloner-Export',
|
|
479
|
+
TemplateHash: 'DataCloner-Export',
|
|
480
|
+
DestinationAddress: '#DataCloner-Section-Export'
|
|
481
|
+
}
|
|
482
|
+
]
|
|
483
|
+
};
|