tina4ruby 0.5.2 → 3.2.1

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/README.md +434 -544
  4. data/exe/{tina4 → tina4ruby} +1 -0
  5. data/lib/tina4/ai.rb +312 -0
  6. data/lib/tina4/auth.rb +44 -3
  7. data/lib/tina4/auto_crud.rb +163 -0
  8. data/lib/tina4/cli.rb +389 -97
  9. data/lib/tina4/constants.rb +46 -0
  10. data/lib/tina4/cors.rb +74 -0
  11. data/lib/tina4/database/sqlite3_adapter.rb +139 -0
  12. data/lib/tina4/database.rb +144 -7
  13. data/lib/tina4/debug.rb +4 -79
  14. data/lib/tina4/dev_admin.rb +1162 -0
  15. data/lib/tina4/dev_mailbox.rb +191 -0
  16. data/lib/tina4/dev_reload.rb +9 -9
  17. data/lib/tina4/drivers/firebird_driver.rb +19 -3
  18. data/lib/tina4/drivers/mssql_driver.rb +3 -3
  19. data/lib/tina4/drivers/mysql_driver.rb +4 -4
  20. data/lib/tina4/drivers/postgres_driver.rb +9 -2
  21. data/lib/tina4/drivers/sqlite_driver.rb +1 -1
  22. data/lib/tina4/env.rb +42 -2
  23. data/lib/tina4/error_overlay.rb +252 -0
  24. data/lib/tina4/events.rb +90 -0
  25. data/lib/tina4/field_types.rb +4 -0
  26. data/lib/tina4/frond.rb +1497 -0
  27. data/lib/tina4/gallery/auth/meta.json +1 -0
  28. data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -0
  29. data/lib/tina4/gallery/database/meta.json +1 -0
  30. data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -0
  31. data/lib/tina4/gallery/error-overlay/meta.json +1 -0
  32. data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -0
  33. data/lib/tina4/gallery/orm/meta.json +1 -0
  34. data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -0
  35. data/lib/tina4/gallery/queue/meta.json +1 -0
  36. data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -0
  37. data/lib/tina4/gallery/rest-api/meta.json +1 -0
  38. data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -0
  39. data/lib/tina4/gallery/templates/meta.json +1 -0
  40. data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -0
  41. data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -0
  42. data/lib/tina4/health.rb +39 -0
  43. data/lib/tina4/html_element.rb +148 -0
  44. data/lib/tina4/localization.rb +2 -2
  45. data/lib/tina4/log.rb +203 -0
  46. data/lib/tina4/messenger.rb +562 -0
  47. data/lib/tina4/migration.rb +132 -29
  48. data/lib/tina4/orm.rb +463 -35
  49. data/lib/tina4/public/css/tina4.css +178 -1
  50. data/lib/tina4/public/css/tina4.min.css +1 -2
  51. data/lib/tina4/public/favicon.ico +0 -0
  52. data/lib/tina4/public/images/logo.svg +5 -0
  53. data/lib/tina4/public/images/tina4-logo-icon.webp +0 -0
  54. data/lib/tina4/public/js/frond.min.js +420 -0
  55. data/lib/tina4/public/js/tina4-dev-admin.min.js +367 -0
  56. data/lib/tina4/public/js/tina4.min.js +93 -0
  57. data/lib/tina4/public/swagger/index.html +90 -0
  58. data/lib/tina4/public/swagger/oauth2-redirect.html +63 -0
  59. data/lib/tina4/queue.rb +162 -6
  60. data/lib/tina4/queue_backends/lite_backend.rb +88 -0
  61. data/lib/tina4/rack_app.rb +331 -27
  62. data/lib/tina4/rate_limiter.rb +123 -0
  63. data/lib/tina4/request.rb +61 -15
  64. data/lib/tina4/response.rb +54 -24
  65. data/lib/tina4/response_cache.rb +551 -0
  66. data/lib/tina4/router.rb +90 -15
  67. data/lib/tina4/scss_compiler.rb +2 -2
  68. data/lib/tina4/seeder.rb +56 -61
  69. data/lib/tina4/service_runner.rb +303 -0
  70. data/lib/tina4/session.rb +85 -0
  71. data/lib/tina4/session_handlers/mongo_handler.rb +1 -1
  72. data/lib/tina4/session_handlers/valkey_handler.rb +43 -0
  73. data/lib/tina4/shutdown.rb +84 -0
  74. data/lib/tina4/sql_translation.rb +295 -0
  75. data/lib/tina4/template.rb +36 -6
  76. data/lib/tina4/templates/base.twig +2 -2
  77. data/lib/tina4/templates/errors/302.twig +14 -0
  78. data/lib/tina4/templates/errors/401.twig +9 -0
  79. data/lib/tina4/templates/errors/403.twig +22 -15
  80. data/lib/tina4/templates/errors/404.twig +22 -15
  81. data/lib/tina4/templates/errors/500.twig +31 -15
  82. data/lib/tina4/templates/errors/502.twig +9 -0
  83. data/lib/tina4/templates/errors/503.twig +12 -0
  84. data/lib/tina4/templates/errors/base.twig +37 -0
  85. data/lib/tina4/version.rb +1 -1
  86. data/lib/tina4/webserver.rb +28 -18
  87. data/lib/tina4.rb +118 -21
  88. metadata +68 -8
  89. data/lib/tina4/public/js/tina4.js +0 -134
  90. data/lib/tina4/public/js/tina4helper.js +0 -387
