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.
Files changed (28) hide show
  1. package/.claude/launch.json +2 -2
  2. package/.quackage.json +19 -0
  3. package/package.json +13 -6
  4. package/source/services/data-cloner/DataCloner-Command-Sync.js +83 -50
  5. package/source/services/data-cloner/DataCloner-Command-WebUI.js +27 -10
  6. package/source/services/data-cloner/Retold-Data-Service-DataCloner.js +281 -4
  7. package/source/services/data-cloner/pict-app/Pict-Application-DataCloner-Configuration.json +9 -0
  8. package/source/services/data-cloner/pict-app/Pict-Application-DataCloner.js +102 -0
  9. package/source/services/data-cloner/pict-app/Pict-DataCloner-Bundle.js +6 -0
  10. package/source/services/data-cloner/pict-app/providers/Pict-Provider-DataCloner.js +998 -0
  11. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Connection.js +407 -0
  12. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Deploy.js +126 -0
  13. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Export.js +483 -0
  14. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Layout.js +390 -0
  15. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Schema.js +241 -0
  16. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Session.js +268 -0
  17. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-Sync.js +575 -0
  18. package/source/services/data-cloner/pict-app/views/PictView-DataCloner-ViewData.js +176 -0
  19. package/source/services/data-cloner/web/data-cloner.js +7952 -0
  20. package/source/services/data-cloner/web/data-cloner.js.map +1 -0
  21. package/source/services/data-cloner/web/data-cloner.min.js +2 -0
  22. package/source/services/data-cloner/web/data-cloner.min.js.map +1 -0
  23. package/source/services/data-cloner/web/index.html +17 -0
  24. package/test/DataCloner-Integration_tests.js +1205 -0
  25. package/test/DataCloner-Puppeteer_tests.js +502 -0
  26. package/test/integration-report.json +311 -0
  27. package/test/run-integration-tests.js +501 -0
  28. package/source/services/data-cloner/data-cloner-web.html +0 -2706
