Kea2-python 0.2.4__py3-none-any.whl → 0.3.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.

Potentially problematic release.


This version of Kea2-python might be problematic. Click here for more details.

@@ -0,0 +1,2547 @@
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>Kea2 Merged Test Report</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
9
+ <style>
10
+ :root {
11
+ --primary-color: #3498db;
12
+ --secondary-color: #2ecc71;
13
+ --warning-color: #f39c12;
14
+ --danger-color: #e74c3c;
15
+ --dark-color: #2c3e50;
16
+ --light-color: #ecf0f1;
17
+ }
18
+
19
+ body {
20
+ font-family: 'Segoe UI', Roboto, -apple-system, sans-serif;
21
+ background-color: #f8f9fa;
22
+ color: #333;
23
+ line-height: 1.6;
24
+ }
25
+
26
+ /* Custom container width - wider than Bootstrap default */
27
+ .container {
28
+ max-width: 98% !important;
29
+ width: 98% !important;
30
+ }
31
+
32
+ @media (min-width: 1200px) {
33
+ .container {
34
+ max-width: 1800px !important;
35
+ width: 95% !important;
36
+ }
37
+ }
38
+
39
+ @media (min-width: 1400px) {
40
+ .container {
41
+ max-width: 2000px !important;
42
+ width: 92% !important;
43
+ }
44
+ }
45
+
46
+ @media (min-width: 1600px) {
47
+ .container {
48
+ max-width: 2200px !important;
49
+ width: 90% !important;
50
+ }
51
+ }
52
+
53
+ @media (min-width: 1800px) {
54
+ .container {
55
+ max-width: 2400px !important;
56
+ width: 88% !important;
57
+ }
58
+ }
59
+
60
+ @media (min-width: 2000px) {
61
+ .container {
62
+ max-width: 2600px !important;
63
+ width: 85% !important;
64
+ }
65
+ }
66
+
67
+ .header {
68
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
69
+ color: white;
70
+ padding: 2.5rem 0;
71
+ margin-bottom: 3rem;
72
+ border-radius: 0 0 20px 20px;
73
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
74
+ }
75
+
76
+ .stats-card {
77
+ border-radius: 12px;
78
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
79
+ transition: transform 0.3s, box-shadow 0.3s;
80
+ height: 100%;
81
+ overflow: hidden;
82
+ padding: 1rem;
83
+ }
84
+
85
+ .stats-card:hover {
86
+ transform: translateY(-5px);
87
+ box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
88
+ }
89
+
90
+ .card-header {
91
+ font-weight: 600;
92
+ padding: 1.25rem 1.5rem;
93
+ }
94
+
95
+ .card-body {
96
+ padding: 1.5rem;
97
+ }
98
+
99
+ .table-custom {
100
+ border-radius: 10px;
101
+ overflow: hidden;
102
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
103
+ width: 100%;
104
+ table-layout: auto;
105
+ }
106
+
107
+ .table-custom thead {
108
+ background-color: #495057;
109
+ color: white;
110
+ }
111
+
112
+ .table-custom th {
113
+ font-weight: 600;
114
+ padding: 15px 12px;
115
+ white-space: nowrap;
116
+ text-align: center;
117
+ }
118
+
119
+ .table-custom td {
120
+ padding: 15px 12px;
121
+ vertical-align: middle;
122
+ text-align: center;
123
+ }
124
+
125
+ /* Specific column widths for property statistics table */
126
+ .table-custom th:nth-child(1), .table-custom td:nth-child(1) { /* Index */
127
+ width: 8%;
128
+ min-width: 55px;
129
+ }
130
+
131
+ .table-custom th:nth-child(2), .table-custom td:nth-child(2) { /* Property Name */
132
+ width: 30%;
133
+ min-width: 200px;
134
+ text-align: left;
135
+ }
136
+
137
+ .table-custom th:nth-child(3), .table-custom td:nth-child(3) { /* Precondition Satisfied */
138
+ width: 15%;
139
+ min-width: 95px;
140
+ }
141
+
142
+ .table-custom th:nth-child(4), .table-custom td:nth-child(4) { /* Executed */
143
+ width: 12%;
144
+ min-width: 75px;
145
+ }
146
+
147
+ .table-custom th:nth-child(5), .table-custom td:nth-child(5) { /* Fails */
148
+ width: 12%;
149
+ min-width: 75px;
150
+ }
151
+
152
+ .table-custom th:nth-child(6), .table-custom td:nth-child(6) { /* Errors */
153
+ width: 12%;
154
+ min-width: 75px;
155
+ }
156
+
157
+ .table-custom tbody tr:nth-of-type(odd) {
158
+ background-color: rgba(0, 0, 0, 0.02);
159
+ }
160
+
161
+ .table-custom tbody tr:hover {
162
+ background-color: rgba(0, 0, 0, 0.05);
163
+ }
164
+
165
+ .stat-value {
166
+ font-size: 2.2rem;
167
+ font-weight: 700;
168
+ display: block;
169
+ margin-bottom: 0.8rem;
170
+ line-height: 1.2;
171
+ }
172
+
173
+ .stat-label {
174
+ font-size: 1rem;
175
+ color: #666;
176
+ display: block;
177
+ margin-top: 5px;
178
+ }
179
+
180
+ .section-title {
181
+ position: relative;
182
+ padding-bottom: 15px;
183
+ margin-bottom: 30px;
184
+ font-weight: 600;
185
+ color: var(--dark-color);
186
+ }
187
+
188
+ .section-title::after {
189
+ content: '';
190
+ position: absolute;
191
+ bottom: 0;
192
+ left: 0;
193
+ height: 3px;
194
+ width: 50px;
195
+ background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
196
+ border-radius: 3px;
197
+ }
198
+
199
+ .section-block {
200
+ margin-bottom: 70px;
201
+ }
202
+
203
+ .value-highlight {
204
+ color: var(--primary-color);
205
+ }
206
+
207
+ .value-danger {
208
+ color: var(--danger-color);
209
+ }
210
+
211
+ .value-warning {
212
+ color: var(--warning-color);
213
+ }
214
+
215
+ .value-success {
216
+ color: var(--secondary-color);
217
+ }
218
+
219
+ .summary-card {
220
+ border-radius: 12px;
221
+ background-color: white;
222
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
223
+ padding: 30px;
224
+ margin-bottom: 40px;
225
+ }
226
+
227
+ .badge-custom {
228
+ padding: 6px 12px;
229
+ border-radius: 50px;
230
+ font-weight: 500;
231
+ font-size: 0.9rem;
232
+ margin: 0 2px;
233
+ }
234
+
235
+
236
+
237
+ .activities-container {
238
+ display: flex;
239
+ flex-direction: column;
240
+ height: 650px;
241
+ }
242
+
243
+ .pagination-container {
244
+ padding: 10px 0;
245
+ background-color: white;
246
+ border-radius: 0 0 8px 8px;
247
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
248
+ }
249
+
250
+ /* Modern Activity Item Styling */
251
+ .activity-item {
252
+ background: #ffffff;
253
+ border: 1px solid #f3f4f6;
254
+ border-radius: 12px;
255
+ padding: 16px 20px;
256
+ margin-bottom: 8px;
257
+ transition: all 0.2s ease;
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: space-between;
261
+ }
262
+
263
+ .activity-item:hover {
264
+ border-color: #e5e7eb;
265
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
266
+ transform: translateY(-1px);
267
+ }
268
+
269
+ .activity-content {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 12px;
273
+ }
274
+
275
+ .activity-name {
276
+ font-weight: 500;
277
+ color: #374151;
278
+ font-size: 14px;
279
+ }
280
+
281
+ .traversal-badge {
282
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8) !important;
283
+ border: none;
284
+ border-radius: 20px;
285
+ padding: 6px 12px;
286
+ font-size: 12px;
287
+ font-weight: 500;
288
+ box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
289
+ }
290
+
291
+ /* Modern Activity List Container */
292
+ .activity-list {
293
+ background: #ffffff;
294
+ border: 1px solid #e5e7eb;
295
+ border-radius: 12px;
296
+ padding: 16px;
297
+ height: 550px;
298
+ overflow-y: auto;
299
+ scrollbar-width: thin;
300
+ scrollbar-color: var(--primary-color) #eee;
301
+ margin-bottom: 15px;
302
+ }
303
+
304
+ .activity-list::-webkit-scrollbar {
305
+ width: 8px;
306
+ }
307
+
308
+ .activity-list::-webkit-scrollbar-track {
309
+ background: #f1f1f1;
310
+ border-radius: 10px;
311
+ }
312
+
313
+ .activity-list::-webkit-scrollbar-thumb {
314
+ background-color: var(--primary-color);
315
+ border-radius: 10px;
316
+ }
317
+
318
+ .activity-list::-webkit-scrollbar-thumb:hover {
319
+ background-color: #2980b9;
320
+ }
321
+
322
+ .nav-tabs .nav-link {
323
+ color: #666;
324
+ border: 1px solid transparent;
325
+ border-radius: 6px 6px 0 0;
326
+ font-weight: 500;
327
+ transition: all 0.3s ease;
328
+ }
329
+
330
+ .nav-tabs .nav-link:hover {
331
+ color: var(--primary-color);
332
+ border-color: rgba(52, 152, 219, 0.2);
333
+ background-color: rgba(52, 152, 219, 0.05);
334
+ }
335
+
336
+ .nav-tabs .nav-link.active {
337
+ color: var(--primary-color);
338
+ background-color: white;
339
+ border-color: #dee2e6 #dee2e6 #fff;
340
+ font-weight: 600;
341
+ }
342
+
343
+ .tab-content {
344
+ border: 1px solid #dee2e6;
345
+ border-top: none;
346
+ border-radius: 0 0 8px 8px;
347
+ padding: 20px;
348
+ background-color: #fafafa;
349
+ }
350
+
351
+ .sorting-controls {
352
+ background-color: #f8f9fa;
353
+ border: 1px solid #e9ecef;
354
+ border-radius: 8px;
355
+ padding: 15px;
356
+ margin-bottom: 20px;
357
+ }
358
+
359
+ .sorting-controls .form-select {
360
+ min-width: 140px;
361
+ }
362
+
363
+ .sorting-controls .btn {
364
+ transition: all 0.3s ease;
365
+ }
366
+
367
+ .sorting-controls .btn:hover {
368
+ transform: translateY(-1px);
369
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
370
+ }
371
+
372
+ .sort-icon {
373
+ margin-left: 8px;
374
+ transition: all 0.3s ease;
375
+ font-size: 1.4rem;
376
+ color: #ffffff !important;
377
+ opacity: 0.6;
378
+ text-shadow: 0 0 3px rgba(0,0,0,0.3);
379
+ }
380
+
381
+ .sort-icon:hover {
382
+ opacity: 1;
383
+ transform: scale(1.2);
384
+ text-shadow: 0 0 5px rgba(0,0,0,0.5);
385
+ }
386
+
387
+ .sort-icon.active {
388
+ opacity: 1;
389
+ font-weight: bold;
390
+ text-shadow: 0 0 5px rgba(0,0,0,0.5);
391
+ }
392
+
393
+ .sort-icon.asc.active {
394
+ color: #40e0d0 !important;
395
+ }
396
+
397
+ .sort-icon.desc.active {
398
+ color: #ff6b6b !important;
399
+ }
400
+
401
+ /* Modern Sorting Controls Styling */
402
+ .sorting-controls-modern {
403
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
404
+ border: 1px solid #e3e6ea;
405
+ border-radius: 12px;
406
+ padding: 20px 24px;
407
+ margin-bottom: 24px;
408
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
409
+ transition: all 0.3s ease;
410
+ position: relative;
411
+ overflow: hidden;
412
+ }
413
+
414
+ .sorting-controls-modern::before {
415
+ content: '';
416
+ position: absolute;
417
+ top: 0;
418
+ left: 0;
419
+ right: 0;
420
+ height: 3px;
421
+ background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
422
+ border-radius: 12px 12px 0 0;
423
+ }
424
+
425
+ .sorting-controls-modern:hover {
426
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
427
+ transform: translateY(-1px);
428
+ }
429
+
430
+ .sort-label-section {
431
+ display: flex;
432
+ align-items: center;
433
+ gap: 16px;
434
+ }
435
+
436
+ .sort-icon-wrapper {
437
+ width: 48px;
438
+ height: 48px;
439
+ background: linear-gradient(135deg, #3498db, #2980b9);
440
+ border-radius: 12px;
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: center;
444
+ box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3);
445
+ }
446
+
447
+ .sort-icon-wrapper i {
448
+ color: white;
449
+ font-size: 20px;
450
+ }
451
+
452
+ .sort-text {
453
+ display: flex;
454
+ flex-direction: column;
455
+ gap: 4px;
456
+ }
457
+
458
+ .sort-title {
459
+ font-size: 16px;
460
+ font-weight: 600;
461
+ color: #2c3e50;
462
+ line-height: 1.2;
463
+ }
464
+
465
+ .sort-subtitle {
466
+ font-size: 13px;
467
+ color: #7f8c8d;
468
+ font-weight: 400;
469
+ line-height: 1.2;
470
+ }
471
+
472
+ .sort-button-section {
473
+ display: flex;
474
+ align-items: center;
475
+ }
476
+
477
+ .btn-sort-modern {
478
+ background: linear-gradient(135deg, #27ae60, #2ecc71);
479
+ border: none;
480
+ border-radius: 10px;
481
+ padding: 12px 20px;
482
+ color: white;
483
+ font-weight: 500;
484
+ font-size: 14px;
485
+ cursor: pointer;
486
+ transition: all 0.3s ease;
487
+ box-shadow: 0 2px 8px rgba(46, 204, 113, 0.3);
488
+ position: relative;
489
+ overflow: hidden;
490
+ }
491
+
492
+ .btn-sort-modern::before {
493
+ content: '';
494
+ position: absolute;
495
+ top: 0;
496
+ left: -100%;
497
+ width: 100%;
498
+ height: 100%;
499
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
500
+ transition: left 0.5s ease;
501
+ }
502
+
503
+ .btn-sort-modern:hover {
504
+ transform: translateY(-2px);
505
+ box-shadow: 0 4px 16px rgba(46, 204, 113, 0.4);
506
+ }
507
+
508
+ .btn-sort-modern:hover::before {
509
+ left: 100%;
510
+ }
511
+
512
+ .btn-sort-modern:active {
513
+ transform: translateY(0px);
514
+ box-shadow: 0 2px 8px rgba(46, 204, 113, 0.3);
515
+ }
516
+
517
+ .btn-content {
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 8px;
521
+ position: relative;
522
+ z-index: 1;
523
+ }
524
+
525
+ .btn-icon {
526
+ font-size: 16px;
527
+ opacity: 0.9;
528
+ }
529
+
530
+ .btn-text {
531
+ font-size: 14px;
532
+ font-weight: 500;
533
+ white-space: nowrap;
534
+ }
535
+
536
+ .btn-arrow {
537
+ font-size: 14px;
538
+ transition: transform 0.3s ease;
539
+ opacity: 0.8;
540
+ }
541
+
542
+ .btn-sort-modern:hover .btn-arrow {
543
+ transform: scale(1.1);
544
+ opacity: 1;
545
+ }
546
+
547
+ /* Search Controls Styling */
548
+ .search-controls-modern {
549
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
550
+ border: 1px solid #e3e6ea;
551
+ border-radius: 12px;
552
+ padding: 20px 24px;
553
+ margin-bottom: 24px;
554
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
555
+ transition: all 0.3s ease;
556
+ position: relative;
557
+ overflow: hidden;
558
+ }
559
+
560
+ .search-controls-modern::before {
561
+ content: '';
562
+ position: absolute;
563
+ top: 0;
564
+ left: 0;
565
+ right: 0;
566
+ height: 3px;
567
+ background: linear-gradient(90deg, #17a2b8, #20c997);
568
+ border-radius: 12px 12px 0 0;
569
+ }
570
+
571
+ .search-controls-modern:hover {
572
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
573
+ transform: translateY(-1px);
574
+ }
575
+
576
+ .search-icon-wrapper {
577
+ width: 48px;
578
+ height: 48px;
579
+ background: linear-gradient(135deg, #17a2b8, #138496);
580
+ border-radius: 12px;
581
+ display: flex;
582
+ align-items: center;
583
+ justify-content: center;
584
+ box-shadow: 0 2px 8px rgba(23, 162, 184, 0.3);
585
+ }
586
+
587
+ .search-icon-wrapper i {
588
+ color: white;
589
+ font-size: 20px;
590
+ }
591
+
592
+ /* Combined Search and Sort Controls */
593
+ .search-sort-controls-modern {
594
+ background: #ffffff;
595
+ border: 1px solid #e5e7eb;
596
+ border-radius: 16px;
597
+ padding: 20px 24px;
598
+ margin-bottom: 20px;
599
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
600
+ transition: all 0.2s ease;
601
+ position: relative;
602
+ }
603
+
604
+ .search-sort-controls-modern:hover {
605
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.06);
606
+ border-color: #d1d5db;
607
+ }
608
+
609
+ .search-section {
610
+ min-width: 0; /* Allow flex item to shrink */
611
+ }
612
+
613
+ .sort-section {
614
+ flex-shrink: 0; /* Prevent sort section from shrinking */
615
+ }
616
+
617
+ .sort-label {
618
+ white-space: nowrap;
619
+ font-size: 14px;
620
+ }
621
+
622
+ /* Responsive design for combined controls */
623
+ @media (max-width: 768px) {
624
+ .search-sort-controls-modern .d-flex {
625
+ flex-direction: column;
626
+ gap: 16px !important;
627
+ }
628
+
629
+ .search-section {
630
+ width: 100% !important;
631
+ max-width: none !important;
632
+ flex-shrink: 1 !important;
633
+ }
634
+
635
+ .sort-section {
636
+ justify-content: center;
637
+ width: 100%;
638
+ margin-left: 0 !important;
639
+ }
640
+ }
641
+
642
+ @media (max-width: 576px) {
643
+ .search-section {
644
+ max-width: none !important;
645
+ }
646
+
647
+ .search-icon-wrapper {
648
+ width: 40px;
649
+ height: 40px;
650
+ }
651
+
652
+ .search-icon-wrapper i {
653
+ font-size: 18px;
654
+ }
655
+ }
656
+
657
+ /* Modern Search Input */
658
+ .activity-search-input {
659
+ border: 1px solid #e5e7eb;
660
+ border-radius: 12px;
661
+ padding: 12px 16px;
662
+ font-size: 14px;
663
+ transition: all 0.2s ease;
664
+ background: #f9fafb;
665
+ }
666
+
667
+ .activity-search-input:focus {
668
+ border-color: #3b82f6;
669
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
670
+ background: #ffffff;
671
+ outline: none;
672
+ }
673
+
674
+ .search-btn {
675
+ border-radius: 12px;
676
+ padding: 12px 16px;
677
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8);
678
+ border: none;
679
+ color: white;
680
+ transition: all 0.2s ease;
681
+ }
682
+
683
+ .search-btn:hover {
684
+ background: linear-gradient(135deg, #2563eb, #1e40af);
685
+ transform: translateY(-1px);
686
+ box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
687
+ color: white;
688
+ }
689
+
690
+ .search-btn:focus {
691
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
692
+ color: white;
693
+ }
694
+
695
+ .search-clear-btn {
696
+ border-radius: 0 8px 8px 0;
697
+ border-left: none;
698
+ padding: 12px 16px;
699
+ transition: all 0.3s ease;
700
+ }
701
+
702
+ .search-clear-btn:hover {
703
+ background-color: #dc3545;
704
+ border-color: #dc3545;
705
+ color: white;
706
+ }
707
+
708
+ .search-results-count {
709
+ display: block;
710
+ margin-top: 8px;
711
+ font-size: 12px;
712
+ font-style: italic;
713
+ }
714
+
715
+ @media (max-width: 768px) {
716
+ .container {
717
+ max-width: 98% !important;
718
+ width: 98% !important;
719
+ padding-left: 10px !important;
720
+ padding-right: 10px !important;
721
+ }
722
+
723
+ .stat-value {
724
+ font-size: 1.5rem;
725
+ }
726
+
727
+ .table-custom {
728
+ font-size: 0.9rem;
729
+ }
730
+
731
+ .table-custom th, .table-custom td {
732
+ padding: 10px 6px;
733
+ }
734
+
735
+ .badge-custom {
736
+ font-size: 0.8rem;
737
+ padding: 4px 8px;
738
+ }
739
+ }
740
+
741
+ @media (max-width: 576px) {
742
+ .container {
743
+ max-width: 100% !important;
744
+ width: 100% !important;
745
+ padding-left: 5px !important;
746
+ padding-right: 5px !important;
747
+ }
748
+
749
+ .table-custom {
750
+ font-size: 0.8rem;
751
+ }
752
+
753
+ .table-custom th, .table-custom td {
754
+ padding: 8px 4px;
755
+ white-space: normal;
756
+ }
757
+ }
758
+
759
+ /* Merge Information Styles */
760
+ .merge-info-section {
761
+ margin-bottom: 1.5rem;
762
+ }
763
+
764
+ .merge-info-card {
765
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
766
+ border: 1px solid #e3f2fd;
767
+ border-radius: 12px;
768
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
769
+ overflow: hidden;
770
+ transition: all 0.3s ease;
771
+ }
772
+
773
+ .merge-info-card:hover {
774
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
775
+ transform: translateY(-1px);
776
+ }
777
+
778
+ .merge-info-header {
779
+ padding: 16px 20px;
780
+ background: linear-gradient(135deg, #e3f2fd 0%, #f0f8ff 100%);
781
+ border-bottom: 1px solid rgba(23, 162, 184, 0.2);
782
+ cursor: pointer;
783
+ transition: all 0.3s ease;
784
+ user-select: none;
785
+ }
786
+
787
+ .merge-info-header:hover {
788
+ background: linear-gradient(135deg, #d1ecf1 0%, #e8f4f8 100%);
789
+ }
790
+
791
+ .merge-icon-wrapper {
792
+ width: 40px;
793
+ height: 40px;
794
+ background: linear-gradient(135deg, #17a2b8, #138496);
795
+ border-radius: 10px;
796
+ display: flex;
797
+ align-items: center;
798
+ justify-content: center;
799
+ box-shadow: 0 2px 8px rgba(23, 162, 184, 0.3);
800
+ }
801
+
802
+ .merge-icon-wrapper i {
803
+ color: white;
804
+ font-size: 18px;
805
+ }
806
+
807
+ .merge-title {
808
+ font-size: 16px;
809
+ font-weight: 600;
810
+ color: #2c3e50;
811
+ margin-bottom: 2px;
812
+ }
813
+
814
+ .merge-subtitle {
815
+ font-size: 13px;
816
+ color: #6c757d;
817
+ }
818
+
819
+ .merge-count-badge {
820
+ font-size: 13px;
821
+ font-weight: 500;
822
+ padding: 6px 12px;
823
+ border-radius: 20px;
824
+ background: linear-gradient(135deg, #007bff, #0056b3) !important;
825
+ box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3);
826
+ }
827
+
828
+ .collapse-icon {
829
+ font-size: 16px;
830
+ color: #6c757d;
831
+ transition: transform 0.3s ease;
832
+ }
833
+
834
+ .merge-info-header[aria-expanded="true"] .collapse-icon {
835
+ transform: rotate(180deg);
836
+ }
837
+
838
+ .merge-info-body {
839
+ padding: 20px;
840
+ background: #ffffff;
841
+ }
842
+
843
+ .merge-directories-header h6 {
844
+ color: #495057;
845
+ font-weight: 600;
846
+ font-size: 15px;
847
+ }
848
+
849
+ .merged-directories-grid {
850
+ display: grid;
851
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
852
+ gap: 12px;
853
+ max-height: 300px;
854
+ overflow-y: auto;
855
+ padding: 4px;
856
+ }
857
+
858
+ .directory-item-card {
859
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
860
+ border: 1px solid #e9ecef;
861
+ border-radius: 8px;
862
+ padding: 14px;
863
+ transition: all 0.2s ease;
864
+ cursor: default;
865
+ min-height: 70px;
866
+ }
867
+
868
+ .directory-item-card:hover {
869
+ background: linear-gradient(135deg, #e3f2fd 0%, #f0f8ff 100%);
870
+ border-color: #17a2b8;
871
+ transform: translateY(-2px);
872
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
873
+ }
874
+
875
+ .directory-item-content {
876
+ display: flex;
877
+ align-items: center;
878
+ }
879
+
880
+ .directory-icon {
881
+ width: 36px;
882
+ height: 36px;
883
+ background: linear-gradient(135deg, #ffc107, #e0a800);
884
+ border-radius: 8px;
885
+ display: flex;
886
+ align-items: center;
887
+ justify-content: center;
888
+ margin-right: 14px;
889
+ flex-shrink: 0;
890
+ }
891
+
892
+ .directory-icon i {
893
+ color: white;
894
+ font-size: 16px;
895
+ }
896
+
897
+ .directory-info {
898
+ flex: 1;
899
+ min-width: 0;
900
+ }
901
+
902
+ .directory-name {
903
+ font-family: 'Courier New', monospace;
904
+ font-size: 15px;
905
+ font-weight: 600;
906
+ color: #2c3e50;
907
+ margin-bottom: 3px;
908
+ word-break: break-all;
909
+ line-height: 1.3;
910
+ }
911
+
912
+ .directory-index {
913
+ font-size: 13px;
914
+ color: #6c757d;
915
+ font-weight: 500;
916
+ }
917
+
918
+ .merged-directories-grid::-webkit-scrollbar {
919
+ width: 6px;
920
+ }
921
+
922
+ .merged-directories-grid::-webkit-scrollbar-track {
923
+ background: #f1f1f1;
924
+ border-radius: 3px;
925
+ }
926
+
927
+ .merged-directories-grid::-webkit-scrollbar-thumb {
928
+ background: #c1c1c1;
929
+ border-radius: 3px;
930
+ }
931
+
932
+ .merged-directories-grid::-webkit-scrollbar-thumb:hover {
933
+ background: #a8a8a8;
934
+ }
935
+
936
+ /* Animation for collapse */
937
+ .collapse {
938
+ transition: height 0.35s ease;
939
+ }
940
+
941
+ /* Responsive adjustments */
942
+ @media (max-width: 768px) {
943
+ .merged-directories-grid {
944
+ grid-template-columns: 1fr;
945
+ }
946
+
947
+ .merge-info-header {
948
+ padding: 12px 16px;
949
+ }
950
+
951
+ .merge-info-body {
952
+ padding: 16px;
953
+ }
954
+
955
+ .merge-icon-wrapper {
956
+ width: 36px;
957
+ height: 36px;
958
+ }
959
+
960
+ .merge-title {
961
+ font-size: 14px;
962
+ }
963
+
964
+ .merge-subtitle {
965
+ font-size: 12px;
966
+ }
967
+
968
+ .directory-name {
969
+ font-size: 14px;
970
+ }
971
+
972
+ .directory-index {
973
+ font-size: 12px;
974
+ }
975
+
976
+ .directory-icon {
977
+ width: 32px;
978
+ height: 32px;
979
+ }
980
+
981
+ .directory-icon i {
982
+ font-size: 14px;
983
+ }
984
+ }
985
+ </style>
986
+ </head>
987
+
988
+ <body>
989
+ <!-- Header -->
990
+ <header class="header text-center">
991
+ <div class="container">
992
+ <h1><i class="bi bi-bug"></i> Kea2 Merged Test Report</h1>
993
+ <p class="lead">Test Time: {{ timestamp }}</p>
994
+ </div>
995
+ </header>
996
+
997
+ <div class="container mb-5">
998
+ <!-- Test Summary -->
999
+ <div class="row mb-4">
1000
+ <div class="col-12">
1001
+ <div class="summary-card">
1002
+ <h2 class="section-title">Test Summary</h2>
1003
+
1004
+ <!-- Merge Information Section -->
1005
+ {% if merge_info %}
1006
+ <div class="merge-info-section mb-4">
1007
+ <div class="merge-info-card">
1008
+ <!-- Collapsible Header -->
1009
+ <div class="merge-info-header" data-bs-toggle="collapse" data-bs-target="#mergeInfoCollapse" aria-expanded="false" aria-controls="mergeInfoCollapse">
1010
+ <div class="d-flex align-items-center justify-content-between">
1011
+ <div class="d-flex align-items-center">
1012
+ <div class="merge-icon-wrapper me-3">
1013
+ <i class="bi bi-layers"></i>
1014
+ </div>
1015
+ <div>
1016
+ <h6 class="mb-0 merge-title">Merged Report</h6>
1017
+ <small class="text-muted merge-subtitle">
1018
+ <i class="bi bi-clock me-1"></i>
1019
+ Generated at {{ merge_info.merge_timestamp }}
1020
+ </small>
1021
+ </div>
1022
+ </div>
1023
+ <div class="d-flex align-items-center">
1024
+ <span class="badge bg-primary me-3 merge-count-badge">
1025
+ <i class="bi bi-folder2-open me-1"></i>
1026
+ {{ merge_info.source_count }} directories
1027
+ </span>
1028
+ <i class="bi bi-chevron-down collapse-icon"></i>
1029
+ </div>
1030
+ </div>
1031
+ </div>
1032
+
1033
+ <!-- Collapsible Content -->
1034
+ <div class="collapse" id="mergeInfoCollapse">
1035
+ <div class="merge-info-body">
1036
+ <div class="merge-directories-header">
1037
+ <h6 class="mb-3">
1038
+ <i class="bi bi-list-ul me-2"></i>
1039
+ Merged Directories
1040
+ </h6>
1041
+ </div>
1042
+ <div class="merged-directories-grid">
1043
+ {% for dir_name in merge_info.source_directories %}
1044
+ <div class="directory-item-card">
1045
+ <div class="directory-item-content">
1046
+ <div class="directory-icon">
1047
+ <i class="bi bi-folder-fill"></i>
1048
+ </div>
1049
+ <div class="directory-info">
1050
+ <div class="directory-name">{{ dir_name }}</div>
1051
+ <div class="directory-index">Directory {{ loop.index }}</div>
1052
+ </div>
1053
+ </div>
1054
+ </div>
1055
+ {% endfor %}
1056
+ </div>
1057
+ </div>
1058
+ </div>
1059
+ </div>
1060
+ </div>
1061
+ {% endif %}
1062
+
1063
+ <!-- Statistics Cards -->
1064
+ <div class="row g-4">
1065
+ <div class="col-lg-2 col-md-4 col-sm-6">
1066
+ <div class="text-center">
1067
+ <i class="bi bi-bug text-danger" style="font-size: 2rem;"></i>
1068
+ <span class="stat-value value-danger">{{ bugs_found }}</span>
1069
+ <span class="stat-label">Total Bugs</span>
1070
+ </div>
1071
+ </div>
1072
+ <div class="col-lg-2 col-md-4 col-sm-6">
1073
+ <div class="text-center">
1074
+ <i class="bi bi-x-circle text-danger" style="font-size: 2rem;"></i>
1075
+ <span class="stat-value value-danger">{{ total_crash_count|default(0) }}</span>
1076
+ <span class="stat-label">Crashes</span>
1077
+ </div>
1078
+ </div>
1079
+ <div class="col-lg-2 col-md-4 col-sm-6">
1080
+ <div class="text-center">
1081
+ <i class="bi bi-clock text-warning" style="font-size: 2rem;"></i>
1082
+ <span class="stat-value value-warning">{{ total_anr_count|default(0) }}</span>
1083
+ <span class="stat-label">ANRs</span>
1084
+ </div>
1085
+ </div>
1086
+ <div class="col-lg-2 col-md-4 col-sm-6">
1087
+ <div class="text-center">
1088
+ <i class="bi bi-pie-chart text-info" style="font-size: 2rem;"></i>
1089
+ <span class="stat-value value-highlight">{{ "%.2f"|format(coverage_percent) }}%</span>
1090
+ <span class="stat-label">Coverage</span>
1091
+ </div>
1092
+ </div>
1093
+ <div class="col-lg-2 col-md-4 col-sm-6">
1094
+ <div class="text-center">
1095
+ <i class="bi bi-list-check text-primary" style="font-size: 2rem;"></i>
1096
+ <span class="stat-value value-highlight">{{ all_properties_count }}</span>
1097
+ <span class="stat-label">All Properties</span>
1098
+ </div>
1099
+ </div>
1100
+ <div class="col-lg-2 col-md-4 col-sm-6">
1101
+ <div class="text-center">
1102
+ <i class="bi bi-check-square text-success" style="font-size: 2rem;"></i>
1103
+ <span class="stat-value value-success">{{ executed_properties_count }}</span>
1104
+ <span class="stat-label">Executed</span>
1105
+ </div>
1106
+ </div>
1107
+ </div>
1108
+ </div>
1109
+ </div>
1110
+ </div>
1111
+
1112
+ <!-- Tested Activities List -->
1113
+ <div class="section-block">
1114
+ <h2 class="section-title">Activities Coverage</h2>
1115
+
1116
+ <div class="card">
1117
+ <div class="card-header bg-primary text-white">
1118
+ <div class="d-flex justify-content-between align-items-center">
1119
+ <span><i class="bi bi-app"></i> Activities Coverage Overview</span>
1120
+ <span class="badge bg-light text-dark" style="font-size: 1.1em; font-weight: 600;">Coverage: {{ "%.2f"|format(coverage_percent) }}%</span>
1121
+ </div>
1122
+ </div>
1123
+ <div class="card-body">
1124
+ <div class="alert alert-info mb-3" style="border-left: 4px solid #17a2b8; background-color: #f8f9fa;">
1125
+ <small class="text-muted">
1126
+ <i class="bi bi-info-circle me-1"></i>
1127
+ <strong>Visit Count Explanation:</strong>
1128
+ The number after the <i class="bi bi-eye"></i> icon indicates how many times each Activity was visited during testing.
1129
+ </small>
1130
+ </div>
1131
+
1132
+ <!-- Navigation Tabs -->
1133
+ <ul class="nav nav-tabs mb-3" id="activitiesTabs" role="tablist">
1134
+ <li class="nav-item" role="presentation">
1135
+ <button class="nav-link active" id="tested-tab" data-bs-toggle="tab"
1136
+ data-bs-target="#tested-activities" type="button" role="tab"
1137
+ aria-controls="tested-activities" aria-selected="true">
1138
+ <i class="bi bi-check-circle"></i> Tested Activities ({{ tested_activities|length }})
1139
+ </button>
1140
+ </li>
1141
+ <li class="nav-item" role="presentation">
1142
+ <button class="nav-link" id="all-tab" data-bs-toggle="tab"
1143
+ data-bs-target="#all-activities" type="button" role="tab"
1144
+ aria-controls="all-activities" aria-selected="false">
1145
+ <i class="bi bi-app"></i> All Activities ({{ total_activities|length }})
1146
+ </button>
1147
+ </li>
1148
+ </ul>
1149
+
1150
+ <!-- Tab Content -->
1151
+ <div class="tab-content" id="activitiesTabContent">
1152
+ <!-- Tested Activities Tab -->
1153
+ <div class="tab-pane fade show active" id="tested-activities" role="tabpanel"
1154
+ aria-labelledby="tested-tab">
1155
+ <div class="d-flex justify-content-between align-items-center mb-3">
1156
+ <h5 class="mb-0 text-success">
1157
+ <i class="bi bi-check-circle-fill"></i> Tested Activities
1158
+ </h5>
1159
+ <span class="badge bg-success" style="font-size: 1.0em; font-weight: 500;">{{ tested_activities|length }} / {{ total_activities_count }}</span>
1160
+ </div>
1161
+
1162
+ <!-- Combined Search and Sort Controls for Tested Activities -->
1163
+ <div class="search-sort-controls-modern mb-4">
1164
+ <div class="d-flex align-items-center gap-4">
1165
+ <!-- Search Section -->
1166
+ <div class="search-section d-flex align-items-center" style="width: 600px; flex-shrink: 0;">
1167
+ <div class="search-icon-wrapper me-3">
1168
+ <i class="bi bi-search"></i>
1169
+ </div>
1170
+ <div class="flex-grow-1">
1171
+ <div class="input-group">
1172
+ <input type="text" class="form-control activity-search-input"
1173
+ id="tested-activity-search"
1174
+ placeholder="Search activities..."
1175
+ data-target="tested-activities-container"
1176
+ data-item-class="tested-activity"
1177
+ data-pagination="tested-pagination"
1178
+ data-page-size="tested-page-size">
1179
+ <button class="btn search-btn" type="button"
1180
+ data-target="tested-activity-search">
1181
+ <i class="bi bi-search"></i>
1182
+ </button>
1183
+ <button class="btn btn-outline-secondary search-clear-btn" type="button"
1184
+ data-target="tested-activity-search">
1185
+ <i class="bi bi-x-lg"></i>
1186
+ </button>
1187
+ </div>
1188
+ <small class="text-muted search-results-count" id="tested-search-results"></small>
1189
+ </div>
1190
+ </div>
1191
+
1192
+ <!-- Sort Section -->
1193
+ <div class="sort-section d-flex align-items-center gap-3" style="margin-left: auto;">
1194
+ <div class="sort-label d-flex align-items-center gap-2">
1195
+ <i class="bi bi-funnel-fill text-muted"></i>
1196
+ <span class="text-muted fw-medium">Sort:</span>
1197
+ </div>
1198
+ <button type="button" class="btn-sort-modern activity-sort-btn" data-sort="traversal" data-order="desc">
1199
+ <div class="btn-content">
1200
+ <i class="bi bi-eye btn-icon"></i>
1201
+ <span class="btn-text">Visit Count</span>
1202
+ <i class="bi bi-arrow-down sort-icon btn-arrow"></i>
1203
+ </div>
1204
+ </button>
1205
+ </div>
1206
+ </div>
1207
+ </div>
1208
+
1209
+ <div class="activities-container">
1210
+ <div class="activity-list">
1211
+ {% if tested_activities|length > 0 %}
1212
+ <div id="tested-activities-container">
1213
+ {% for activity in tested_activities %}
1214
+ <div class="activity-item tested-activity" data-page="1">
1215
+ <div class="activity-content">
1216
+ <i class="bi bi-check-circle-fill text-success me-2"></i>
1217
+ <span class="activity-name">{{ activity }}</span>
1218
+ </div>
1219
+ {% if activity in activity_count_history %}
1220
+ <span class="badge bg-info text-white traversal-badge">
1221
+ <i class="bi bi-eye"></i> {{ activity_count_history[activity] }} times
1222
+ </span>
1223
+ {% endif %}
1224
+ </div>
1225
+ {% endfor %}
1226
+ </div>
1227
+ {% else %}
1228
+ <div class="alert alert-warning">
1229
+ No tested activities detected
1230
+ </div>
1231
+ {% endif %}
1232
+ </div>
1233
+ <!-- Pagination for Tested Activities -->
1234
+ <div class="pagination-container d-flex justify-content-between align-items-center">
1235
+ <div class="d-flex align-items-center">
1236
+ <label for="tested-page-size" class="form-label me-2 mb-0">Show:</label>
1237
+ <select class="form-select form-select-sm" id="tested-page-size" style="width: auto;">
1238
+ <option value="5">5</option>
1239
+ <option value="10" selected>10</option>
1240
+ <option value="20">20</option>
1241
+ <option value="50">50</option>
1242
+ <option value="100">100</option>
1243
+ </select>
1244
+ </div>
1245
+ <nav aria-label="Tested Activities Pagination">
1246
+ <ul class="pagination pagination-sm mb-0" id="tested-pagination">
1247
+ <!-- Pagination will be generated by JavaScript -->
1248
+ </ul>
1249
+ </nav>
1250
+ </div>
1251
+ </div>
1252
+ </div>
1253
+
1254
+ <!-- All Activities Tab -->
1255
+ <div class="tab-pane fade" id="all-activities" role="tabpanel"
1256
+ aria-labelledby="all-tab">
1257
+ <div class="d-flex justify-content-between align-items-center mb-3">
1258
+ <h5 class="mb-0 text-primary">
1259
+ <i class="bi bi-app"></i> All Activities Overview
1260
+ </h5>
1261
+ <span class="badge bg-primary" style="font-size: 1.0em; font-weight: 500;">Total: {{ total_activities|length }}</span>
1262
+ </div>
1263
+
1264
+ <!-- Combined Search and Sort Controls for All Activities -->
1265
+ <div class="search-sort-controls-modern mb-4">
1266
+ <div class="d-flex align-items-center gap-4">
1267
+ <!-- Search Section -->
1268
+ <div class="search-section d-flex align-items-center" style="width: 600px; flex-shrink: 0;">
1269
+ <div class="search-icon-wrapper me-3">
1270
+ <i class="bi bi-search"></i>
1271
+ </div>
1272
+ <div class="flex-grow-1">
1273
+ <div class="input-group">
1274
+ <input type="text" class="form-control activity-search-input"
1275
+ id="all-activity-search"
1276
+ placeholder="Search activities..."
1277
+ data-target="all-activities-container"
1278
+ data-item-class="all-activity"
1279
+ data-pagination="all-pagination"
1280
+ data-page-size="all-page-size">
1281
+ <button class="btn search-btn" type="button"
1282
+ data-target="all-activity-search">
1283
+ <i class="bi bi-search"></i>
1284
+ </button>
1285
+ <button class="btn btn-outline-secondary search-clear-btn" type="button"
1286
+ data-target="all-activity-search">
1287
+ <i class="bi bi-x-lg"></i>
1288
+ </button>
1289
+ </div>
1290
+ <small class="text-muted search-results-count" id="all-search-results"></small>
1291
+ </div>
1292
+ </div>
1293
+
1294
+ <!-- Sort Section -->
1295
+ <div class="sort-section d-flex align-items-center gap-3" style="margin-left: auto;">
1296
+ <div class="sort-label d-flex align-items-center gap-2">
1297
+ <i class="bi bi-funnel-fill text-muted"></i>
1298
+ <span class="text-muted fw-medium">Sort:</span>
1299
+ </div>
1300
+ <button type="button" class="btn-sort-modern activity-sort-btn" data-sort="traversal" data-order="desc">
1301
+ <div class="btn-content">
1302
+ <i class="bi bi-eye btn-icon"></i>
1303
+ <span class="btn-text">Visit Count</span>
1304
+ <i class="bi bi-arrow-down sort-icon btn-arrow"></i>
1305
+ </div>
1306
+ </button>
1307
+ </div>
1308
+ </div>
1309
+ </div>
1310
+
1311
+ <div class="activities-container">
1312
+ <div class="activity-list">
1313
+ {% if total_activities|length > 0 %}
1314
+ <div id="all-activities-container">
1315
+ {% for activity in total_activities %}
1316
+ <div class="activity-item all-activity" data-page="1">
1317
+ <div class="activity-content">
1318
+ {% if activity in tested_activities %}
1319
+ <i class="bi bi-check-circle-fill text-success me-2"></i>
1320
+ {% else %}
1321
+ <i class="bi bi-dash-circle text-secondary me-2"></i>
1322
+ {% endif %}
1323
+ <span class="activity-name">{{ activity }}</span>
1324
+ </div>
1325
+ {% if activity in activity_count_history %}
1326
+ <span class="badge bg-info text-white traversal-badge">
1327
+ <i class="bi bi-eye"></i> {{ activity_count_history[activity] }} times
1328
+ </span>
1329
+ {% endif %}
1330
+ </div>
1331
+ {% endfor %}
1332
+ </div>
1333
+ {% else %}
1334
+ <div class="alert alert-warning">
1335
+ No activities information available
1336
+ </div>
1337
+ {% endif %}
1338
+ </div>
1339
+ <!-- Pagination for All Activities -->
1340
+ <div class="pagination-container d-flex justify-content-between align-items-center">
1341
+ <div class="d-flex align-items-center">
1342
+ <label for="all-page-size" class="form-label me-2 mb-0">Show:</label>
1343
+ <select class="form-select form-select-sm" id="all-page-size" style="width: auto;">
1344
+ <option value="5">5</option>
1345
+ <option value="10" selected>10</option>
1346
+ <option value="20">20</option>
1347
+ <option value="50">50</option>
1348
+ <option value="100">100</option>
1349
+ </select>
1350
+ </div>
1351
+ <nav aria-label="All Activities Pagination">
1352
+ <ul class="pagination pagination-sm mb-0" id="all-pagination">
1353
+ <!-- Pagination will be generated by JavaScript -->
1354
+ </ul>
1355
+ </nav>
1356
+ </div>
1357
+ </div>
1358
+ </div>
1359
+ </div>
1360
+ </div>
1361
+ </div>
1362
+ </div>
1363
+
1364
+ <!-- Crash Analysis Section -->
1365
+ {% if crash_events or anr_events %}
1366
+ <div class="section-block">
1367
+ <h2 class="section-title">
1368
+ <i class="bi bi-exclamation-triangle text-danger"></i> Crash Analysis
1369
+ </h2>
1370
+
1371
+ <!-- Detailed Crash Information -->
1372
+ <div class="card">
1373
+ <div class="card-header bg-danger text-white">
1374
+ <div class="d-flex justify-content-between align-items-center">
1375
+ <span><i class="bi bi-bug"></i> Crash & ANR Events</span>
1376
+ <span class="badge bg-light text-dark" style="font-size: 1.1em; font-weight: 600;">{{ (crash_events|length) + (anr_events|length) }} events</span>
1377
+ </div>
1378
+ </div>
1379
+ <div class="card-body">
1380
+ <!-- Event Filter -->
1381
+ <div class="mb-3">
1382
+ <div class="btn-group" role="group" aria-label="Event filter">
1383
+ <input type="radio" class="btn-check" name="event-filter" id="all-events" autocomplete="off" checked>
1384
+ <label class="btn btn-outline-primary" for="all-events">All Events ({{ (crash_events|length) + (anr_events|length) }})</label>
1385
+
1386
+ <input type="radio" class="btn-check" name="event-filter" id="crashes-only" autocomplete="off">
1387
+ <label class="btn btn-outline-danger" for="crashes-only">Crashes Only ({{ crash_events|length }})</label>
1388
+
1389
+ <input type="radio" class="btn-check" name="event-filter" id="anr-only" autocomplete="off">
1390
+ <label class="btn btn-outline-warning" for="anr-only">ANR Only ({{ anr_events|length }})</label>
1391
+ </div>
1392
+ </div>
1393
+
1394
+
1395
+ <!-- Events Table -->
1396
+ <div class="table-responsive">
1397
+ <table class="table table-custom">
1398
+ <thead>
1399
+ <tr>
1400
+ <th>Type</th>
1401
+ <th>Time</th>
1402
+ <th>Exception</th>
1403
+ <th>Process</th>
1404
+ <th>Details</th>
1405
+ </tr>
1406
+ </thead>
1407
+ <tbody id="crash-events-container">
1408
+ {% for crash in crash_events %}
1409
+ <tr class="event-row" data-type="crash" data-page="1">
1410
+ <td><span class="badge bg-danger">CRASH</span></td>
1411
+ <td>{{ crash.time }}</td>
1412
+ <td>{{ crash.exception_type }}</td>
1413
+ <td>{{ crash.process }}</td>
1414
+ <td>
1415
+ <button class="btn btn-sm btn-outline-primary" type="button"
1416
+ data-bs-toggle="collapse" data-bs-target="#crash-detail-{{ loop.index }}"
1417
+ aria-expanded="false" aria-controls="crash-detail-{{ loop.index }}">
1418
+ <i class="bi bi-eye"></i> Details
1419
+ </button>
1420
+ <button class="btn btn-sm btn-outline-secondary copy-stack-btn"
1421
+ data-stack-index="{{ loop.index }}">
1422
+ <i class="bi bi-clipboard"></i> Copy
1423
+ </button>
1424
+ </td>
1425
+ </tr>
1426
+ <tr class="collapse" id="crash-detail-{{ loop.index }}">
1427
+ <td colspan="5">
1428
+ <div class="bg-light p-3 rounded">
1429
+ <h6 class="text-danger">Stack Trace:</h6>
1430
+ <pre class="text-danger mb-0 text-start" id="stack-trace-{{ loop.index }}" style="font-size: 0.9em; white-space: pre-wrap; text-align: left;">{{ crash.stack_trace }}</pre>
1431
+ </div>
1432
+ </td>
1433
+ </tr>
1434
+ {% endfor %}
1435
+
1436
+
1437
+ {% for anr in anr_events %}
1438
+ <tr class="event-row" data-type="anr" data-page="1">
1439
+ <td><span class="badge bg-warning text-dark">ANR</span></td>
1440
+ <td>{{ anr.time }}</td>
1441
+ <td>{{ anr.reason }}</td>
1442
+ <td>{{ anr.process }}</td>
1443
+ <td>
1444
+ <button class="btn btn-sm btn-outline-primary" type="button"
1445
+ data-bs-toggle="collapse" data-bs-target="#anr-detail-{{ loop.index }}"
1446
+ aria-expanded="false" aria-controls="anr-detail-{{ loop.index }}">
1447
+ <i class="bi bi-eye"></i> Details
1448
+ </button>
1449
+ <button class="btn btn-sm btn-outline-secondary copy-stack-btn"
1450
+ data-stack-index="anr-{{ loop.index }}">
1451
+ <i class="bi bi-clipboard"></i> Copy
1452
+ </button>
1453
+ </td>
1454
+ </tr>
1455
+ <tr class="collapse" id="anr-detail-{{ loop.index }}">
1456
+ <td colspan="5">
1457
+ <div class="bg-light p-3 rounded">
1458
+ <h6 class="text-dark">ANR Details:</h6>
1459
+ <pre class="text-dark mb-0 text-start" id="stack-trace-anr-{{ loop.index }}" style="font-size: 0.9em; white-space: pre-wrap; text-align: left;">{{ anr.trace }}</pre>
1460
+ </div>
1461
+ </td>
1462
+ </tr>
1463
+ {% endfor %}
1464
+ </tbody>
1465
+ </table>
1466
+ </div>
1467
+
1468
+ <!-- Pagination for Crash Events -->
1469
+ <div class="pagination-container d-flex justify-content-between align-items-center mt-3">
1470
+ <div class="d-flex align-items-center">
1471
+ <label for="events-page-size" class="form-label me-2 mb-0">Show:</label>
1472
+ <select class="form-select form-select-sm" id="events-page-size" style="width: auto;">
1473
+ <option value="5">5</option>
1474
+ <option value="10" selected>10</option>
1475
+ <option value="20">20</option>
1476
+ <option value="50">50</option>
1477
+ <option value="100">100</option>
1478
+ </select>
1479
+ </div>
1480
+ <nav aria-label="Crash Events Pagination">
1481
+ <ul class="pagination pagination-sm mb-0" id="events-pagination">
1482
+ <!-- Pagination will be generated by JavaScript -->
1483
+ </ul>
1484
+ </nav>
1485
+ </div>
1486
+ </div>
1487
+ </div>
1488
+ </div>
1489
+ {% else %}
1490
+ <div class="section-block">
1491
+ <h2 class="section-title">
1492
+ <i class="bi bi-exclamation-triangle text-danger"></i> Crash Analysis
1493
+ </h2>
1494
+ <div class="alert alert-info text-center">
1495
+ <i class="bi bi-info-circle"></i> No crash or ANR events detected in this test session.
1496
+ </div>
1497
+ </div>
1498
+ {% endif %}
1499
+
1500
+ <!-- Property Checking Statistics -->
1501
+ <div class="section-block">
1502
+ <h2 class="section-title">Property Checking Statistics</h2>
1503
+
1504
+ <!-- Search Controls for Property Statistics -->
1505
+ <div class="search-controls-modern mb-4">
1506
+ <div class="d-flex align-items-center">
1507
+ <div class="search-icon-wrapper me-3">
1508
+ <i class="bi bi-search"></i>
1509
+ </div>
1510
+ <div class="flex-grow-1">
1511
+ <div class="input-group">
1512
+ <input type="text" class="form-control property-search-input"
1513
+ id="property-stats-search"
1514
+ placeholder="Search properties by name..."
1515
+ data-target="property-stats-container"
1516
+ data-item-class="property-stat-row"
1517
+ data-pagination="stats-pagination"
1518
+ data-page-size="stats-page-size">
1519
+ <button class="btn btn-primary search-btn" type="button"
1520
+ data-target="property-stats-search">
1521
+ <i class="bi bi-search"></i>
1522
+ </button>
1523
+ <button class="btn btn-outline-secondary search-clear-btn" type="button"
1524
+ data-target="property-stats-search">
1525
+ <i class="bi bi-x-lg"></i>
1526
+ </button>
1527
+ </div>
1528
+ <small class="text-muted search-results-count" id="property-search-results"></small>
1529
+ </div>
1530
+ </div>
1531
+ </div>
1532
+
1533
+ <div class="table-responsive">
1534
+ <table class="table table-custom">
1535
+ <thead>
1536
+ <tr>
1537
+ <th>Index</th>
1538
+ <th>Property Name</th>
1539
+ <th>Precondition Satisfied</th>
1540
+ <th>Executed</th>
1541
+ <th>Fails <i class="bi bi-arrow-down-up text-muted sort-icon" id="fails-sort" data-column="fails" data-order="none" style="cursor: pointer;"></i></th>
1542
+ <th>Errors <i class="bi bi-arrow-down-up text-muted sort-icon" id="errors-sort" data-column="errors" data-order="none" style="cursor: pointer;"></i></th>
1543
+ </tr>
1544
+ </thead>
1545
+ <tbody id="property-stats-container">
1546
+ {% for property_name, test_result in property_stats.items() %}
1547
+ <tr class="property-stat-row" data-page="1"
1548
+ data-index="{{ loop.index }}"
1549
+ data-property-name="{{ property_name }}"
1550
+ data-precond-satisfied="{{ test_result.precond_satisfied|default(0) }}"
1551
+ data-executed="{{ test_result.executed|default(0) }}"
1552
+ data-fails="{{ test_result.fail|default(0) }}"
1553
+ data-errors="{{ test_result.error|default(0) }}">
1554
+ <td>{{ loop.index }}</td>
1555
+ <td><span class="badge bg-light text-dark badge-custom">{{ property_name }}</span></td>
1556
+ <td>{{ test_result.precond_satisfied|default(0) }}</td>
1557
+ <td>{{ test_result.executed|default(0) }}</td>
1558
+ <td><span class="badge bg-danger text-white">{{ test_result.fail|default(0) }}</span></td>
1559
+ <td><span class="badge bg-warning text-dark">{{ test_result.error|default(0) }}</span></td>
1560
+ </tr>
1561
+ {% endfor %}
1562
+ </tbody>
1563
+ </table>
1564
+
1565
+ <!-- Pagination for Property Checking Statistics -->
1566
+ <div class="d-flex justify-content-between align-items-center mt-3">
1567
+ <div class="d-flex align-items-center">
1568
+ <label for="stats-page-size" class="form-label me-2 mb-0">Show:</label>
1569
+ <select class="form-select form-select-sm" id="stats-page-size" style="width: auto;">
1570
+ <option value="5">5</option>
1571
+ <option value="10" selected>10</option>
1572
+ <option value="20">20</option>
1573
+ <option value="50">50</option>
1574
+ <option value="100">100</option>
1575
+ </select>
1576
+ </div>
1577
+ <nav aria-label="Property Stats Pagination">
1578
+ <ul class="pagination pagination-sm mb-0" id="stats-pagination">
1579
+ <!-- Pagination will be generated by JavaScript -->
1580
+ </ul>
1581
+ </nav>
1582
+ </div>
1583
+ </div>
1584
+ </div>
1585
+ </div>
1586
+
1587
+ <!-- Footer -->
1588
+ <footer class="bg-dark text-white text-center py-4">
1589
+ <div class="container">
1590
+ <p class="mb-0">Kea2 Merged Test Report | Generated at: {{ timestamp }}</p>
1591
+ </div>
1592
+ </footer>
1593
+
1594
+ <!-- JavaScript -->
1595
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
1596
+ <script>
1597
+ document.addEventListener('DOMContentLoaded', function() {
1598
+ // Initialize merge info collapse functionality
1599
+ initMergeInfoCollapse();
1600
+
1601
+ // Initialize pagination for Activities lists
1602
+ initPagination('tested-activities-container', 'tested-activity', 'tested-pagination', 'tested-page-size');
1603
+ initPagination('all-activities-container', 'all-activity', 'all-pagination', 'all-page-size');
1604
+
1605
+ // Initialize activity sorting
1606
+ initActivitySorting();
1607
+
1608
+ // Initialize activity searching
1609
+ initActivitySearching();
1610
+
1611
+ // Initialize property statistics searching
1612
+ initPropertySearching();
1613
+
1614
+ // Initialize pagination for Property tables
1615
+ initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', 'stats-page-size');
1616
+
1617
+ // Initialize sorting for Property Checking Statistics
1618
+ initSorting();
1619
+
1620
+ // Initialize crash events functionality (only if crash analysis section exists)
1621
+ setTimeout(function() {
1622
+ const crashContainer = document.getElementById('crash-events-container');
1623
+ if (crashContainer) {
1624
+ initCrashAnalysis();
1625
+ // Pagination will be set up automatically by initEventFiltering
1626
+ }
1627
+ }, 100);
1628
+
1629
+ function setupCrashEventsPagination() {
1630
+ const container = document.getElementById('crash-events-container');
1631
+ const paginationElement = document.getElementById('events-pagination');
1632
+ const pageSizeSelect = document.getElementById('events-page-size');
1633
+
1634
+ if (!container || !paginationElement || !pageSizeSelect) {
1635
+ return;
1636
+ }
1637
+
1638
+ const allEventRows = Array.from(container.querySelectorAll('.event-row'));
1639
+
1640
+ // Get currently filtered visible rows (those not hidden by filter)
1641
+ const filteredRows = allEventRows.filter(row => {
1642
+ // Check if row should be visible based on current filter
1643
+ const eventType = row.dataset.type;
1644
+ const currentFilter = getCurrentFilter();
1645
+
1646
+ switch(currentFilter) {
1647
+ case 'all-events':
1648
+ return true;
1649
+ case 'crashes-only':
1650
+ return eventType === 'crash';
1651
+ case 'anr-only':
1652
+ return eventType === 'anr';
1653
+ default:
1654
+ return true;
1655
+ }
1656
+ });
1657
+
1658
+ const pageSize = parseInt(pageSizeSelect.value) || 10;
1659
+ const totalPages = Math.ceil(filteredRows.length / pageSize);
1660
+
1661
+ // Clear pagination first
1662
+ paginationElement.innerHTML = '';
1663
+
1664
+ // Show/hide pagination container based on whether pagination is needed
1665
+ const paginationContainer = paginationElement.closest('.pagination-container');
1666
+ if (paginationContainer) {
1667
+ if (totalPages > 1) {
1668
+ paginationContainer.style.display = '';
1669
+ } else {
1670
+ paginationContainer.style.display = 'none';
1671
+ }
1672
+ }
1673
+
1674
+ // Hide all rows first
1675
+ allEventRows.forEach(row => {
1676
+ row.style.display = 'none';
1677
+ const detailRow = row.nextElementSibling;
1678
+ if (detailRow && detailRow.classList.contains('collapse')) {
1679
+ detailRow.style.display = 'none';
1680
+ }
1681
+ });
1682
+
1683
+ // Show only the first page of filtered rows
1684
+ for (let i = 0; i < Math.min(pageSize, filteredRows.length); i++) {
1685
+ const row = filteredRows[i];
1686
+ row.style.display = '';
1687
+ const detailRow = row.nextElementSibling;
1688
+ if (detailRow && detailRow.classList.contains('collapse')) {
1689
+ detailRow.style.display = '';
1690
+ }
1691
+ }
1692
+
1693
+ // Create pagination if needed
1694
+ if (totalPages > 1) {
1695
+ createPaginationControls(totalPages, 1);
1696
+ }
1697
+
1698
+ function getCurrentFilter() {
1699
+ const filterButtons = document.querySelectorAll('input[name="event-filter"]');
1700
+ for (let button of filterButtons) {
1701
+ if (button.checked) {
1702
+ return button.id;
1703
+ }
1704
+ }
1705
+ return 'all-events'; // default
1706
+ }
1707
+
1708
+ function createPaginationControls(totalPages, currentPage) {
1709
+ paginationElement.innerHTML = '';
1710
+
1711
+ // Previous button
1712
+ const prevLi = document.createElement('li');
1713
+ prevLi.className = 'page-item' + (currentPage === 1 ? ' disabled' : '');
1714
+ prevLi.innerHTML = '<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>';
1715
+ prevLi.addEventListener('click', function(e) {
1716
+ e.preventDefault();
1717
+ if (currentPage > 1) {
1718
+ showCrashEventsPage(currentPage - 1, totalPages);
1719
+ }
1720
+ });
1721
+ paginationElement.appendChild(prevLi);
1722
+
1723
+ // Page numbers
1724
+ for (let i = 1; i <= totalPages; i++) {
1725
+ const pageLi = document.createElement('li');
1726
+ pageLi.className = i === currentPage ? 'page-item active' : 'page-item';
1727
+ pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
1728
+ pageLi.addEventListener('click', function(e) {
1729
+ e.preventDefault();
1730
+ showCrashEventsPage(i, totalPages);
1731
+ });
1732
+ paginationElement.appendChild(pageLi);
1733
+ }
1734
+
1735
+ // Next button
1736
+ const nextLi = document.createElement('li');
1737
+ nextLi.className = 'page-item' + (currentPage === totalPages ? ' disabled' : '');
1738
+ nextLi.innerHTML = '<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>';
1739
+ nextLi.addEventListener('click', function(e) {
1740
+ e.preventDefault();
1741
+ if (currentPage < totalPages) {
1742
+ showCrashEventsPage(currentPage + 1, totalPages);
1743
+ }
1744
+ });
1745
+ paginationElement.appendChild(nextLi);
1746
+ }
1747
+
1748
+ function showCrashEventsPage(pageNum, totalPages) {
1749
+ // Get current page size
1750
+ const currentPageSize = parseInt(pageSizeSelect.value) || 10;
1751
+
1752
+ // Recalculate filtered rows based on current filter
1753
+ const currentFilteredRows = allEventRows.filter(row => {
1754
+ const eventType = row.dataset.type;
1755
+ const currentFilter = getCurrentFilter();
1756
+
1757
+ switch(currentFilter) {
1758
+ case 'all-events':
1759
+ return true;
1760
+ case 'crashes-only':
1761
+ return eventType === 'crash';
1762
+ case 'anr-only':
1763
+ return eventType === 'anr';
1764
+ default:
1765
+ return true;
1766
+ }
1767
+ });
1768
+
1769
+ const startIndex = (pageNum - 1) * currentPageSize;
1770
+ const endIndex = startIndex + currentPageSize;
1771
+
1772
+ // Hide all rows first
1773
+ allEventRows.forEach(row => {
1774
+ row.style.display = 'none';
1775
+ const detailRow = row.nextElementSibling;
1776
+ if (detailRow && detailRow.classList.contains('collapse')) {
1777
+ detailRow.style.display = 'none';
1778
+ }
1779
+ });
1780
+
1781
+ // Show only the rows for current page
1782
+ for (let i = startIndex; i < endIndex && i < currentFilteredRows.length; i++) {
1783
+ const row = currentFilteredRows[i];
1784
+ row.style.display = '';
1785
+ const detailRow = row.nextElementSibling;
1786
+ if (detailRow && detailRow.classList.contains('collapse')) {
1787
+ detailRow.style.display = '';
1788
+ }
1789
+ }
1790
+
1791
+ // Update pagination controls and container visibility
1792
+ const paginationContainer = paginationElement.closest('.pagination-container');
1793
+ if (paginationContainer) {
1794
+ if (totalPages > 1) {
1795
+ paginationContainer.style.display = '';
1796
+ createPaginationControls(totalPages, pageNum);
1797
+ } else {
1798
+ paginationContainer.style.display = 'none';
1799
+ paginationElement.innerHTML = '';
1800
+ }
1801
+ }
1802
+ }
1803
+ }
1804
+
1805
+ // Activity sorting function
1806
+ function initActivitySorting() {
1807
+ const sortButtons = document.querySelectorAll('.activity-sort-btn');
1808
+
1809
+ sortButtons.forEach(function(button) {
1810
+ button.addEventListener('click', function() {
1811
+ const sortType = this.dataset.sort;
1812
+ const currentOrder = this.dataset.order;
1813
+ const parentTab = this.closest('.tab-pane');
1814
+ const isTestedTab = parentTab.id === 'tested-activities';
1815
+
1816
+ // Toggle sort order
1817
+ const newOrder = currentOrder === 'asc' ? 'desc' : 'asc';
1818
+
1819
+ this.dataset.order = newOrder;
1820
+ const icon = this.querySelector('.sort-icon');
1821
+
1822
+ if (newOrder === 'asc') {
1823
+ icon.className = 'bi bi-arrow-up sort-icon btn-arrow';
1824
+ } else {
1825
+ icon.className = 'bi bi-arrow-down sort-icon btn-arrow';
1826
+ }
1827
+
1828
+ // Sort activities
1829
+ sortActivities(sortType, newOrder, isTestedTab);
1830
+ });
1831
+ });
1832
+
1833
+ function sortActivities(sortType, order, isTestedTab) {
1834
+ const containerId = isTestedTab ? 'tested-activities-container' : 'all-activities-container';
1835
+ const itemClass = isTestedTab ? 'tested-activity' : 'all-activity';
1836
+ const paginationId = isTestedTab ? 'tested-pagination' : 'all-pagination';
1837
+ const pageSizeSelectId = isTestedTab ? 'tested-page-size' : 'all-page-size';
1838
+
1839
+ const container = document.getElementById(containerId);
1840
+ const items = Array.from(container.getElementsByClassName(itemClass));
1841
+
1842
+ // Check if there's an active search
1843
+ const searchInputId = isTestedTab ? 'tested-activity-search' : 'all-activity-search';
1844
+ const searchInput = document.getElementById(searchInputId);
1845
+ const hasActiveSearch = searchInput && searchInput.value.trim() !== '';
1846
+ const searchTerm = hasActiveSearch ? searchInput.value.toLowerCase().trim() : '';
1847
+
1848
+ // Store current search visibility state before sorting
1849
+ const searchStates = new Map();
1850
+ if (hasActiveSearch) {
1851
+ items.forEach(function(item) {
1852
+ const searchVisible = item.getAttribute('data-search-visible');
1853
+ searchStates.set(item, searchVisible);
1854
+ });
1855
+ }
1856
+
1857
+ // Sort all items
1858
+ items.sort(function(a, b) {
1859
+ const badgeA = a.querySelector('.traversal-badge');
1860
+ const badgeB = b.querySelector('.traversal-badge');
1861
+
1862
+ // Extract traversal count from badge text like " 5 times"
1863
+ const valueA = badgeA ? parseInt(badgeA.textContent.match(/\d+/)[0]) || 0 : 0;
1864
+ const valueB = badgeB ? parseInt(badgeB.textContent.match(/\d+/)[0]) || 0 : 0;
1865
+
1866
+ if (order === 'asc') {
1867
+ return valueA - valueB;
1868
+ } else {
1869
+ return valueB - valueA;
1870
+ }
1871
+ });
1872
+
1873
+ // Clear container and append sorted items
1874
+ container.innerHTML = '';
1875
+ items.forEach(function(item) {
1876
+ container.appendChild(item);
1877
+ });
1878
+
1879
+ // Restore search state and apply filtering
1880
+ if (hasActiveSearch) {
1881
+ let visibleCount = 0;
1882
+ items.forEach(function(item) {
1883
+ const activityNameElement = item.querySelector('.activity-name');
1884
+ if (activityNameElement) {
1885
+ const activityName = activityNameElement.textContent.toLowerCase();
1886
+
1887
+ if (activityName.includes(searchTerm)) {
1888
+ item.setAttribute('data-search-visible', 'true');
1889
+ visibleCount++;
1890
+ } else {
1891
+ item.setAttribute('data-search-visible', 'false');
1892
+ }
1893
+ }
1894
+ });
1895
+
1896
+ // Update search results count
1897
+ const resultsElement = document.getElementById(searchInput.id.replace('-search', '-search-results'));
1898
+ if (resultsElement) {
1899
+ resultsElement.textContent = `Found ${visibleCount} of ${items.length} activities`;
1900
+ }
1901
+
1902
+ // Re-initialize pagination with filtered results
1903
+ initPagination(containerId, itemClass, paginationId, pageSizeSelectId);
1904
+ } else {
1905
+ // No active search, just re-initialize pagination
1906
+ initPagination(containerId, itemClass, paginationId, pageSizeSelectId);
1907
+ }
1908
+ }
1909
+ }
1910
+
1911
+ // Simplified sorting function for Fails and Errors columns
1912
+ function initSorting() {
1913
+ const sortIcons = document.querySelectorAll('.sort-icon');
1914
+
1915
+ sortIcons.forEach(function(icon) {
1916
+ icon.addEventListener('click', function() {
1917
+ const column = this.dataset.column;
1918
+ const currentOrder = this.dataset.order;
1919
+
1920
+ // Reset all other sort icons
1921
+ sortIcons.forEach(function(otherIcon) {
1922
+ if (otherIcon !== icon) {
1923
+ otherIcon.dataset.order = 'none';
1924
+ otherIcon.className = 'bi bi-arrow-down-up sort-icon';
1925
+ otherIcon.style.color = '#6c757d';
1926
+ }
1927
+ });
1928
+
1929
+ // Toggle current sort order
1930
+ let newOrder;
1931
+ if (currentOrder === 'none' || currentOrder === 'desc') {
1932
+ newOrder = 'asc';
1933
+ this.className = 'bi bi-arrow-up sort-icon active asc';
1934
+ this.style.color = '#28a745';
1935
+ } else {
1936
+ newOrder = 'desc';
1937
+ this.className = 'bi bi-arrow-down sort-icon active desc';
1938
+ this.style.color = '#dc3545';
1939
+ }
1940
+
1941
+ this.dataset.order = newOrder;
1942
+ sortTable(column, newOrder);
1943
+ });
1944
+ });
1945
+
1946
+ function sortTable(column, order) {
1947
+ const container = document.getElementById('property-stats-container');
1948
+ const rows = Array.from(container.getElementsByClassName('property-stat-row'));
1949
+
1950
+ rows.sort(function(a, b) {
1951
+ let valueA, valueB;
1952
+
1953
+ if (column === 'fails') {
1954
+ valueA = parseInt(a.dataset.fails);
1955
+ valueB = parseInt(b.dataset.fails);
1956
+ } else if (column === 'errors') {
1957
+ valueA = parseInt(a.dataset.errors);
1958
+ valueB = parseInt(b.dataset.errors);
1959
+ }
1960
+
1961
+ if (order === 'asc') {
1962
+ return valueA - valueB;
1963
+ } else {
1964
+ return valueB - valueA;
1965
+ }
1966
+ });
1967
+
1968
+ // Clear container and append sorted rows
1969
+ container.innerHTML = '';
1970
+ rows.forEach(function(row) {
1971
+ container.appendChild(row);
1972
+ });
1973
+
1974
+ // Re-initialize pagination after sorting
1975
+ initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', 'stats-page-size');
1976
+ }
1977
+ }
1978
+
1979
+ // Function to handle page navigation
1980
+ function goToPage(pageNumber, containerId, itemClass, itemsPerPage, paginationId) {
1981
+ const container = document.getElementById(containerId);
1982
+ const allItems = Array.from(container.getElementsByClassName(itemClass));
1983
+ const paginationElement = document.getElementById(paginationId);
1984
+
1985
+ // Get items that should be visible based on search filter
1986
+ const filteredItems = allItems.filter(item => {
1987
+ const searchVisible = item.getAttribute('data-search-visible');
1988
+ const shouldShow = searchVisible === null || searchVisible === 'true';
1989
+ return shouldShow;
1990
+ });
1991
+
1992
+ // Update pagination active state
1993
+ if (paginationElement) {
1994
+ const pageItems = paginationElement.getElementsByClassName('page-item');
1995
+ const totalPages = Math.max(1, Math.ceil(filteredItems.length / itemsPerPage));
1996
+
1997
+ for (let i = 0; i < pageItems.length; i++) {
1998
+ const pageText = pageItems[i].textContent.trim();
1999
+ if (pageText === pageNumber.toString()) {
2000
+ pageItems[i].className = 'page-item active';
2001
+ } else if (pageText !== '«' && pageText !== '»') {
2002
+ pageItems[i].className = 'page-item';
2003
+ } else if (pageText === '«') {
2004
+ // Previous button
2005
+ pageItems[i].className = pageNumber <= 1 ? 'page-item disabled' : 'page-item';
2006
+ } else if (pageText === '»') {
2007
+ // Next button
2008
+ pageItems[i].className = pageNumber >= totalPages ? 'page-item disabled' : 'page-item';
2009
+ }
2010
+ }
2011
+ }
2012
+
2013
+ // Hide all items first
2014
+ allItems.forEach((item, index) => {
2015
+ item.style.display = 'none';
2016
+ });
2017
+
2018
+ // Show items for current page (only from filtered items)
2019
+ const startIndex = (pageNumber - 1) * itemsPerPage;
2020
+ const endIndex = Math.min(startIndex + itemsPerPage, filteredItems.length);
2021
+
2022
+ for (let i = startIndex; i < endIndex; i++) {
2023
+ if (filteredItems[i]) {
2024
+ filteredItems[i].style.display = '';
2025
+ }
2026
+ }
2027
+ }
2028
+
2029
+ // Pagination initialization function
2030
+ function initPagination(containerId, itemClass, paginationId, pageSizeSelectId) {
2031
+ const container = document.getElementById(containerId);
2032
+ const pageSizeSelect = document.getElementById(pageSizeSelectId);
2033
+ if (!container) return;
2034
+
2035
+ // Remove existing event listener to prevent duplicate bindings
2036
+ if (pageSizeSelect && !pageSizeSelect.hasAttribute('data-listener-bound')) {
2037
+ pageSizeSelect.addEventListener('change', function() {
2038
+ const newItemsPerPage = parseInt(this.value);
2039
+ renderPagination();
2040
+ goToPage(1, containerId, itemClass, newItemsPerPage, paginationId);
2041
+ });
2042
+ // Mark as having listener bound
2043
+ pageSizeSelect.setAttribute('data-listener-bound', 'true');
2044
+ }
2045
+
2046
+ function renderPagination() {
2047
+ const allItems = Array.from(container.getElementsByClassName(itemClass));
2048
+
2049
+ // Always get current page size from select element
2050
+ const currentPageSizeSelect = document.getElementById(pageSizeSelectId);
2051
+ const currentItemsPerPage = currentPageSizeSelect ? parseInt(currentPageSizeSelect.value) : 10;
2052
+
2053
+ // Use same filtering logic as goToPage function
2054
+ const filteredItems = allItems.filter(item => {
2055
+ const searchVisible = item.getAttribute('data-search-visible');
2056
+ // If no search filter is applied, show all items
2057
+ return searchVisible === null || searchVisible === 'true';
2058
+ });
2059
+
2060
+ const totalItems = filteredItems.length;
2061
+ const totalPages = Math.max(1, Math.ceil(totalItems / currentItemsPerPage));
2062
+
2063
+ // Create pagination
2064
+ const paginationElement = document.getElementById(paginationId);
2065
+ if (!paginationElement) return;
2066
+
2067
+ // Clear pagination
2068
+ paginationElement.innerHTML = '';
2069
+
2070
+ // Don't show pagination if there's only one page or no items
2071
+ if (totalPages <= 1) {
2072
+ // Hide the entire pagination container when not needed
2073
+ const paginationContainer = paginationElement.closest('.pagination-container');
2074
+ if (paginationContainer) {
2075
+ paginationContainer.style.display = 'none';
2076
+ }
2077
+ return;
2078
+ }
2079
+
2080
+ // Show the pagination container when needed
2081
+ const paginationContainer = paginationElement.closest('.pagination-container');
2082
+ if (paginationContainer) {
2083
+ paginationContainer.style.display = '';
2084
+ }
2085
+
2086
+ // Previous button
2087
+ const prevLi = document.createElement('li');
2088
+ prevLi.className = 'page-item disabled';
2089
+ prevLi.innerHTML = '<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>';
2090
+ prevLi.addEventListener('click', function(e) {
2091
+ e.preventDefault();
2092
+ const activePage = paginationElement.querySelector('.page-item.active');
2093
+ if (activePage) {
2094
+ const currentPageNum = parseInt(activePage.textContent.trim());
2095
+ if (currentPageNum > 1) {
2096
+ goToPage(currentPageNum - 1, containerId, itemClass, currentItemsPerPage, paginationId);
2097
+ }
2098
+ }
2099
+ });
2100
+ paginationElement.appendChild(prevLi);
2101
+
2102
+ // Add page numbers
2103
+ for (let i = 1; i <= totalPages; i++) {
2104
+ const pageLi = document.createElement('li');
2105
+ pageLi.className = i === 1 ? 'page-item active' : 'page-item';
2106
+ pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
2107
+ pageLi.addEventListener('click', function(e) {
2108
+ e.preventDefault();
2109
+ goToPage(i, containerId, itemClass, currentItemsPerPage, paginationId);
2110
+ });
2111
+ paginationElement.appendChild(pageLi);
2112
+ }
2113
+
2114
+ // Next button
2115
+ const nextLi = document.createElement('li');
2116
+ nextLi.className = totalPages <= 1 ? 'page-item disabled' : 'page-item';
2117
+ nextLi.innerHTML = '<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>';
2118
+ nextLi.addEventListener('click', function(e) {
2119
+ e.preventDefault();
2120
+ const activePage = paginationElement.querySelector('.page-item.active');
2121
+ if (activePage) {
2122
+ const currentPageNum = parseInt(activePage.textContent.trim());
2123
+ if (currentPageNum < totalPages) {
2124
+ goToPage(currentPageNum + 1, containerId, itemClass, currentItemsPerPage, paginationId);
2125
+ }
2126
+ }
2127
+ });
2128
+ paginationElement.appendChild(nextLi);
2129
+ }
2130
+
2131
+ // Initial render
2132
+ renderPagination();
2133
+ // Always call goToPage to ensure items are displayed correctly, even if no pagination is shown
2134
+ const currentItemsPerPage = pageSizeSelect ? parseInt(pageSizeSelect.value) : 10;
2135
+ goToPage(1, containerId, itemClass, currentItemsPerPage, paginationId);
2136
+ }
2137
+
2138
+ // Activity searching function
2139
+ function initActivitySearching() {
2140
+ const searchInputs = document.querySelectorAll('.activity-search-input');
2141
+
2142
+ searchInputs.forEach(function(searchInput) {
2143
+ const searchButton = document.querySelector('.search-btn[data-target="' + searchInput.id + '"]');
2144
+ const clearButton = document.querySelector('[data-target="' + searchInput.id + '"].search-clear-btn');
2145
+
2146
+ // Search on Enter key
2147
+ searchInput.addEventListener('keydown', function(e) {
2148
+ if (e.key === 'Enter') {
2149
+ e.preventDefault();
2150
+ performActivitySearch(this);
2151
+ } else if (e.key === 'Escape') {
2152
+ clearActivitySearch(this);
2153
+ }
2154
+ });
2155
+
2156
+ // Search button functionality
2157
+ if (searchButton) {
2158
+ searchButton.addEventListener('click', function() {
2159
+ performActivitySearch(searchInput);
2160
+ });
2161
+ }
2162
+
2163
+ // Clear button functionality
2164
+ if (clearButton) {
2165
+ clearButton.addEventListener('click', function() {
2166
+ clearActivitySearch(searchInput);
2167
+ });
2168
+ }
2169
+ });
2170
+
2171
+ function performActivitySearch(searchInput) {
2172
+ const searchTerm = searchInput.value.toLowerCase().trim();
2173
+ const containerId = searchInput.dataset.target;
2174
+ const itemClass = searchInput.dataset.itemClass;
2175
+ const paginationId = searchInput.dataset.pagination;
2176
+ const pageSizeSelectId = searchInput.dataset.pageSize;
2177
+
2178
+ const container = document.getElementById(containerId);
2179
+ const items = Array.from(container.getElementsByClassName(itemClass));
2180
+
2181
+ let visibleCount = 0;
2182
+ let totalCount = items.length;
2183
+
2184
+ // Filter items based on search term
2185
+ items.forEach(function(item) {
2186
+ const activityNameElement = item.querySelector('.activity-name');
2187
+ if (activityNameElement) {
2188
+ const activityName = activityNameElement.textContent.toLowerCase();
2189
+
2190
+ if (searchTerm === '' || activityName.includes(searchTerm)) {
2191
+ item.setAttribute('data-search-visible', 'true');
2192
+ visibleCount++;
2193
+ } else {
2194
+ item.setAttribute('data-search-visible', 'false');
2195
+ }
2196
+ }
2197
+ });
2198
+
2199
+ // Update search results count
2200
+ const resultsElement = document.getElementById(searchInput.id.replace('-search', '-search-results'));
2201
+ if (resultsElement) {
2202
+ if (searchTerm === '') {
2203
+ resultsElement.textContent = '';
2204
+ } else {
2205
+ resultsElement.textContent = `Found ${visibleCount} of ${totalCount} activities`;
2206
+ }
2207
+ }
2208
+
2209
+ // Re-initialize pagination with filtered results
2210
+ initPagination(containerId, itemClass, paginationId, pageSizeSelectId);
2211
+ }
2212
+
2213
+ function clearActivitySearch(searchInput) {
2214
+ searchInput.value = '';
2215
+ performActivitySearch(searchInput);
2216
+ }
2217
+ }
2218
+
2219
+ // Property statistics searching function
2220
+ function initPropertySearching() {
2221
+ const searchInput = document.getElementById('property-stats-search');
2222
+ const searchButton = document.querySelector('.search-btn[data-target="property-stats-search"]');
2223
+ const clearButton = document.querySelector('[data-target="property-stats-search"].search-clear-btn');
2224
+
2225
+ if (!searchInput) return;
2226
+
2227
+ // Search on Enter key
2228
+ searchInput.addEventListener('keydown', function(e) {
2229
+ if (e.key === 'Enter') {
2230
+ e.preventDefault();
2231
+ performPropertySearch(this);
2232
+ } else if (e.key === 'Escape') {
2233
+ clearPropertySearch(this);
2234
+ }
2235
+ });
2236
+
2237
+ // Search button functionality
2238
+ if (searchButton) {
2239
+ searchButton.addEventListener('click', function() {
2240
+ performPropertySearch(searchInput);
2241
+ });
2242
+ }
2243
+
2244
+ // Clear button functionality
2245
+ if (clearButton) {
2246
+ clearButton.addEventListener('click', function() {
2247
+ clearPropertySearch(searchInput);
2248
+ });
2249
+ }
2250
+
2251
+ function performPropertySearch(searchInput) {
2252
+ const searchTerm = searchInput.value.toLowerCase().trim();
2253
+ const container = document.getElementById('property-stats-container');
2254
+ const items = Array.from(container.getElementsByClassName('property-stat-row'));
2255
+
2256
+ let visibleCount = 0;
2257
+ let totalCount = items.length;
2258
+
2259
+ // Filter items based on search term
2260
+ items.forEach(function(item) {
2261
+ const propertyNameElement = item.querySelector('.badge-custom');
2262
+ if (propertyNameElement) {
2263
+ const propertyName = propertyNameElement.textContent.toLowerCase();
2264
+
2265
+ if (searchTerm === '' || propertyName.includes(searchTerm)) {
2266
+ item.setAttribute('data-search-visible', 'true');
2267
+ visibleCount++;
2268
+ } else {
2269
+ item.setAttribute('data-search-visible', 'false');
2270
+ }
2271
+ }
2272
+ });
2273
+
2274
+ // Update search results count
2275
+ const resultsElement = document.getElementById('property-search-results');
2276
+ if (resultsElement) {
2277
+ if (searchTerm === '') {
2278
+ resultsElement.textContent = '';
2279
+ } else {
2280
+ resultsElement.textContent = `Found ${visibleCount} of ${totalCount} properties`;
2281
+ }
2282
+ }
2283
+
2284
+ // Re-initialize pagination with filtered results
2285
+ initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', 'stats-page-size');
2286
+ }
2287
+
2288
+ function clearPropertySearch(searchInput) {
2289
+ searchInput.value = '';
2290
+ performPropertySearch(searchInput);
2291
+ }
2292
+ }
2293
+
2294
+ function updatePaginationControls(paginationId, currentPage, totalPages, onPageClick) {
2295
+ var pagination = document.getElementById(paginationId);
2296
+ if (!pagination) return;
2297
+
2298
+ pagination.innerHTML = '';
2299
+
2300
+ if (totalPages <= 1) {
2301
+ // Hide the entire pagination container when not needed
2302
+ var paginationContainer = pagination.closest('.pagination-container');
2303
+ if (paginationContainer) {
2304
+ paginationContainer.style.display = 'none';
2305
+ }
2306
+ return;
2307
+ }
2308
+
2309
+ // Show the pagination container when needed
2310
+ var paginationContainer = pagination.closest('.pagination-container');
2311
+ if (paginationContainer) {
2312
+ paginationContainer.style.display = '';
2313
+ }
2314
+
2315
+ // Previous button
2316
+ var prevLi = document.createElement('li');
2317
+ prevLi.className = 'page-item' + (currentPage === 1 ? ' disabled' : '');
2318
+ var prevLink = document.createElement('a');
2319
+ prevLink.className = 'page-link';
2320
+ prevLink.href = '#';
2321
+ prevLink.setAttribute('aria-label', 'Previous');
2322
+ prevLink.innerHTML = '<span aria-hidden="true">&laquo;</span>';
2323
+
2324
+ prevLink.addEventListener('click', function(e) {
2325
+ e.preventDefault();
2326
+ e.stopPropagation();
2327
+ // Get current page dynamically to avoid stale closure values
2328
+ var currentActivePage = getCurrentPageNumber(paginationId);
2329
+ if (currentActivePage > 1 && !prevLi.classList.contains('disabled')) {
2330
+ onPageClick(currentActivePage - 1);
2331
+ }
2332
+ });
2333
+
2334
+ prevLi.appendChild(prevLink);
2335
+ pagination.appendChild(prevLi);
2336
+
2337
+ // Page numbers
2338
+ for (var i = 1; i <= totalPages; i++) {
2339
+ var li = document.createElement('li');
2340
+ li.className = 'page-item' + (i === currentPage ? ' active' : '');
2341
+ var link = document.createElement('a');
2342
+ link.className = 'page-link';
2343
+ link.href = '#';
2344
+ link.textContent = i;
2345
+ link.addEventListener('click', (function(page) {
2346
+ return function(e) {
2347
+ e.preventDefault();
2348
+ e.stopPropagation();
2349
+ onPageClick(page);
2350
+ };
2351
+ })(i));
2352
+ li.appendChild(link);
2353
+ pagination.appendChild(li);
2354
+ }
2355
+
2356
+ // Next button
2357
+ var nextLi = document.createElement('li');
2358
+ nextLi.className = 'page-item' + (currentPage === totalPages ? ' disabled' : '');
2359
+ var nextLink = document.createElement('a');
2360
+ nextLink.className = 'page-link';
2361
+ nextLink.href = '#';
2362
+ nextLink.setAttribute('aria-label', 'Next');
2363
+ nextLink.innerHTML = '<span aria-hidden="true">&raquo;</span>';
2364
+
2365
+ nextLink.addEventListener('click', function(e) {
2366
+ e.preventDefault();
2367
+ e.stopPropagation();
2368
+ // Get current page dynamically to avoid stale closure values
2369
+ var currentActivePage = getCurrentPageNumber(paginationId);
2370
+ if (currentActivePage < totalPages && !nextLi.classList.contains('disabled')) {
2371
+ onPageClick(currentActivePage + 1);
2372
+ }
2373
+ });
2374
+
2375
+ nextLi.appendChild(nextLink);
2376
+ pagination.appendChild(nextLi);
2377
+ }
2378
+
2379
+ function getCurrentPageNumber(paginationId) {
2380
+ var pagination = document.getElementById(paginationId);
2381
+ if (!pagination) return 1;
2382
+
2383
+ var activeItem = pagination.querySelector('.page-item.active');
2384
+ if (activeItem) {
2385
+ var pageText = activeItem.textContent.trim();
2386
+ var pageNum = parseInt(pageText);
2387
+ return isNaN(pageNum) ? 1 : pageNum;
2388
+ }
2389
+ return 1;
2390
+ }
2391
+
2392
+ function initMergeInfoCollapse() {
2393
+ // Initialize merge info collapse functionality if needed
2394
+ // This function can be expanded based on specific merge info requirements
2395
+ console.log('Merge info collapse initialized');
2396
+ }
2397
+
2398
+ // Crash Analysis Functions
2399
+ function initCrashAnalysis() {
2400
+ // Initialize event filtering first (this will set initial visibility)
2401
+ initEventFiltering();
2402
+
2403
+ // Initialize pagination for crash events (using specialized function)
2404
+ initCrashEventsPagination();
2405
+
2406
+ // Initialize copy stack trace functionality
2407
+ initCopyStackTrace();
2408
+ }
2409
+
2410
+ function initEventFiltering() {
2411
+ const filterButtons = document.querySelectorAll('input[name="event-filter"]');
2412
+
2413
+ filterButtons.forEach(button => {
2414
+ button.addEventListener('change', function() {
2415
+ const filterType = this.id;
2416
+ filterEvents(filterType);
2417
+ });
2418
+ });
2419
+
2420
+ // Initialize with default filter (all-events)
2421
+ filterEvents('all-events');
2422
+ }
2423
+
2424
+ function filterEvents(filterType) {
2425
+ const eventRows = document.querySelectorAll('.event-row');
2426
+
2427
+ eventRows.forEach(row => {
2428
+ const eventType = row.dataset.type;
2429
+ let shouldShow = false;
2430
+
2431
+ switch(filterType) {
2432
+ case 'all-events':
2433
+ shouldShow = true;
2434
+ break;
2435
+ case 'crashes-only':
2436
+ shouldShow = eventType === 'crash';
2437
+ break;
2438
+ case 'anr-only':
2439
+ shouldShow = eventType === 'anr';
2440
+ break;
2441
+ }
2442
+
2443
+ // Set the display style to control visibility
2444
+ row.style.display = shouldShow ? '' : 'none';
2445
+
2446
+ // Also control the corresponding detail row
2447
+ const detailRow = row.nextElementSibling;
2448
+ if (detailRow && detailRow.classList.contains('collapse')) {
2449
+ detailRow.style.display = shouldShow ? '' : 'none';
2450
+ }
2451
+ });
2452
+
2453
+ // Re-setup pagination after filtering
2454
+ setTimeout(function() {
2455
+ setupCrashEventsPagination();
2456
+ }, 10);
2457
+ }
2458
+
2459
+
2460
+
2461
+ function initCrashEventsPagination() {
2462
+ const pageSizeSelect = document.getElementById('events-page-size');
2463
+
2464
+ if (pageSizeSelect) {
2465
+ // Add event listener for page size change
2466
+ pageSizeSelect.addEventListener('change', function() {
2467
+ setupCrashEventsPagination();
2468
+ });
2469
+ }
2470
+ }
2471
+
2472
+
2473
+
2474
+
2475
+
2476
+
2477
+ function initCopyStackTrace() {
2478
+ const copyButtons = document.querySelectorAll('.copy-stack-btn');
2479
+
2480
+ copyButtons.forEach(button => {
2481
+ button.addEventListener('click', function() {
2482
+ const stackIndex = this.dataset.stackIndex;
2483
+ copyStackTrace(stackIndex);
2484
+ });
2485
+ });
2486
+ }
2487
+
2488
+ function copyStackTrace(stackIndex) {
2489
+ const stackElement = document.getElementById(`stack-trace-${stackIndex}`);
2490
+ if (!stackElement) return;
2491
+
2492
+ const stackText = stackElement.textContent;
2493
+
2494
+ if (navigator.clipboard && navigator.clipboard.writeText) {
2495
+ navigator.clipboard.writeText(stackText).then(() => {
2496
+ showCopyNotification('Stack trace copied to clipboard!');
2497
+ }).catch(err => {
2498
+ console.error('Failed to copy stack trace: ', err);
2499
+ showCopyNotification('Failed to copy stack trace', 'error');
2500
+ });
2501
+ } else {
2502
+ // Fallback for older browsers
2503
+ const textArea = document.createElement('textarea');
2504
+ textArea.value = stackText;
2505
+ document.body.appendChild(textArea);
2506
+ textArea.select();
2507
+ try {
2508
+ document.execCommand('copy');
2509
+ showCopyNotification('Stack trace copied to clipboard!');
2510
+ } catch (err) {
2511
+ console.error('Fallback copy failed: ', err);
2512
+ showCopyNotification('Failed to copy stack trace', 'error');
2513
+ }
2514
+ document.body.removeChild(textArea);
2515
+ }
2516
+ }
2517
+
2518
+ function showCopyNotification(message, type) {
2519
+ type = type || 'success';
2520
+
2521
+ // Create notification element
2522
+ const notification = document.createElement('div');
2523
+ notification.className = 'alert alert-' + (type === 'error' ? 'danger' : 'success') + ' position-fixed';
2524
+ notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px; opacity: 0; transition: opacity 0.3s ease-in-out;';
2525
+ notification.innerHTML = '<i class="bi bi-' + (type === 'error' ? 'exclamation-triangle' : 'check-circle') + '"></i> ' + message;
2526
+
2527
+ document.body.appendChild(notification);
2528
+
2529
+ // Fade in animation
2530
+ setTimeout(function() {
2531
+ notification.style.opacity = '1';
2532
+ }, 10);
2533
+
2534
+ // Auto remove after 3 seconds with fade out
2535
+ setTimeout(function() {
2536
+ notification.style.opacity = '0';
2537
+ setTimeout(function() {
2538
+ if (notification.parentNode) {
2539
+ notification.parentNode.removeChild(notification);
2540
+ }
2541
+ }, 300);
2542
+ }, 3000);
2543
+ }
2544
+ });
2545
+ </script>
2546
+ </body>
2547
+ </html>