orionis 0.561.0__py3-none-any.whl → 0.562.0__py3-none-any.whl

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.
@@ -0,0 +1,23 @@
1
+ from orionis.console.args.argument import CLIArgument
2
+ from orionis.console.base.command import BaseCommand
3
+
4
+ class {{command-class}}(BaseCommand):
5
+
6
+ # Command executed from the CLI, by convention starts with "app:".
7
+ signature: str = "{{command-signature}}"
8
+
9
+ # Brief description of the command.
10
+ description: str = "{{command-description}}"
11
+
12
+ # List of command arguments and options.
13
+ arguments: list[CLIArgument] = [
14
+ # Example:
15
+ # CLIArgument("--name", type=str, help="Example name", required=True)
16
+ ]
17
+
18
+ async def handle(self) -> None:
19
+ """
20
+ Main logic of the command.
21
+ Override this method to implement the desired behavior.
22
+ """
23
+ pass
@@ -0,0 +1,167 @@
1
+ from orionis.console.base.scheduler_event_listener import BaseScheduleEventListener
2
+ from orionis.console.contracts.schedule import ISchedule
3
+ from orionis.console.entities.event_job import EventJob
4
+
5
+ class {{name-listener}}(BaseScheduleEventListener):
6
+
7
+ async def before(self, event: EventJob, schedule: ISchedule) -> None:
8
+ """
9
+ Called before processing a job submission event.
10
+
11
+ Parameters
12
+ ----------
13
+ event : EventJob
14
+ The job submission event containing details about the job.
15
+ schedule : ISchedule
16
+ The associated schedule instance managing the job.
17
+
18
+ Returns
19
+ -------
20
+ None
21
+ This method does not return any value.
22
+ """
23
+ pass
24
+
25
+ async def after(self, event: EventJob, schedule: ISchedule) -> None:
26
+ """
27
+ Called after processing a job execution event.
28
+
29
+ Parameters
30
+ ----------
31
+ event : EventJob
32
+ The job execution event containing details about the executed job.
33
+ schedule : ISchedule
34
+ The associated schedule instance managing the job.
35
+
36
+ Returns
37
+ -------
38
+ None
39
+ This method does not return any value.
40
+ """
41
+ pass
42
+
43
+ async def onSuccess(self, event: EventJob, schedule: ISchedule) -> None:
44
+ """
45
+ Called when a job is successfully executed.
46
+
47
+ Parameters
48
+ ----------
49
+ event : EventJob
50
+ The event containing details about the successful job execution.
51
+ schedule : ISchedule
52
+ The associated schedule instance managing the job.
53
+
54
+ Returns
55
+ -------
56
+ None
57
+ This method does not return any value.
58
+ """
59
+ pass
60
+
61
+ async def onFailure(self, event: EventJob, schedule: ISchedule) -> None:
62
+ """
63
+ Called when a job execution fails.
64
+
65
+ Parameters
66
+ ----------
67
+ event : EventJob
68
+ The event containing details about the job execution failure.
69
+ schedule : ISchedule
70
+ The associated schedule instance managing the job.
71
+
72
+ Returns
73
+ -------
74
+ None
75
+ This method does not return any value.
76
+ """
77
+ pass
78
+
79
+ async def onMissed(self, event: EventJob, schedule: ISchedule) -> None:
80
+ """
81
+ Called when a job execution is missed.
82
+
83
+ Parameters
84
+ ----------
85
+ event : EventJob
86
+ The event containing details about the missed job execution.
87
+ schedule : ISchedule
88
+ The associated schedule instance managing the job.
89
+
90
+ Returns
91
+ -------
92
+ None
93
+ This method does not return any value.
94
+ """
95
+ pass
96
+
97
+ async def onMaxInstances(self, event: EventJob, schedule: ISchedule) -> None:
98
+ """
99
+ Called when a job exceeds the maximum allowed instances.
100
+
101
+ Parameters
102
+ ----------
103
+ event : EventJob
104
+ The event containing details about the max instances violation.
105
+ schedule : ISchedule
106
+ The associated schedule instance managing the job.
107
+
108
+ Returns
109
+ -------
110
+ None
111
+ This method does not return any value.
112
+ """
113
+ pass
114
+
115
+ async def onPaused(self, event: EventJob, schedule: ISchedule) -> None:
116
+ """
117
+ Called when the scheduler is paused.
118
+
119
+ Parameters
120
+ ----------
121
+ event : EventJob
122
+ The event containing details about the scheduler pause.
123
+ schedule : ISchedule
124
+ The associated schedule instance managing the jobs.
125
+
126
+ Returns
127
+ -------
128
+ None
129
+ This method does not return any value.
130
+ """
131
+ pass
132
+
133
+ async def onResumed(self, event: EventJob, schedule: ISchedule) -> None:
134
+ """
135
+ Called when the scheduler is resumed.
136
+
137
+ Parameters
138
+ ----------
139
+ event : EventJob
140
+ The event containing details about the scheduler resume.
141
+ schedule : ISchedule
142
+ The associated schedule instance managing the jobs.
143
+
144
+ Returns
145
+ -------
146
+ None
147
+ This method does not return any value.
148
+ """
149
+ pass
150
+
151
+ async def onRemoved(self, event: EventJob, schedule: ISchedule) -> None:
152
+ """
153
+ Called when a job is removed from the scheduler.
154
+
155
+ Parameters
156
+ ----------
157
+ event : EventJob
158
+ The event containing details about the job removal.
159
+ schedule : ISchedule
160
+ The associated schedule instance managing the jobs.
161
+
162
+ Returns
163
+ -------
164
+ None
165
+ This method does not return any value.
166
+ """
167
+ pass
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.561.0"
8
+ VERSION = "0.562.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
@@ -0,0 +1,565 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Orionis Test Dashboard</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="icon" href="https://orionis-framework.com/svg/logo.svg" />
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10
+ <link href="https://unpkg.com/gridjs/dist/theme/mermaid.min.css" rel="stylesheet" />
11
+ <link href="https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap" rel="stylesheet">
12
+ <style>
13
+ #test-table,
14
+ #test-table .gridjs-td,
15
+ #test-table .gridjs-th {
16
+ font-family: 'Fira Mono', 'Consolas', 'Menlo', 'Monaco', 'Liberation Mono', monospace !important;
17
+ }
18
+
19
+ #test-table .gridjs-td,
20
+ #test-table .gridjs-th {
21
+ max-width: 220px;
22
+ white-space: normal;
23
+ overflow-wrap: anywhere;
24
+ text-overflow: initial;
25
+ vertical-align: middle;
26
+ font-size: 0.85rem;
27
+ line-height: 1.28;
28
+ }
29
+
30
+ #test-table .gridjs-th {
31
+ font-size: 0.82rem !important;
32
+ font-weight: 600;
33
+ letter-spacing: 0.01em;
34
+ }
35
+
36
+ #test-table .gridjs-td.status-cell {
37
+ max-width: 90px;
38
+ text-align: center;
39
+ font-weight: 600;
40
+ letter-spacing: 0.02em;
41
+ white-space: nowrap;
42
+ font-size: 0.75em;
43
+ padding-top: 0.2em;
44
+ padding-bottom: 0.2em;
45
+ }
46
+
47
+ #test-table .gridjs-td.doc-cell {
48
+ max-width: 60px;
49
+ text-align: center;
50
+ white-space: nowrap;
51
+ }
52
+
53
+ #test-table {
54
+ overflow-x: auto;
55
+ }
56
+
57
+ .badge-status {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ gap: 0.3em;
61
+ padding: 0.13em 0.6em;
62
+ border-radius: 9999px;
63
+ font-size: 0.78em;
64
+ font-weight: 600;
65
+ letter-spacing: 0.01em;
66
+ }
67
+
68
+ .badge-passed {
69
+ background: #d1fae5;
70
+ color: #059669;
71
+ }
72
+
73
+ .badge-failed {
74
+ background: #fee2e2;
75
+ color: #dc2626;
76
+ }
77
+
78
+ .badge-errors {
79
+ background: #fef9c3;
80
+ color: #ca8a04;
81
+ }
82
+
83
+ .badge-skipped {
84
+ background: #e0e7ff;
85
+ color: #3730a3;
86
+ }
87
+
88
+ .doc-btn {
89
+ background: #eef2ff;
90
+ color: #3730a3;
91
+ border: none;
92
+ border-radius: 0.5em;
93
+ padding: 0.2em 0.8em;
94
+ font-size: 0.92em;
95
+ font-weight: 500;
96
+ cursor: pointer;
97
+ transition: background 0.15s;
98
+ font-family: inherit;
99
+ }
100
+
101
+ .doc-btn:hover {
102
+ background: #c7d2fe;
103
+ }
104
+
105
+ /* Modal styles */
106
+ .orionis-modal {
107
+ position: fixed;
108
+ left: 0;
109
+ top: 0;
110
+ width: 100vw;
111
+ height: 100vh;
112
+ background: rgba(30, 41, 59, 0.45);
113
+ display: flex !important;
114
+ align-items: center;
115
+ justify-content: center;
116
+ z-index: 10000;
117
+ }
118
+
119
+ .orionis-modal-content {
120
+ background: white;
121
+ max-width: 70vw;
122
+ width: 70vw;
123
+ padding: 2em 1.5em 1.2em 1.5em;
124
+ border-radius: 1.2em;
125
+ box-shadow: 0 8px 32px 0 rgba(31, 41, 55, 0.18);
126
+ position: relative;
127
+ }
128
+
129
+ .orionis-modal-close {
130
+ position: absolute;
131
+ top: 0.7em;
132
+ right: 1em;
133
+ font-size: 1.3em;
134
+ color: #64748b;
135
+ background: none;
136
+ border: none;
137
+ cursor: pointer;
138
+ }
139
+
140
+ .orionis-modal-title {
141
+ font-size: 0.98em; /* reducido de 1.08em */
142
+ font-weight: 600;
143
+ margin-bottom: 0.7em;
144
+ color: #3730a3;
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 0.5em;
148
+ font-family: inherit;
149
+ }
150
+
151
+ .orionis-modal-pre {
152
+ white-space: pre-wrap;
153
+ font-size: 0.92em;
154
+ color: #334155;
155
+ background: #f1f5f9;
156
+ padding: 1em;
157
+ border-radius: 0.7em;
158
+ max-height: 320px;
159
+ overflow: auto;
160
+ font-family: 'Fira Mono', 'Consolas', 'Menlo', 'Monaco', 'Liberation Mono', monospace;
161
+ }
162
+ </style>
163
+ </head>
164
+ <body class="bg-gray-100 text-gray-800 font-sans">
165
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 py-8">
166
+
167
+ <!-- Header -->
168
+ <header class="bg-gradient-to-r from-blue-900 to-cyan-400 text-white rounded-2xl shadow-xl p-6 mb-10">
169
+ <div class="flex flex-col md:flex-row justify-between items-center gap-4">
170
+ <div class="flex items-center gap-4">
171
+ <img src="https://orionis-framework.com/svg/logo.svg" alt="Orionis Logo"
172
+ class="h-10 brightness-0 invert" />
173
+ <h1 class="text-2xl font-light tracking-wider">Orionis Testing Results Dashboard</h1>
174
+ </div>
175
+ <div id="timestamp" class="text-sm text-white/90"></div>
176
+ </header>
177
+
178
+ <!-- Execution Summary Card -->
179
+ <div class="w-full mb-10">
180
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-indigo-500 flex flex-col sm:flex-row items-center justify-between gap-4">
181
+ <div>
182
+ <div class="flex items-center gap-2 text-xs font-semibold text-gray-500 uppercase">
183
+ <i class="bi bi-graph-up-arrow text-indigo-500 text-base"></i>
184
+ <span>Execution Summary</span>
185
+ <span class="inline-flex items-center gap-1 bg-indigo-100 text-indigo-700 px-2 py-0.5 rounded-full text-[0.7em] font-bold ml-2">
186
+ <i class="bi bi-lightning-charge-fill text-yellow-500"></i> {{orionis-testing-persistent}}
187
+ </span>
188
+ </div>
189
+ <div class="text-lg font-bold text-gray-800 mt-2" id="execution-summary-title">Select an execution</div>
190
+ <div class="text-sm text-gray-600 mt-1" id="execution-summary-desc">Select an execution to view the summary.</div>
191
+ </div>
192
+ <div class="flex items-center gap-4">
193
+ <div class="flex items-center gap-1 text-gray-700">
194
+ <span id="execution-time">Duration: --:--:--</span>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+
200
+ <!-- Summary Cards -->
201
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 mb-10">
202
+
203
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-green-500">
204
+ <div class="text-xs font-semibold text-gray-500 uppercase">Passed</div>
205
+ <div class="text-4xl font-bold text-gray-800 mt-2" id="passed">0</div>
206
+ <div class="mt-4 bg-gray-200 rounded-full h-2">
207
+ <div class="bg-green-500 h-2 rounded-full" id="passed-progress" style="width: 0%"></div>
208
+ </div>
209
+ </div>
210
+
211
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-red-500">
212
+ <div class="text-xs font-semibold text-gray-500 uppercase">Failed</div>
213
+ <div class="text-4xl font-bold text-gray-800 mt-2" id="failed">0</div>
214
+ </div>
215
+
216
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-yellow-500">
217
+ <div class="text-xs font-semibold text-gray-500 uppercase">Errors</div>
218
+ <div class="text-4xl font-bold text-gray-800 mt-2" id="errors">0</div>
219
+ </div>
220
+
221
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-t-4 border-blue-500">
222
+ <div class="text-xs font-semibold text-gray-500 uppercase">Skipped</div>
223
+ <div class="text-4xl font-bold text-gray-800 mt-2" id="skipped">0</div>
224
+ </div>
225
+
226
+ </div>
227
+
228
+ <!-- Download Buttons & Select -->
229
+ <div class="flex flex-wrap justify-between items-center mb-10 gap-4">
230
+ <!-- Buttons to the left -->
231
+ <div class="flex flex-wrap gap-4">
232
+ <button disabled id="download-json" class="flex items-center gap-2 bg-blue-300 text-white px-4 py-2 rounded shadow cursor-not-allowed opacity-60">
233
+ <i class="bi bi-file-earmark-code-fill text-lg"></i>
234
+ <span>Download JSON</span>
235
+ </button>
236
+ </div>
237
+ <!-- Elegant Select to the right -->
238
+ <div>
239
+ <select class="appearance-none bg-white border border-gray-300 text-gray-700 py-2 px-4 pr-10 rounded-lg shadow focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-blue-400 transition text-base font-medium" id="test-select">
240
+ </select>
241
+ </div>
242
+ </div>
243
+
244
+ <!-- Table Summary View with Grid.js -->
245
+ <div class="bg-white rounded-xl shadow-md p-5 border border-indigo-100">
246
+ <div class="flex items-center gap-2 mb-3">
247
+ <i class="bi bi-table text-lg text-indigo-500"></i>
248
+ <h2 class="text-lg font-semibold text-indigo-900">Execution Details</h2>
249
+ </div>
250
+ <div class="text-gray-500 mb-4 text-sm">
251
+ See all test cases for the selected execution.<br>Click
252
+ <span class="inline-flex items-center gap-1 px-1.5 py-0.5 bg-blue-50 text-blue-700 rounded text-xs font-mono">
253
+ <i class="bi bi-journal-text"></i> View
254
+ </span>
255
+ to show the docstring.
256
+ </div>
257
+ <div id="test-table"></div>
258
+ </div>
259
+
260
+ <!-- Footer -->
261
+ <footer class="mt-12 text-center text-gray-500 text-sm py-6">
262
+ Developed with the power of
263
+ <a href="https://orionis-framework.com/" target="_blank" rel="noopener"
264
+ class="font-semibold text-blue-700 hover:underline">
265
+ Orionis Framework
266
+ </a>
267
+ <i class="bi bi-stars text-yellow-400 align-middle ml-1"></i>
268
+ </footer>
269
+
270
+ <!-- Scripts -->
271
+ <script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
272
+ <script>
273
+
274
+ // Result Orionis Testing
275
+ const data = {{orionis-testing-result}};
276
+
277
+ // Live Clock (Header Timestamp)
278
+ function updateClock() {
279
+ const now = new Date();
280
+ const formatted = now.toLocaleString('en-US', {
281
+ year: 'numeric',
282
+ month: '2-digit',
283
+ day: '2-digit',
284
+ hour: '2-digit',
285
+ minute: '2-digit',
286
+ second: '2-digit',
287
+ hour12: false
288
+ });
289
+ document.getElementById("timestamp").textContent = `Current time: ${formatted}`;
290
+ }
291
+ updateClock();
292
+ setInterval(updateClock, 1000);
293
+
294
+ // Populate Dropdown in English
295
+ let html = '<option selected disabled>Select an execution</option>';
296
+ data.forEach((item, i) => {
297
+ const date = new Date(item.timestamp);
298
+ const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1)
299
+ .toString()
300
+ .padStart(2, '0')}-${date.getDate()
301
+ .toString()
302
+ .padStart(2, '0')} ${date.getHours()
303
+ .toString()
304
+ .padStart(2, '0')}:${date.getMinutes()
305
+ .toString()
306
+ .padStart(2, '0')}:${date.getSeconds()
307
+ .toString()
308
+ .padStart(2, '0')}`;
309
+ html += `<option value="${i}">Execution ${i + 1} - ${formattedDate}</option>`;
310
+ });
311
+ document.getElementById("test-select").innerHTML = html;
312
+
313
+ // Event Listener for the dropdown
314
+ document.getElementById("test-select").addEventListener("change", function () {
315
+ const selectedIndex = this.value;
316
+ const selectedData = data[selectedIndex];
317
+
318
+ // Animate Counters
319
+ function animateValue(id, start, end, duration) {
320
+ const obj = document.getElementById(id);
321
+ let startTimestamp = null;
322
+ const step = (timestamp) => {
323
+ if (!startTimestamp) startTimestamp = timestamp;
324
+ const progress = Math.min((timestamp - startTimestamp) / duration, 1);
325
+ obj.textContent = Math.floor(progress * (end - start) + start);
326
+ if (progress < 1) {
327
+ window.requestAnimationFrame(step);
328
+ } else {
329
+ obj.textContent = end;
330
+ }
331
+ };
332
+ window.requestAnimationFrame(step);
333
+ }
334
+ animateValue("passed", Number(document.getElementById("passed").textContent), selectedData.passed, 500);
335
+ animateValue("failed", Number(document.getElementById("failed").textContent), selectedData.failed, 500);
336
+ animateValue("errors", Number(document.getElementById("errors").textContent), selectedData.errors, 500);
337
+ animateValue("skipped", Number(document.getElementById("skipped").textContent), selectedData.skipped, 500);
338
+
339
+ // Animate Progress Bar
340
+ const passedBar = document.getElementById("passed-progress");
341
+ passedBar.style.transition = "width 0.6s cubic-bezier(0.4,0,0.2,1)";
342
+ const passedPercentage = (selectedData.passed / selectedData.total_tests) * 100;
343
+ passedBar.style.width = `${passedPercentage}%`;
344
+
345
+ // Update Execution Summary
346
+ const date = new Date(selectedData.timestamp);
347
+ const formattedDate = `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`;
348
+ document.getElementById("execution-summary-title").innerHTML = `<span class="inline-flex items-center gap-2"><i class="bi bi-activity text-indigo-500"></i> Execution - <span class="font-mono">${formattedDate}</span></span>`;
349
+ document.getElementById("execution-summary-desc").innerHTML =
350
+ `<span class="inline-flex gap-2 flex-wrap">
351
+ <span class="bg-green-100 text-green-800 px-2 py-1 rounded text-xs font-semibold"><i class="bi bi-check-circle-fill"></i> Passed: ${selectedData.passed}</span>
352
+ <span class="bg-red-100 text-red-800 px-2 py-1 rounded text-xs font-semibold"><i class="bi bi-x-circle-fill"></i> Failed: ${selectedData.failed}</span>
353
+ <span class="bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-xs font-semibold"><i class="bi bi-exclamation-triangle-fill"></i> Errors: ${selectedData.errors}</span>
354
+ <span class="bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs font-semibold"><i class="bi bi-skip-forward-fill"></i> Skipped: ${selectedData.skipped}</span>
355
+ <span class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-xs font-semibold"><i class="bi bi-list-ol"></i> Total: ${selectedData.total_tests}</span>
356
+ <span class="bg-indigo-100 text-indigo-800 px-2 py-1 rounded text-xs font-semibold"><i class="bi bi-bar-chart-fill"></i> Success: ${selectedData.success_rate.toFixed(2)}%</span>
357
+ </span>`;
358
+
359
+ // Update Duration
360
+ function formatTime(seconds) {
361
+ const h = Math.floor(seconds / 3600).toString().padStart(2, '0');
362
+ const m = Math.floor((seconds % 3600) / 60).toString().padStart(2, '0');
363
+ const s = Math.floor(seconds % 60).toString().padStart(2, '0');
364
+ return `${h}:${m}:${s}`;
365
+ }
366
+ document.getElementById("execution-time").innerHTML = `<i class="bi bi-clock-history text-indigo-500"></i> Duration: <span class="font-mono">${formatTime(selectedData.total_time)}</span>`;
367
+
368
+ // Enable Download Button
369
+ document.getElementById("download-json").disabled = false;
370
+ document.getElementById("download-json").classList.remove("bg-blue-300", "cursor-not-allowed", "opacity-60");
371
+ document.getElementById("download-json").classList.add("bg-blue-600", "hover:bg-blue-700", "cursor-pointer", "opacity-100");
372
+ });
373
+
374
+ // Download JSON report with execution date and time in filename
375
+ document.getElementById("download-json").addEventListener("click", function () {
376
+ const btn = this;
377
+ const select = document.getElementById("test-select");
378
+ const selectedIndex = select.value;
379
+ if (selectedIndex === "" || selectedIndex === null || isNaN(selectedIndex)) return;
380
+
381
+ // Disable button and show loading state
382
+ btn.disabled = true;
383
+ btn.classList.add("opacity-60", "cursor-not-allowed");
384
+ btn.classList.remove("bg-blue-600", "hover:bg-blue-700", "cursor-pointer");
385
+ const originalText = btn.innerHTML;
386
+ btn.innerHTML = `<span class="animate-spin mr-2"><i class="bi bi-arrow-repeat"></i></span> Generating...`;
387
+
388
+ setTimeout(() => {
389
+ const selectedData = data[selectedIndex];
390
+ const jsonString = JSON.stringify(selectedData, null, 2);
391
+ const date = new Date(selectedData.timestamp);
392
+ // Format: YYYYMMDD_HHMMSS
393
+ const formattedDate = `${date.getFullYear()}${(date.getMonth()+1).toString().padStart(2,'0')}${date.getDate().toString().padStart(2,'0')}_${date.getHours().toString().padStart(2,'0')}${date.getMinutes().toString().padStart(2,'0')}${date.getSeconds().toString().padStart(2,'0')}`;
394
+ const filename = `test_report_${formattedDate}.json`;
395
+ const blob = new Blob([jsonString], { type: "application/json" });
396
+ const url = URL.createObjectURL(blob);
397
+ const a = document.createElement("a");
398
+ a.href = url;
399
+ a.download = filename;
400
+ document.body.appendChild(a);
401
+ a.click();
402
+ document.body.removeChild(a);
403
+ URL.revokeObjectURL(url);
404
+
405
+ // Restore button state
406
+ btn.disabled = false;
407
+ btn.classList.remove("opacity-60", "cursor-not-allowed");
408
+ btn.classList.add("bg-blue-600", "hover:bg-blue-700", "cursor-pointer");
409
+ btn.innerHTML = originalText;
410
+ }, 600);
411
+ });
412
+
413
+ // Show doc string in a modal dialog (event delegation)
414
+ function showDocString(doc) {
415
+ // Remove any existing modal
416
+ const oldModal = document.querySelector('.orionis-modal');
417
+ if (oldModal) oldModal.remove();
418
+
419
+ // Create modal element
420
+ const modal = document.createElement('div');
421
+ modal.className = 'orionis-modal';
422
+ modal.style.display = 'flex'; // Ensure modal is visible
423
+
424
+ modal.innerHTML = `
425
+ <div class="orionis-modal-content">
426
+ <button class="orionis-modal-close" aria-label="Cerrar" type="button">&times;</button>
427
+ <div class="orionis-modal-title">
428
+ <i class="bi bi-journal-text"></i> Docstring
429
+ </div>
430
+ <pre class="orionis-modal-pre">${doc ? String(doc).replace(/</g, "&lt;").replace(/>/g, "&gt;") : 'No docstring.'}</pre>
431
+ </div>
432
+ `;
433
+ // Close modal when clicking outside content
434
+ modal.addEventListener('mousedown', function (e) {
435
+ if (e.target === modal) modal.remove();
436
+ });
437
+ // Close modal when clicking close button
438
+ modal.querySelector('.orionis-modal-close').onclick = () => modal.remove();
439
+ document.body.appendChild(modal);
440
+ }
441
+
442
+ function renderTestTable(testDetails) {
443
+ if (window.testGrid) {
444
+ try { window.testGrid.destroy(); } catch (e) { }
445
+ }
446
+ const tableDiv = document.getElementById("test-table");
447
+ tableDiv.innerHTML = "";
448
+
449
+ try {
450
+ window.testGrid = new gridjs.Grid({
451
+ columns: [
452
+ { name: "ID", width: "22%" },
453
+ { name: "Class", width: "12%" },
454
+ { name: "Method", width: "14%" },
455
+ { name: "Status", width: "11%" },
456
+ { name: "Time (s)", width: "9%" },
457
+ { name: "File", width: "22%" },
458
+ { name: "Doc", width: "10%" }
459
+ ],
460
+ data: testDetails.map((t, idx) => [
461
+ gridjs.html(`<div title="${t.id}">${t.id}</div>`),
462
+ gridjs.html(`<div title="${t.class}">${t.class}</div>`),
463
+ gridjs.html(`<div title="${t.method}">${t.method}</div>`),
464
+ gridjs.html(`
465
+ <span class="badge-status ${
466
+ t.status === 'PASSED' ? 'badge-passed' :
467
+ t.status === 'FAILED' ? 'badge-failed' :
468
+ t.status === 'ERRORS' ? 'badge-errors' : 'badge-skipped'
469
+ } status-cell">
470
+ ${
471
+ t.status === 'PASSED' ? '<i class="bi bi-check-circle-fill"></i>' :
472
+ t.status === 'FAILED' ? '<i class="bi bi-x-circle-fill"></i>' :
473
+ t.status === 'ERRORS' ? '<i class="bi bi-exclamation-triangle-fill"></i>' :
474
+ '<i class="bi bi-skip-forward-fill"></i>'
475
+ }
476
+ ${t.status}
477
+ </span>
478
+ `),
479
+ t.execution_time,
480
+ gridjs.html(`<div title="${t.file_path}">${t.file_path}</div>`),
481
+ gridjs.html(`<button class="doc-btn doc-cell" data-doc-idx="${idx}" type="button"><i class="bi bi-journal-text"></i> Detail</button>`)
482
+ ]),
483
+ search: {
484
+ enabled: true,
485
+ placeholder: 'Buscar solo por clase...'
486
+ },
487
+ pagination: { limit: 8, summary: false },
488
+ sort: false,
489
+ resizable: true,
490
+ style: {
491
+ th: { 'text-align': 'left', 'background': '#f1f5f9', 'font-size': '1em' }
492
+ }
493
+ }).render(tableDiv);
494
+
495
+ // Delegated event for doc buttons (always works, even after pagination)
496
+ tableDiv.addEventListener('click', function (e) {
497
+ const btn = e.target.closest('.doc-btn');
498
+ if (!btn) return;
499
+ const idx = Number(btn.getAttribute('data-doc-idx'));
500
+ const t = testDetails[idx];
501
+ if (!t) return;
502
+
503
+ let html = '';
504
+ html += `<div class="orionis-modal-title"><i class="bi bi-journal-text"></i> Docstring</div>`;
505
+ html += `<pre class="orionis-modal-pre">${t.doc_string ? String(t.doc_string).replace(/</g, "&lt;").replace(/>/g, "&gt;") : 'No docstring.'}</pre>`;
506
+ if (t.status === 'FAILED' || t.status === 'ERRORS') {
507
+ html += `<div class="orionis-modal-title mt-4"><i class="bi bi-bug-fill"></i> Traceback</div>`;
508
+ html += `<pre class="orionis-modal-pre" style="background:#fee2e2;color:#991b1b">${t.traceback ? String(t.traceback).replace(/</g, "&lt;").replace(/>/g, "&gt;") : 'No traceback.'}</pre>`;
509
+ }
510
+
511
+ // Remove any existing modal
512
+ const oldModal = document.querySelector('.orionis-modal');
513
+ if (oldModal) oldModal.remove();
514
+
515
+ // Create modal element
516
+ const modal = document.createElement('div');
517
+ modal.className = 'orionis-modal';
518
+ modal.style.display = 'flex';
519
+
520
+ modal.innerHTML = `
521
+ <div class="orionis-modal-content">
522
+ <button class="orionis-modal-close" aria-label="Cerrar" type="button">&times;</button>
523
+ ${html}
524
+ </div>
525
+ `;
526
+
527
+ // Close modal when clicking outside content
528
+ modal.addEventListener('mousedown', function (e) {
529
+ if (e.target === modal) modal.remove();
530
+ });
531
+
532
+ // Close modal when clicking close button
533
+ modal.querySelector('.orionis-modal-close').onclick = () => modal.remove();
534
+ document.body.appendChild(modal);
535
+ });
536
+ } catch (err) {
537
+ tableDiv.innerHTML = `<div class="text-red-600 font-mono text-sm py-4">Ocurrió un error al mostrar la tabla.<br>${err.message}</div>`;
538
+ }
539
+ }
540
+
541
+ document.getElementById("test-select").addEventListener("change", function () {
542
+ const selectedIndex = this.value;
543
+ if (!data[selectedIndex]) return;
544
+ renderTestTable(data[selectedIndex].test_details);
545
+ });
546
+
547
+ document.addEventListener("DOMContentLoaded", function () {
548
+ renderTestTable([]);
549
+ });
550
+
551
+ // Show an elegant console message every second using setInterval
552
+ setInterval(function () {
553
+ console.clear();
554
+ console.log(
555
+ "%c✨ Developed with the power of %cOrionis Framework%c 🚀",
556
+ "color:#6366f1;font-weight:bold;font-size:1.1em;",
557
+ "color:#0ea5e9;font-weight:bold;font-size:1.15em;text-shadow:0 1px 0 #fff;",
558
+ "color:#f59e42;font-size:1.1em;"
559
+ );
560
+ }, 1000);
561
+
562
+ </script>
563
+ </body>
564
+
565
+ </html>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionis
3
- Version: 0.561.0
3
+ Version: 0.562.0
4
4
  Summary: Orionis Framework – Elegant, Fast, and Powerful.
5
5
  Home-page: https://github.com/orionis-framework/framework
6
6
  Author: Raul Mauricio Uñate Castro
@@ -81,6 +81,8 @@ orionis/console/output/console.py,sha256=EtSDWRBW8wk0iJdPfB1mzU49krLJBaSAUdVdVOz
81
81
  orionis/console/output/executor.py,sha256=uQjFPOlyZLFj9pcyYPugCqxwJog0AJgK1OcmQH2ELbw,7314
82
82
  orionis/console/request/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  orionis/console/request/cli_request.py,sha256=zFuoFzLVDCcSNcJZAuLAlsc9jfhck5o23qaKc2C0QAg,13231
84
+ orionis/console/stub/command.stub,sha256=kRBdSqt1eBPe3W8xDR5meQBXl3JuRcs75Q6kmfkyRKM,733
85
+ orionis/console/stub/listener.stub,sha256=aFod_RZhoqo1W7l0S7fFBJ0-S46bQqwRAOr47ru8XYU,4846
84
86
  orionis/console/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
87
  orionis/console/tasks/schedule.py,sha256=qFnzoLyK69iaKEWMfEVxLs3S9aWVRzD4G6emTFhJMaY,83386
86
88
  orionis/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -241,7 +243,7 @@ orionis/foundation/providers/scheduler_provider.py,sha256=1do4B09bU_6xbFHHVYYTGM
241
243
  orionis/foundation/providers/testing_provider.py,sha256=SrJRpdvcblx9WvX7x9Y3zc7OQfiTf7la0HAJrm2ESlE,3725
242
244
  orionis/foundation/providers/workers_provider.py,sha256=oa_2NIDH6UxZrtuGkkoo_zEoNIMGgJ46vg5CCgAm7wI,3926
243
245
  orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
244
- orionis/metadata/framework.py,sha256=ZedPapsfGH9Z6U16rVmZ7Z_VQHF4RwOFC2v9GLj8q20,4109
246
+ orionis/metadata/framework.py,sha256=qFRLVT7ha-_n0m8JiCXRHsbpTM9LmzALdqg8BhOPS_A,4109
245
247
  orionis/metadata/package.py,sha256=k7Yriyp5aUcR-iR8SK2ec_lf0_Cyc-C7JczgXa-I67w,16039
246
248
  orionis/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
247
249
  orionis/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -433,8 +435,9 @@ orionis/test/validators/web_report.py,sha256=n9BfzOZz6aEiNTypXcwuWbFRG0OdHNSmCNu
433
435
  orionis/test/validators/workers.py,sha256=rWcdRexINNEmGaO7mnc1MKUxkHKxrTsVuHgbnIfJYgc,1206
434
436
  orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
435
437
  orionis/test/view/render.py,sha256=f-zNhtKSg9R5Njqujbg2l2amAs2-mRVESneLIkWOZjU,4082
436
- orionis-0.561.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
437
- orionis-0.561.0.dist-info/METADATA,sha256=RxU48ZosvjNC16PEfzBqUWnJu1XVauDGemE7jGUJCao,4801
438
- orionis-0.561.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
439
- orionis-0.561.0.dist-info/top_level.txt,sha256=lyXi6jArpqJ-0zzNqd_uwsH-z9TCEBVBL-pC3Ekv7hU,8
440
- orionis-0.561.0.dist-info/RECORD,,
438
+ orionis/test/view/report.stub,sha256=QLqqCdRoENr3ECiritRB3DO_MOjRQvgBh5jxZ3Hs1r0,28189
439
+ orionis-0.562.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
440
+ orionis-0.562.0.dist-info/METADATA,sha256=3Zyfs3T84Cpzo8KD2YB84MT4imnwHt0QHi8N8Ypb3JI,4801
441
+ orionis-0.562.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
442
+ orionis-0.562.0.dist-info/top_level.txt,sha256=lyXi6jArpqJ-0zzNqd_uwsH-z9TCEBVBL-pC3Ekv7hU,8
443
+ orionis-0.562.0.dist-info/RECORD,,