tina4ruby 0.5.2 → 3.0.0

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 +360 -559
  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 +242 -77
  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 +43 -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 +1336 -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 +27 -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 +484 -0
  47. data/lib/tina4/migration.rb +132 -29
  48. data/lib/tina4/orm.rb +337 -31
  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 +40 -4
  60. data/lib/tina4/queue_backends/lite_backend.rb +88 -0
  61. data/lib/tina4/rack_app.rb +314 -23
  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 +134 -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 +57 -21
  88. metadata +51 -19
  89. data/lib/tina4/public/js/tina4.js +0 -134
  90. data/lib/tina4/public/js/tina4helper.js +0 -387
@@ -1,387 +0,0 @@
1
- var formToken = null;
2
-
3
- /**
4
- * Sends an http request
5
- * @param url
6
- * @param request
7
- * @param method
8
- * @param callback
9
- */
10
- function sendRequest(url, request, method, callback) {
11
- // Default values
12
- if (url === undefined) url = "";
13
- if (request === undefined) request = null;
14
- if (method === undefined) method = 'GET';
15
-
16
- const xhr = new XMLHttpRequest();
17
- xhr.open(method, url, true);
18
-
19
- // Add authorization header if token exists
20
- if (formToken !== null) {
21
- xhr.setRequestHeader('Authorization', 'Bearer ' + formToken);
22
- }
23
-
24
- // ────────────────────────────────────────────────
25
- // Content-Type logic – only set when appropriate
26
- // ────────────────────────────────────────────────
27
- let isFormData = request instanceof FormData;
28
-
29
- if (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PUT' || method.toUpperCase() === 'PATCH') {
30
- if (isFormData) {
31
- //DO not touch this
32
- } else if (typeof request === 'object' && request !== null) {
33
- //Becomes a JSON String
34
- request = JSON.stringify(request);
35
- xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
36
- } else if (typeof request === 'string') {
37
- // Already a string – assume JSON or let server decide
38
- // You can set charset=UTF-8 if you're sure it's JSON/text
39
- xhr.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8');
40
- }
41
- }
42
-
43
- // ────────────────────────────────────────────────
44
- // Response handling
45
- // ────────────────────────────────────────────────
46
- xhr.onload = function () {
47
- let content = xhr.response;
48
-
49
- // Update token if server sent a fresh one
50
- const freshToken = xhr.getResponseHeader('FreshToken');
51
- if (freshToken && freshToken !== '') {
52
- formToken = freshToken;
53
- }
54
-
55
- try {
56
- content = JSON.parse(content);
57
- } catch (e) {
58
- // Not JSON → keep as raw string/text
59
- }
60
-
61
- if (typeof callback === 'function') {
62
- callback(content, xhr.status, xhr);
63
- }
64
- };
65
-
66
- // Optional: handle errors
67
- xhr.onerror = function () {
68
- if (typeof callback === 'function') {
69
- callback(null, xhr.status, xhr);
70
- }
71
- };
72
-
73
- // Send the body (or null)
74
- xhr.send(request);
75
- }
76
-
77
- /**
78
- * Gets form data based on a form Id
79
- * @param formId
80
- * @returns {FormData}
81
- */
82
- function getFormData(formId) {
83
- let data = new FormData();
84
- let elements = document.querySelectorAll("#" + formId + " select, #" + formId + " input, #" + formId + " textarea");
85
- for (let ie = 0; ie < elements.length; ie++ )
86
- {
87
- let element = elements[ie];
88
- //refresh the token
89
- if (element.name === 'formToken' && formToken !== null) {
90
- element.value = formToken;
91
- }
92
- if (element.name) {
93
- if (element.type === 'file') {
94
- for (let i = 0; i < element.files.length; i++) {
95
- let fileData = element.files[i];
96
- let elementName = element.name;
97
- if (fileData !== undefined) {
98
- if (element.files.length > 1 && !elementName.includes('[')) {
99
- elementName = elementName + '[]';
100
- }
101
- data.append(elementName, fileData, fileData.name);
102
- }
103
- }
104
- } else if (element.type === 'checkbox' || element.type === 'radio') {
105
- if (element.checked) {
106
- data.append(element.name, element.value)
107
- } else {
108
- if (element.type !== 'radio') {
109
- data.append(element.name, "0")
110
- }
111
- }
112
- } else {
113
- if (element.value === '') {
114
- element.value = null;
115
- }
116
- data.append(element.name, element.value);
117
- }
118
- }
119
- }
120
- return data;
121
- }
122
-
123
- /**
124
- * Handles the data returned from a request
125
- * @param data
126
- * @param targetElement
127
- */
128
- function handleHtmlData(data, targetElement) {
129
- //Strip out the scripts
130
- if (data === "") return '';
131
- const parser = new DOMParser();
132
- const htmlData = parser.parseFromString(data.includes !== undefined && data.includes('<html>') ? data : '<body>'+data+'</body></html>', 'text/html');
133
- const body = htmlData.querySelector('body');
134
- const scripts = body.querySelectorAll('script');
135
- // remove the script tags
136
- body.querySelectorAll('script').forEach(script => script.remove());
137
-
138
- if (targetElement !== null) {
139
- if (body.children.length > 0) {
140
- document.getElementById(targetElement).replaceChildren(...body.children);
141
- } else {
142
- document.getElementById(targetElement).replaceChildren(body.innerHTML);
143
- }
144
- if (scripts) {
145
- scripts.forEach(script => {
146
- const newScript = document.createElement("script");
147
- newScript.type = 'text/javascript';
148
- newScript.async = true;
149
- newScript.textContent = script.innerText;
150
- document.getElementById(targetElement).append(newScript);
151
- });
152
- }
153
- } else {
154
- if (scripts) {
155
- scripts.forEach(script => {
156
- const newScript = document.createElement("script");
157
- newScript.type = 'text/javascript';
158
- newScript.async = true;
159
- newScript.textContent = script.innerText;
160
- document.body.append(newScript);
161
- console.log(newScript);
162
- });
163
- }
164
-
165
- return body.innerHTML;
166
- }
167
-
168
- return '';
169
- }
170
-
171
- /**
172
- * Loads a page to a target html element
173
- * @param loadURL
174
- * @param targetElement
175
- * @param callback
176
- * @callback
177
- */
178
- function loadPage(loadURL, targetElement, callback = null) {
179
- if (targetElement === undefined) targetElement = 'content';
180
- sendRequest(loadURL, null, "GET", function(data) {
181
- let processedHTML = '';
182
- if (document.getElementById(targetElement) !== null) {
183
- processedHTML = handleHtmlData(data, targetElement);
184
- } else {
185
- if (callback) {
186
- callback(data);
187
- } else {
188
- console.log('TINA4 - define targetElement or callback for loadPage', data);
189
- }
190
- return;
191
- }
192
-
193
- if (callback) {
194
- callback(processedHTML, data);
195
- }
196
- });
197
- }
198
-
199
- /**
200
- * Shows a form from a URL in a target html element
201
- * @param action
202
- * @param loadURL
203
- * @param targetElement
204
- * @param callback
205
- */
206
- function showForm(action, loadURL, targetElement, callback = null) {
207
- if (targetElement === undefined) targetElement = 'form';
208
-
209
- if (action === 'create') action = 'GET';
210
- if (action === 'edit') action = 'GET';
211
- if (action === 'delete') action = 'DELETE';
212
-
213
- sendRequest(loadURL, null, action, function(data) {
214
- let processedHTML = '';
215
- if (data.message !== undefined) {
216
- processedHTML = handleHtmlData ((data.message), targetElement);
217
- } else {
218
- if (document.getElementById(targetElement) !== null) {
219
- processedHTML = handleHtmlData (data, targetElement);
220
- } else {
221
- if (callback) {
222
- callback(data);
223
- } else {
224
- console.log('TINA4 - define targetElement or callback for showForm', data);
225
- }
226
- return;
227
- }
228
- }
229
-
230
- if (callback) {
231
- callback(processedHTML);
232
- }
233
- });
234
- }
235
-
236
- /**
237
- * Post URL posts data to a specific url
238
- * @param url
239
- * @param data
240
- * @param targetElement
241
- * @param callback
242
- */
243
- function postUrl(url, data, targetElement, callback= null) {
244
- sendRequest(url, data, 'POST', function(data) {
245
- let processedHTML = '';
246
- if (data.message !== undefined) {
247
- processedHTML = handleHtmlData ((data.message), targetElement);
248
- } else {
249
- if (document.getElementById(targetElement) !== null) {
250
- processedHTML = handleHtmlData (data, targetElement);
251
- } else {
252
- if (callback) {
253
- callback(data);
254
- } else {
255
- console.log('TINA4 - define targetElement or callback for postUrl', data);
256
- }
257
- return;
258
- }
259
- }
260
-
261
- if (callback) {
262
- callback(processedHTML,data)
263
- }
264
- });
265
- }
266
-
267
- /**
268
- * Saves a form to a POST end point
269
- * @param formId
270
- * @param targetURL
271
- * @param targetElement
272
- * @param callback - optional
273
- */
274
- function saveForm(formId, targetURL, targetElement, callback = null) {
275
- if (targetElement === undefined) targetElement = 'message';
276
- //compile a data model
277
- let data = getFormData(formId);
278
-
279
- postUrl(targetURL, data, targetElement, callback);
280
- }
281
-
282
- /**
283
- * Alias of saveForm
284
- * @param formId
285
- * @param targetURL
286
- * @param targetElement
287
- * @param callback
288
- */
289
- function postForm(formId, targetURL, targetElement, callback = null){
290
- saveForm(formId, targetURL, targetElement, callback)
291
- }
292
-
293
- /**
294
- * Alias of saveForm
295
- * @param formId
296
- * @param targetURL
297
- * @param targetElement
298
- * @param callback
299
- */
300
- function submitForm(formId, targetURL, targetElement, callback = null){
301
- saveForm(formId, targetURL, targetElement, callback)
302
- }
303
-
304
- /**
305
- * Shows a message
306
- * @param message
307
- */
308
- function showMessage(message) {
309
- 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>';
310
- }
311
-
312
- /**
313
- * Set cookie
314
- * @param name
315
- * @param value
316
- * @param days
317
- */
318
- function setCookie(name, value, days) {
319
- let expires = "";
320
- if (days) {
321
- let date = new Date();
322
- date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
323
- expires = "; expires=" + date.toUTCString();
324
- }
325
- document.cookie = name + "=" + (value || "") + expires + "; path=/";
326
- }
327
-
328
- /**
329
- * Get cookie
330
- * @param name
331
- * @returns {null|string}
332
- */
333
- function getCookie(name) {
334
- let nameEQ = name + "=";
335
- let ca = document.cookie.split(';');
336
- for (let i = 0; i < ca.length; i++) {
337
- var c = ca[i];
338
- while (c.charAt(0) == ' ') c = c.substring(1, c.length);
339
- if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
340
- }
341
- return null;
342
- }
343
-
344
- //https://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen
345
- const popupCenter = ({url, title, w, h}) => {
346
- // Fixes dual-screen position Most browsers Firefox
347
- const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
348
- const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
349
-
350
- const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
351
- const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
352
-
353
- const systemZoom = width / window.screen.availWidth;
354
- const left = (width - w) / 2 / systemZoom + dualScreenLeft
355
- const top = (height - h) / 2 / systemZoom + dualScreenTop
356
- const newWindow = window.open(url, title,
357
- `
358
- directories=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,
359
- width=${w / systemZoom},
360
- height=${h / systemZoom},
361
- top=${top},
362
- left=${left}
363
- `
364
- )
365
-
366
- if (window.focus) newWindow.focus();
367
- return newWindow;
368
- }
369
-
370
- /**
371
- * Opens a popup window
372
- * @param pdfReportPath
373
- */
374
- function openReport(pdfReportPath){
375
- if (pdfReportPath.indexOf("No data available") < 0){
376
- open(pdfReportPath, "content", "target=_blank, toolbar=no, scrollbars=yes, resizable=yes, width=800, height=600, top=0, left=0");
377
- }
378
- else {
379
- window.alert("Sorry , unable to print a report according to your selection!");
380
- }
381
- }
382
-
383
- function getRoute(loadURL, callback) {
384
- sendRequest(loadURL, null, 'GET', function(data) {
385
- callback(handleHtmlData (data, null));
386
- });
387
- }