@@ -0,0 +1,420 @@
1
+ (function (global, factory) {
2
+ if (typeof define === 'function' && define.amd) {
3
+ define([], factory);
4
+ } else if (typeof module !== 'undefined' && module.exports){
5
+ module.exports = factory();
6
+ } else {
7
+ global.ReconnectingWebSocket = factory();
8
+ }
9
+ })(this, function () {
10
+ if (!('WebSocket' in window)) {
11
+ return;
12
+ }
13
+ function ReconnectingWebSocket(url, protocols, options) {
14
+ var settings = {
15
+ debug: false,
16
+ automaticOpen: true,
17
+ reconnectInterval: 1000,
18
+ maxReconnectInterval: 30000,
19
+ reconnectDecay: 1.5,
20
+ timeoutInterval: 2000,
21
+ maxReconnectAttempts: null,
22
+ binaryType: 'blob'
23
+ }
24
+ if (!options) { options = {}; }
25
+ for (var key in settings) {
26
+ if (typeof options[key] !== 'undefined') {
27
+ this[key] = options[key];
28
+ } else {
29
+ this[key] = settings[key];
30
+ }
31
+ }
32
+ this.url = url;
33
+ this.reconnectAttempts = 0;
34
+ this.readyState = WebSocket.CONNECTING;
35
+ this.protocol = null;
36
+ var self = this;
37
+ var ws;
38
+ var forcedClose = false;
39
+ var timedOut = false;
40
+ var eventTarget = document.createElement('div');
41
+ eventTarget.addEventListener('open', function(event) { self.onopen(event); });
42
+ eventTarget.addEventListener('close', function(event) { self.onclose(event); });
43
+ eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); });
44
+ eventTarget.addEventListener('message', function(event) { self.onmessage(event); });
45
+ eventTarget.addEventListener('error', function(event) { self.onerror(event); });
46
+ this.addEventListener = eventTarget.addEventListener.bind(eventTarget);
47
+ this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
48
+ this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
49
+ function generateEvent(s, args) {
50
+ var evt = document.createEvent("CustomEvent");
51
+ evt.initCustomEvent(s, false, false, args);
52
+ return evt;
53
+ };
54
+ this.open = function (reconnectAttempt) {
55
+ ws = new WebSocket(self.url, protocols || []);
56
+ ws.binaryType = this.binaryType;
57
+ if (reconnectAttempt) {
58
+ if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
59
+ return;
60
+ }
61
+ } else {
62
+ eventTarget.dispatchEvent(generateEvent('connecting'));
63
+ this.reconnectAttempts = 0;
64
+ }
65
+ if (self.debug || ReconnectingWebSocket.debugAll) {
66
+ console.debug('ReconnectingWebSocket', 'attempt-connect', self.url);
67
+ }
68
+ var localWs = ws;
69
+ var timeout = setTimeout(function() {
70
+ if (self.debug || ReconnectingWebSocket.debugAll) {
71
+ console.debug('ReconnectingWebSocket', 'connection-timeout', self.url);
72
+ }
73
+ timedOut = true;
74
+ localWs.close();
75
+ timedOut = false;
76
+ }, self.timeoutInterval);
77
+ ws.onopen = function(event) {
78
+ clearTimeout(timeout);
79
+ if (self.debug || ReconnectingWebSocket.debugAll) {
80
+ console.debug('ReconnectingWebSocket', 'onopen', self.url);
81
+ }
82
+ self.protocol = ws.protocol;
83
+ self.readyState = WebSocket.OPEN;
84
+ self.reconnectAttempts = 0;
85
+ var e = generateEvent('open');
86
+ e.isReconnect = reconnectAttempt;
87
+ reconnectAttempt = false;
88
+ eventTarget.dispatchEvent(e);
89
+ };
90
+ ws.onclose = function(event) {
91
+ clearTimeout(timeout);
92
+ ws = null;
93
+ if (forcedClose) {
94
+ self.readyState = WebSocket.CLOSED;
95
+ eventTarget.dispatchEvent(generateEvent('close'));
96
+ } else {
97
+ self.readyState = WebSocket.CONNECTING;
98
+ var e = generateEvent('connecting');
99
+ e.code = event.code;
100
+ e.reason = event.reason;
101
+ e.wasClean = event.wasClean;
102
+ eventTarget.dispatchEvent(e);
103
+ if (!reconnectAttempt && !timedOut) {
104
+ if (self.debug || ReconnectingWebSocket.debugAll) {
105
+ console.debug('ReconnectingWebSocket', 'onclose', self.url);
106
+ }
107
+ eventTarget.dispatchEvent(generateEvent('close'));
108
+ }
109
+ var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
110
+ setTimeout(function() {
111
+ self.reconnectAttempts++;
112
+ self.open(true);
113
+ }, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);
114
+ }
115
+ };
116
+ ws.onmessage = function(event) {
117
+ if (self.debug || ReconnectingWebSocket.debugAll) {
118
+ console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data);
119
+ }
120
+ var e = generateEvent('message');
121
+ e.data = event.data;
122
+ eventTarget.dispatchEvent(e);
123
+ };
124
+ ws.onerror = function(event) {
125
+ if (self.debug || ReconnectingWebSocket.debugAll) {
126
+ console.debug('ReconnectingWebSocket', 'onerror', self.url, event);
127
+ }
128
+ eventTarget.dispatchEvent(generateEvent('error'));
129
+ };
130
+ }
131
+ if (this.automaticOpen == true) {
132
+ this.open(false);
133
+ }
134
+ this.send = function(data) {
135
+ if (ws) {
136
+ if (self.debug || ReconnectingWebSocket.debugAll) {
137
+ console.debug('ReconnectingWebSocket', 'send', self.url, data);
138
+ }
139
+ return ws.send(data);
140
+ } else {
141
+ throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
142
+ }
143
+ };
144
+ this.close = function(code, reason) {
145
+ if (typeof code == 'undefined') {
146
+ code = 1000;
147
+ }
148
+ forcedClose = true;
149
+ if (ws) {
150
+ ws.close(code, reason);
151
+ }
152
+ };
153
+ this.refresh = function() {
154
+ if (ws) {
155
+ ws.close();
156
+ }
157
+ };
158
+ }
159
+ ReconnectingWebSocket.prototype.onopen = function(event) {};
160
+ ReconnectingWebSocket.prototype.onclose = function(event) {};
161
+ ReconnectingWebSocket.prototype.onconnecting = function(event) {};
162
+ ReconnectingWebSocket.prototype.onmessage = function(event) {};
163
+ ReconnectingWebSocket.prototype.onerror = function(event) {};
164
+ ReconnectingWebSocket.debugAll = false;
165
+ ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;
166
+ ReconnectingWebSocket.OPEN = WebSocket.OPEN;
167
+ ReconnectingWebSocket.CLOSING = WebSocket.CLOSING;
168
+ ReconnectingWebSocket.CLOSED = WebSocket.CLOSED;
169
+ return ReconnectingWebSocket;
170
+ });
171
+ var formToken = null;
172
+ function sendRequest(url, request, method, callback) {
173
+ if (url === undefined) url = "";
174
+ if (request === undefined) request = null;
175
+ if (method === undefined) method = 'GET';
176
+ const xhr = new XMLHttpRequest();
177
+ xhr.open(method, url, true);
178
+ if (formToken !== null) {
179
+ xhr.setRequestHeader('Authorization', 'Bearer ' + formToken);
180
+ }
181
+ let isFormData = request instanceof FormData;
182
+ if (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PUT' || method.toUpperCase() === 'PATCH') {
183
+ if (isFormData) {
184
+ } else if (typeof request === 'object' && request !== null) {
185
+ request = JSON.stringify(request);
186
+ xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
187
+ } else if (typeof request === 'string') {
188
+ xhr.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8');
189
+ }
190
+ }
191
+ xhr.onload = function () {
192
+ let content = xhr.response;
193
+ const freshToken = xhr.getResponseHeader('FreshToken');
194
+ if (freshToken && freshToken !== '') {
195
+ formToken = freshToken;
196
+ }
197
+ try {
198
+ content = JSON.parse(content);
199
+ } catch (e) {
200
+ }
201
+ if (typeof callback === 'function') {
202
+ callback(content, xhr.status, xhr);
203
+ }
204
+ };
205
+ xhr.onerror = function () {
206
+ if (typeof callback === 'function') {
207
+ callback(null, xhr.status, xhr);
208
+ }
209
+ };
210
+ xhr.send(request);
211
+ }
212
+ function getFormData(formId) {
213
+ let data = new FormData();
214
+ let elements = document.querySelectorAll("#" + formId + " select, #" + formId + " input, #" + formId + " textarea");
215
+ for (let ie = 0; ie < elements.length; ie++ )
216
+ {
217
+ let element = elements[ie];
218
+ if (element.name === 'formToken' && formToken !== null) {
219
+ element.value = formToken;
220
+ }
221
+ if (element.name) {
222
+ if (element.type === 'file') {
223
+ for (let i = 0; i < element.files.length; i++) {
224
+ let fileData = element.files[i];
225
+ let elementName = element.name;
226
+ if (fileData !== undefined) {
227
+ if (element.files.length > 1 && !elementName.includes('[')) {
228
+ elementName = elementName + '[]';
229
+ }
230
+ data.append(elementName, fileData, fileData.name);
231
+ }
232
+ }
233
+ } else if (element.type === 'checkbox' || element.type === 'radio') {
234
+ if (element.checked) {
235
+ data.append(element.name, element.value)
236
+ } else {
237
+ if (element.type !== 'radio') {
238
+ data.append(element.name, "0")
239
+ }
240
+ }
241
+ } else {
242
+ if (element.value === '') {
243
+ element.value = null;
244
+ }
245
+ data.append(element.name, element.value);
246
+ }
247
+ }
248
+ }
249
+ return data;
250
+ }
251
+ function handleHtmlData(data, targetElement) {
252
+ if (data === "") return '';
253
+ const parser = new DOMParser();
254
+ const htmlData = parser.parseFromString(data.includes !== undefined && data.includes('<html>') ? data : '<body>'+data+'</body></html>', 'text/html');
255
+ const body = htmlData.querySelector('body');
256
+ const scripts = body.querySelectorAll('script');
257
+ body.querySelectorAll('script').forEach(script => script.remove());
258
+ if (targetElement !== null) {
259
+ if (body.children.length > 0) {
260
+ document.getElementById(targetElement).replaceChildren(...body.children);
261
+ } else {
262
+ document.getElementById(targetElement).replaceChildren(body.innerHTML);
263
+ }
264
+ if (scripts) {
265
+ scripts.forEach(script => {
266
+ const newScript = document.createElement("script");
267
+ newScript.type = 'text/javascript';
268
+ newScript.async = true;
269
+ newScript.textContent = script.innerText;
270
+ document.getElementById(targetElement).append(newScript);
271
+ });
272
+ }
273
+ } else {
274
+ if (scripts) {
275
+ scripts.forEach(script => {
276
+ const newScript = document.createElement("script");
277
+ newScript.type = 'text/javascript';
278
+ newScript.async = true;
279
+ newScript.textContent = script.innerText;
280
+ document.body.append(newScript);
281
+ console.log(newScript);
282
+ });
283
+ }
284
+ return body.innerHTML;
285
+ }
286
+ return '';
287
+ }
288
+ function loadPage(loadURL, targetElement, callback = null) {
289
+ if (targetElement === undefined) targetElement = 'content';
290
+ sendRequest(loadURL, null, "GET", function(data) {
291
+ let processedHTML = '';
292
+ if (document.getElementById(targetElement) !== null) {
293
+ processedHTML = handleHtmlData(data, targetElement);
294
+ } else {
295
+ if (callback) {
296
+ callback(data);
297
+ } else {
298
+ console.log('TINA4 - define targetElement or callback for loadPage', data);
299
+ }
300
+ return;
301
+ }
302
+ if (callback) {
303
+ callback(processedHTML, data);
304
+ }
305
+ });
306
+ }
307
+ function showForm(action, loadURL, targetElement, callback = null) {
308
+ if (targetElement === undefined) targetElement = 'form';
309
+ if (action === 'create') action = 'GET';
310
+ if (action === 'edit') action = 'GET';
311
+ if (action === 'delete') action = 'DELETE';
312
+ sendRequest(loadURL, null, action, function(data) {
313
+ let processedHTML = '';
314
+ if (data.message !== undefined) {
315
+ processedHTML = handleHtmlData ((data.message), targetElement);
316
+ } else {
317
+ if (document.getElementById(targetElement) !== null) {
318
+ processedHTML = handleHtmlData (data, targetElement);
319
+ } else {
320
+ if (callback) {
321
+ callback(data);
322
+ } else {
323
+ console.log('TINA4 - define targetElement or callback for showForm', data);
324
+ }
325
+ return;
326
+ }
327
+ }
328
+ if (callback) {
329
+ callback(processedHTML);
330
+ }
331
+ });
332
+ }
333
+ function postUrl(url, data, targetElement, callback= null) {
334
+ sendRequest(url, data, 'POST', function(data) {
335
+ let processedHTML = '';
336
+ if (data.message !== undefined) {
337
+ processedHTML = handleHtmlData ((data.message), targetElement);
338
+ } else {
339
+ if (document.getElementById(targetElement) !== null) {
340
+ processedHTML = handleHtmlData (data, targetElement);
341
+ } else {
342
+ if (callback) {
343
+ callback(data);
344
+ } else {
345
+ console.log('TINA4 - define targetElement or callback for postUrl', data);
346
+ }
347
+ return;
348
+ }
349
+ }
350
+ if (callback) {
351
+ callback(processedHTML,data)
352
+ }
353
+ });
354
+ }
355
+ function saveForm(formId, targetURL, targetElement, callback = null) {
356
+ if (targetElement === undefined) targetElement = 'message';
357
+ let data = getFormData(formId);
358
+ postUrl(targetURL, data, targetElement, callback);
359
+ }
360
+ function postForm(formId, targetURL, targetElement, callback = null){
361
+ saveForm(formId, targetURL, targetElement, callback)
362
+ }
363
+ function submitForm(formId, targetURL, targetElement, callback = null){
364
+ saveForm(formId, targetURL, targetElement, callback)
365
+ }
366
+ function showMessage(message) {
367
+ document.getElementById('message').innerHTML = '<div class="alert alert-info alert-dismissible fade show"><strong>Info</strong> ' + message + '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
368
+ }
369
+ function setCookie(name, value, days) {
370
+ let expires = "";
371
+ if (days) {
372
+ let date = new Date();
373
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
374
+ expires = "; expires=" + date.toUTCString();
375
+ }
376
+ document.cookie = name + "=" + (value || "") + expires + "; path=/";
377
+ }
378
+ function getCookie(name) {
379
+ let nameEQ = name + "=";
380
+ let ca = document.cookie.split(';');
381
+ for (let i = 0; i < ca.length; i++) {
382
+ var c = ca[i];
383
+ while (c.charAt(0) == ' ') c = c.substring(1, c.length);
384
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
385
+ }
386
+ return null;
387
+ }
388
+ const popupCenter = ({url, title, w, h}) => {
389
+ const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
390
+ const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
391
+ const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
392
+ const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
393
+ const systemZoom = width / window.screen.availWidth;
394
+ const left = (width - w) / 2 / systemZoom + dualScreenLeft
395
+ const top = (height - h) / 2 / systemZoom + dualScreenTop
396
+ const newWindow = window.open(url, title,
397
+ `
398
+ directories=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,
399
+ width=${w / systemZoom},
400
+ height=${h / systemZoom},
401
+ top=${top},
402
+ left=${left}
403
+ `
404
+ )
405
+ if (window.focus) newWindow.focus();
406
+ return newWindow;
407
+ }
408
+ function openReport(pdfReportPath){
409
+ if (pdfReportPath.indexOf("No data available") < 0){
410
+ open(pdfReportPath, "content", "target=_blank, toolbar=no, scrollbars=yes, resizable=yes, width=800, height=600, top=0, left=0");
411
+ }
412
+ else {
413
+ window.alert("Sorry , unable to print a report according to your selection!");
414
+ }
415
+ }
416
+ function getRoute(loadURL, callback) {
417
+ sendRequest(loadURL, null, 'GET', function(data) {
418
+ callback(handleHtmlData (data, null));
419
+ });
420
+ }