@@ -0,0 +1,268 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ class DataClonerSessionView extends libPictView
4
+ {
5
+ constructor(pFable, pOptions, pServiceHash)
6
+ {
7
+ super(pFable, pOptions, pServiceHash);
8
+ }
9
+
10
+ configureSession()
11
+ {
12
+ let tmpServerURL = document.getElementById('serverURL').value.trim();
13
+ if (!tmpServerURL)
14
+ {
15
+ this.pict.providers.DataCloner.setStatus('sessionConfigStatus', 'Server URL is required.', 'error');
16
+ return;
17
+ }
18
+
19
+ let tmpBody = { ServerURL: tmpServerURL.replace(/\/+$/, '') + '/1.0/' };
20
+
21
+ let tmpAuthMethod = document.getElementById('authMethod').value.trim();
22
+ if (tmpAuthMethod)
23
+ {
24
+ tmpBody.AuthenticationMethod = tmpAuthMethod;
25
+ }
26
+
27
+ let tmpAuthURI = document.getElementById('authURI').value.trim();
28
+ if (tmpAuthURI)
29
+ {
30
+ tmpBody.AuthenticationURITemplate = tmpAuthURI;
31
+ }
32
+
33
+ let tmpCheckURI = document.getElementById('checkURI').value.trim();
34
+ if (tmpCheckURI)
35
+ {
36
+ tmpBody.CheckSessionURITemplate = tmpCheckURI;
37
+ }
38
+
39
+ let tmpCookieName = document.getElementById('cookieName').value.trim();
40
+ if (tmpCookieName)
41
+ {
42
+ tmpBody.CookieName = tmpCookieName;
43
+ }
44
+
45
+ let tmpCookieValueAddr = document.getElementById('cookieValueAddr').value.trim();
46
+ if (tmpCookieValueAddr)
47
+ {
48
+ tmpBody.CookieValueAddress = tmpCookieValueAddr;
49
+ }
50
+
51
+ let tmpCookieValueTemplate = document.getElementById('cookieValueTemplate').value.trim();
52
+ if (tmpCookieValueTemplate)
53
+ {
54
+ tmpBody.CookieValueTemplate = tmpCookieValueTemplate;
55
+ }
56
+
57
+ let tmpLoginMarker = document.getElementById('loginMarker').value.trim();
58
+ if (tmpLoginMarker)
59
+ {
60
+ tmpBody.CheckSessionLoginMarker = tmpLoginMarker;
61
+ }
62
+
63
+ this.pict.providers.DataCloner.setStatus('sessionConfigStatus', 'Configuring session...', 'info');
64
+
65
+ this.pict.providers.DataCloner.api('POST', '/clone/session/configure', tmpBody)
66
+ .then(
67
+ (pData) =>
68
+ {
69
+ if (pData.Success)
70
+ {
71
+ this.pict.providers.DataCloner.setStatus('sessionConfigStatus', 'Session configured for ' + pData.ServerURL + ' (domain: ' + pData.DomainMatch + ')', 'ok');
72
+ }
73
+ else
74
+ {
75
+ this.pict.providers.DataCloner.setStatus('sessionConfigStatus', 'Configuration failed: ' + (pData.Error || 'Unknown error'), 'error');
76
+ }
77
+ })
78
+ .catch(
79
+ (pError) =>
80
+ {
81
+ this.pict.providers.DataCloner.setStatus('sessionConfigStatus', 'Request failed: ' + pError.message, 'error');
82
+ });
83
+ }
84
+
85
+ authenticate()
86
+ {
87
+ let tmpUserName = document.getElementById('userName').value.trim();
88
+ let tmpPassword = document.getElementById('password').value.trim();
89
+
90
+ if (!tmpUserName || !tmpPassword)
91
+ {
92
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Username and password are required.', 'error');
93
+ this.pict.providers.DataCloner.setSectionPhase(2, 'error');
94
+ return;
95
+ }
96
+
97
+ this.pict.providers.DataCloner.setSectionPhase(2, 'busy');
98
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Authenticating...', 'info');
99
+
100
+ this.pict.providers.DataCloner.api('POST', '/clone/session/authenticate', { UserName: tmpUserName, Password: tmpPassword })
101
+ .then(
102
+ (pData) =>
103
+ {
104
+ if (pData.Success && pData.Authenticated)
105
+ {
106
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Authenticated successfully.', 'ok');
107
+ this.pict.providers.DataCloner.setSectionPhase(2, 'ok');
108
+ }
109
+ else
110
+ {
111
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Authentication failed: ' + (pData.Error || 'Not authenticated'), 'error');
112
+ this.pict.providers.DataCloner.setSectionPhase(2, 'error');
113
+ }
114
+ })
115
+ .catch(
116
+ (pError) =>
117
+ {
118
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Request failed: ' + pError.message, 'error');
119
+ this.pict.providers.DataCloner.setSectionPhase(2, 'error');
120
+ });
121
+ }
122
+
123
+ checkSession()
124
+ {
125
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Checking session...', 'info');
126
+
127
+ this.pict.providers.DataCloner.api('GET', '/clone/session/check')
128
+ .then(
129
+ (pData) =>
130
+ {
131
+ if (pData.Authenticated)
132
+ {
133
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Session is active. Server: ' + (pData.ServerURL || 'N/A'), 'ok');
134
+ }
135
+ else if (pData.Configured)
136
+ {
137
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Session configured but not authenticated.', 'warn');
138
+ }
139
+ else
140
+ {
141
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'No session configured.', 'warn');
142
+ }
143
+ })
144
+ .catch(
145
+ (pError) =>
146
+ {
147
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Request failed: ' + pError.message, 'error');
148
+ });
149
+ }
150
+
151
+ deauthenticate()
152
+ {
153
+ this.pict.providers.DataCloner.api('POST', '/clone/session/deauthenticate')
154
+ .then(
155
+ (pData) =>
156
+ {
157
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Session deauthenticated.', 'info');
158
+ })
159
+ .catch(
160
+ (pError) =>
161
+ {
162
+ this.pict.providers.DataCloner.setStatus('sessionAuthStatus', 'Request failed: ' + pError.message, 'error');
163
+ });
164
+ }
165
+
166
+ goAction()
167
+ {
168
+ // Two-step: configure session, then authenticate after delay
169
+ this.pict.providers.DataCloner.setSectionPhase(2, 'busy');
170
+ this.configureSession();
171
+ setTimeout(
172
+ () =>
173
+ {
174
+ this.authenticate();
175
+ }, 1500);
176
+ }
177
+ }
178
+
179
+ module.exports = DataClonerSessionView;
180
+
181
+ module.exports.default_configuration =
182
+ {
183
+ ViewIdentifier: 'DataCloner-Session',
184
+ DefaultRenderable: 'DataCloner-Session',
185
+ DefaultDestinationAddress: '#DataCloner-Section-Session',
186
+ Templates:
187
+ [
188
+ {
189
+ Hash: 'DataCloner-Session',
190
+ Template: /*html*/`
191
+ <div class="accordion-row">
192
+ <div class="accordion-number">2</div>
193
+ <div class="accordion-card" id="section2" data-section="2">
194
+ <div class="accordion-header" onclick="pict.views['DataCloner-Layout'].toggleSection('section2')">
195
+ <div class="accordion-title">Remote Session</div>
196
+ <span class="accordion-phase" id="phase2"></span>
197
+ <div class="accordion-preview" id="preview2">Configure remote server URL and credentials</div>
198
+ <div class="accordion-actions">
199
+ <span class="accordion-go" onclick="event.stopPropagation(); pict.views['DataCloner-Session'].goAction()">go</span>
200
+ <label class="accordion-auto" onclick="event.stopPropagation()"><input type="checkbox" id="auto2"> <span class="auto-label">auto</span></label>
201
+ </div>
202
+ <div class="accordion-toggle">&#9660;</div>
203
+ </div>
204
+ <div class="accordion-body">
205
+ <div class="inline-group">
206
+ <div style="flex:2">
207
+ <label for="serverURL">Remote Server URL</label>
208
+ <input type="text" id="serverURL" placeholder="http://remote-server:8086" value="">
209
+ </div>
210
+ <div style="flex:1">
211
+ <label for="authMethod">Auth Method</label>
212
+ <input type="text" id="authMethod" placeholder="get" value="get">
213
+ </div>
214
+ </div>
215
+
216
+ <details style="margin-bottom:10px">
217
+ <summary style="cursor:pointer; font-size:0.9em; color:#666">Advanced Session Options</summary>
218
+ <div style="padding:10px 0">
219
+ <label for="authURI">Authentication URI Template (leave blank for default)</label>
220
+ <input type="text" id="authURI" placeholder="Authenticate/{~D:Record.UserName~}/{~D:Record.Password~}">
221
+ <label for="checkURI">Check Session URI Template</label>
222
+ <input type="text" id="checkURI" placeholder="CheckSession">
223
+ <label for="cookieName">Cookie Name</label>
224
+ <input type="text" id="cookieName" placeholder="SessionID" value="SessionID">
225
+ <label for="cookieValueAddr">Cookie Value Address</label>
226
+ <input type="text" id="cookieValueAddr" placeholder="SessionID" value="SessionID">
227
+ <label for="cookieValueTemplate">Cookie Value Template (overrides Address if set)</label>
228
+ <input type="text" id="cookieValueTemplate" placeholder="{~D:Record.SessionID~}">
229
+ <label for="loginMarker">Login Marker</label>
230
+ <input type="text" id="loginMarker" placeholder="LoggedIn" value="LoggedIn">
231
+ </div>
232
+ </details>
233
+
234
+ <button class="primary" onclick="pict.views['DataCloner-Session'].configureSession()">Configure Session</button>
235
+ <div id="sessionConfigStatus"></div>
236
+
237
+ <hr style="margin:16px 0; border:none; border-top:1px solid #eee">
238
+
239
+ <div class="inline-group">
240
+ <div>
241
+ <label for="userName">Username</label>
242
+ <input type="text" id="userName" placeholder="username">
243
+ </div>
244
+ <div>
245
+ <label for="password">Password</label>
246
+ <input type="password" id="password" placeholder="password">
247
+ </div>
248
+ </div>
249
+
250
+ <button class="success" onclick="pict.views['DataCloner-Session'].authenticate()">Authenticate</button>
251
+ <button class="secondary" onclick="pict.views['DataCloner-Session'].checkSession()">Check Session</button>
252
+ <button class="danger" onclick="pict.views['DataCloner-Session'].deauthenticate()">Deauthenticate</button>
253
+ <div id="sessionAuthStatus"></div>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ `
258
+ }
259
+ ],
260
+ Renderables:
261
+ [
262
+ {
263
+ RenderableHash: 'DataCloner-Session',
264
+ TemplateHash: 'DataCloner-Session',
265
+ DestinationAddress: '#DataCloner-Section-Session'
266
+ }
267
+ ]
268
+ };