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.

@@ -201,43 +201,86 @@
201
201
  vertical-align: middle;
202
202
  text-align: center;
203
203
  }
204
+
205
+ /* Override text alignment for stack trace containers */
206
+ .table-custom td .bg-light {
207
+ text-align: left;
208
+ }
209
+
210
+ .table-custom td .bg-light pre {
211
+ text-align: left !important;
212
+ }
213
+
214
+ /* Enhanced Error Details styling */
215
+ .table-custom td:nth-child(7) .collapse {
216
+ position: relative;
217
+ z-index: 10;
218
+ }
219
+
220
+ .table-custom td:nth-child(7) .card {
221
+ max-width: none;
222
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
223
+ }
224
+
225
+ .table-custom td:nth-child(7) .card-body {
226
+ padding: 1rem;
227
+ max-height: 400px;
228
+ overflow-y: auto;
229
+ }
230
+
231
+ .table-custom td:nth-child(7) pre {
232
+ max-height: 200px;
233
+ overflow-y: auto;
234
+ font-size: 0.8rem;
235
+ line-height: 1.4;
236
+ }
237
+
238
+ .table-custom td:nth-child(7) details {
239
+ margin-top: 0.5rem;
240
+ }
241
+
242
+ .table-custom td:nth-child(7) summary {
243
+ cursor: pointer;
244
+ margin-bottom: 0.5rem;
245
+ }
204
246
 
205
247
  /* Specific column widths for property statistics table */
206
248
  .table-custom th:nth-child(1), .table-custom td:nth-child(1) { /* Index */
207
- width: 8%;
208
- min-width: 60px;
249
+ width: 5%;
250
+ min-width: 50px;
209
251
  }
210
-
252
+
211
253
  .table-custom th:nth-child(2), .table-custom td:nth-child(2) { /* Property Name */
212
- width: 25%;
213
- min-width: 200px;
254
+ width: 18%;
255
+ min-width: 180px;
214
256
  text-align: left;
215
257
  }
216
-
258
+
217
259
  .table-custom th:nth-child(3), .table-custom td:nth-child(3) { /* Precondition Satisfied */
218
- width: 12%;
219
- min-width: 100px;
260
+ width: 9%;
261
+ min-width: 85px;
220
262
  }
221
-
263
+
222
264
  .table-custom th:nth-child(4), .table-custom td:nth-child(4) { /* Executed */
223
- width: 10%;
224
- min-width: 80px;
265
+ width: 8%;
266
+ min-width: 70px;
225
267
  }
226
-
268
+
227
269
  .table-custom th:nth-child(5), .table-custom td:nth-child(5) { /* Fails */
228
- width: 10%;
229
- min-width: 80px;
270
+ width: 7%;
271
+ min-width: 65px;
230
272
  }
231
-
273
+
232
274
  .table-custom th:nth-child(6), .table-custom td:nth-child(6) { /* Errors */
233
- width: 10%;
234
- min-width: 80px;
275
+ width: 8%;
276
+ min-width: 70px;
235
277
  }
236
278
 
237
279
  .table-custom th:nth-child(7), .table-custom td:nth-child(7) { /* Error Details */
238
- width: 25%;
239
- min-width: 200px;
240
- text-align: left;
280
+ width: 45%;
281
+ min-width: 400px;
282
+ text-align: center;
283
+ position: relative;
241
284
  }
242
285
 
243
286
  .table-custom tbody tr:nth-of-type(odd) {
@@ -572,6 +615,418 @@
572
615
  white-space: normal;
573
616
  }
574
617
  }
618
+
619
+ /* Modern Sorting Controls Styling */
620
+ .sorting-controls-modern {
621
+ background: #ffffff;
622
+ border: 1px solid #e5e7eb;
623
+ border-radius: 16px;
624
+ padding: 20px 24px;
625
+ margin-bottom: 16px;
626
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
627
+ transition: all 0.2s ease;
628
+ position: relative;
629
+ }
630
+
631
+ .sorting-controls-modern:hover {
632
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.06);
633
+ border-color: #d1d5db;
634
+ }
635
+
636
+ .sort-label-section {
637
+ display: flex;
638
+ align-items: center;
639
+ gap: 16px;
640
+ }
641
+
642
+ .sort-icon-wrapper {
643
+ width: 48px;
644
+ height: 48px;
645
+ background: linear-gradient(135deg, #3498db, #2980b9);
646
+ border-radius: 12px;
647
+ display: flex;
648
+ align-items: center;
649
+ justify-content: center;
650
+ box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3);
651
+ }
652
+
653
+ .sort-icon-wrapper i {
654
+ color: white;
655
+ font-size: 20px;
656
+ }
657
+
658
+ .sort-text {
659
+ display: flex;
660
+ flex-direction: column;
661
+ gap: 4px;
662
+ }
663
+
664
+ .sort-title {
665
+ font-size: 16px;
666
+ font-weight: 600;
667
+ color: #2c3e50;
668
+ line-height: 1.2;
669
+ }
670
+
671
+ .sort-subtitle {
672
+ font-size: 13px;
673
+ color: #7f8c8d;
674
+ font-weight: 400;
675
+ line-height: 1.2;
676
+ }
677
+
678
+ .sort-button-section {
679
+ display: flex;
680
+ align-items: center;
681
+ }
682
+
683
+ .btn-sort-modern {
684
+ background: linear-gradient(135deg, #27ae60, #2ecc71);
685
+ border: none;
686
+ border-radius: 10px;
687
+ padding: 12px 20px;
688
+ color: white;
689
+ font-weight: 500;
690
+ font-size: 14px;
691
+ cursor: pointer;
692
+ transition: all 0.3s ease;
693
+ box-shadow: 0 2px 8px rgba(46, 204, 113, 0.3);
694
+ position: relative;
695
+ overflow: hidden;
696
+ }
697
+
698
+ .btn-sort-modern::before {
699
+ content: '';
700
+ position: absolute;
701
+ top: 0;
702
+ left: -100%;
703
+ width: 100%;
704
+ height: 100%;
705
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
706
+ transition: left 0.5s ease;
707
+ }
708
+
709
+ .btn-sort-modern:hover {
710
+ transform: translateY(-2px);
711
+ box-shadow: 0 4px 16px rgba(46, 204, 113, 0.4);
712
+ }
713
+
714
+ .btn-sort-modern:hover::before {
715
+ left: 100%;
716
+ }
717
+
718
+ .btn-sort-modern:active {
719
+ transform: translateY(0px);
720
+ box-shadow: 0 2px 8px rgba(46, 204, 113, 0.3);
721
+ }
722
+
723
+ .btn-content {
724
+ display: flex;
725
+ align-items: center;
726
+ gap: 8px;
727
+ position: relative;
728
+ z-index: 1;
729
+ }
730
+
731
+ .btn-icon {
732
+ font-size: 16px;
733
+ opacity: 0.9;
734
+ }
735
+
736
+ .btn-text {
737
+ font-size: 14px;
738
+ font-weight: 500;
739
+ white-space: nowrap;
740
+ }
741
+
742
+ .btn-arrow {
743
+ font-size: 14px;
744
+ transition: transform 0.3s ease;
745
+ opacity: 0.8;
746
+ }
747
+
748
+ .btn-sort-modern:hover .btn-arrow {
749
+ transform: scale(1.1);
750
+ opacity: 1;
751
+ }
752
+
753
+ /* Responsive design for sorting controls */
754
+ @media (max-width: 768px) {
755
+ .sorting-controls-modern {
756
+ padding: 16px 20px;
757
+ }
758
+
759
+ .sort-label-section {
760
+ gap: 12px;
761
+ }
762
+
763
+ .sort-icon-wrapper {
764
+ width: 40px;
765
+ height: 40px;
766
+ }
767
+
768
+ .sort-icon-wrapper i {
769
+ font-size: 16px;
770
+ }
771
+
772
+ .sort-title {
773
+ font-size: 14px;
774
+ }
775
+
776
+ .sort-subtitle {
777
+ font-size: 12px;
778
+ }
779
+
780
+ .btn-sort-modern {
781
+ padding: 10px 16px;
782
+ }
783
+
784
+ .btn-text {
785
+ font-size: 13px;
786
+ }
787
+ }
788
+
789
+ @media (max-width: 576px) {
790
+ .sorting-controls-modern {
791
+ flex-direction: column;
792
+ text-align: center;
793
+ gap: 16px;
794
+ }
795
+
796
+ .sorting-controls-modern .d-flex {
797
+ flex-direction: column;
798
+ gap: 16px;
799
+ }
800
+
801
+ .sort-label-section {
802
+ justify-content: center;
803
+ }
804
+ }
805
+
806
+ /* Search Controls Styling */
807
+ .search-controls-modern {
808
+ background: #ffffff;
809
+ border: 1px solid #e5e7eb;
810
+ border-radius: 16px;
811
+ padding: 20px 24px;
812
+ margin-bottom: 20px;
813
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
814
+ transition: all 0.2s ease;
815
+ position: relative;
816
+ }
817
+
818
+ .search-controls-modern:hover {
819
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.06);
820
+ border-color: #d1d5db;
821
+ }
822
+
823
+ /* Modern Activity Item Styling */
824
+ .activity-item {
825
+ background: #ffffff;
826
+ border: 1px solid #f3f4f6;
827
+ border-radius: 12px;
828
+ padding: 16px 20px;
829
+ margin-bottom: 8px;
830
+ transition: all 0.2s ease;
831
+ display: flex;
832
+ align-items: center;
833
+ justify-content: space-between;
834
+ }
835
+
836
+ .activity-item:hover {
837
+ border-color: #e5e7eb;
838
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
839
+ transform: translateY(-1px);
840
+ }
841
+
842
+ .activity-content {
843
+ display: flex;
844
+ align-items: center;
845
+ gap: 12px;
846
+ }
847
+
848
+ .activity-name {
849
+ font-weight: 500;
850
+ color: #374151;
851
+ font-size: 14px;
852
+ }
853
+
854
+ .traversal-badge {
855
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8) !important;
856
+ border: none;
857
+ border-radius: 20px;
858
+ padding: 6px 12px;
859
+ font-size: 12px;
860
+ font-weight: 500;
861
+ box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
862
+ }
863
+
864
+ /* Modern Search Input */
865
+ .activity-search-input {
866
+ border: 1px solid #e5e7eb;
867
+ border-radius: 12px;
868
+ padding: 12px 16px;
869
+ font-size: 14px;
870
+ transition: all 0.2s ease;
871
+ background: #f9fafb;
872
+ }
873
+
874
+ .activity-search-input:focus {
875
+ border-color: #3b82f6;
876
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
877
+ background: #ffffff;
878
+ }
879
+
880
+ .search-btn {
881
+ border-radius: 12px;
882
+ padding: 12px 16px;
883
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8);
884
+ border: none;
885
+ transition: all 0.2s ease;
886
+ }
887
+
888
+ .search-btn:hover {
889
+ background: linear-gradient(135deg, #2563eb, #1e40af);
890
+ transform: translateY(-1px);
891
+ box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
892
+ }
893
+
894
+ /* Combined Search and Sort Controls */
895
+ .search-sort-controls-modern {
896
+ background: #ffffff;
897
+ border: 1px solid #e5e7eb;
898
+ border-radius: 16px;
899
+ padding: 20px 24px;
900
+ margin-bottom: 20px;
901
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
902
+ transition: all 0.2s ease;
903
+ position: relative;
904
+ }
905
+
906
+ .search-sort-controls-modern:hover {
907
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.06);
908
+ border-color: #d1d5db;
909
+ }
910
+
911
+ .search-section {
912
+ min-width: 0; /* Allow flex item to shrink */
913
+ }
914
+
915
+ .sort-section {
916
+ flex-shrink: 0; /* Prevent sort section from shrinking */
917
+ }
918
+
919
+ .sort-label {
920
+ white-space: nowrap;
921
+ font-size: 14px;
922
+ }
923
+
924
+ /* Responsive design for combined controls */
925
+ @media (max-width: 768px) {
926
+ .search-sort-controls-modern .d-flex {
927
+ flex-direction: column;
928
+ gap: 16px !important;
929
+ }
930
+
931
+ .search-section {
932
+ width: 100% !important;
933
+ max-width: none !important;
934
+ flex-shrink: 1 !important;
935
+ }
936
+
937
+ .sort-section {
938
+ justify-content: center;
939
+ width: 100%;
940
+ margin-left: 0 !important;
941
+ }
942
+ }
943
+
944
+ @media (max-width: 576px) {
945
+ .search-section {
946
+ max-width: none !important;
947
+ }
948
+
949
+ .search-icon-wrapper {
950
+ width: 40px;
951
+ height: 40px;
952
+ }
953
+
954
+ .search-icon-wrapper i {
955
+ font-size: 18px;
956
+ }
957
+ }
958
+
959
+ .search-icon-wrapper {
960
+ width: 48px;
961
+ height: 48px;
962
+ background: linear-gradient(135deg, #17a2b8, #138496);
963
+ border-radius: 12px;
964
+ display: flex;
965
+ align-items: center;
966
+ justify-content: center;
967
+ box-shadow: 0 2px 8px rgba(23, 162, 184, 0.3);
968
+ }
969
+
970
+ .search-icon-wrapper i {
971
+ color: white;
972
+ font-size: 20px;
973
+ }
974
+
975
+ .activity-search-input {
976
+ border: 1px solid #ced4da;
977
+ border-radius: 8px 0 0 8px;
978
+ padding: 12px 16px;
979
+ font-size: 14px;
980
+ transition: all 0.3s ease;
981
+ }
982
+
983
+ .activity-search-input:focus {
984
+ border-color: #17a2b8;
985
+ box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.25);
986
+ }
987
+
988
+ .search-btn {
989
+ border-radius: 0;
990
+ border-left: none;
991
+ border-right: none;
992
+ padding: 12px 16px;
993
+ transition: all 0.3s ease;
994
+ background-color: #17a2b8;
995
+ border-color: #17a2b8;
996
+ color: white;
997
+ }
998
+
999
+ .search-btn:hover {
1000
+ background-color: #138496;
1001
+ border-color: #117a8b;
1002
+ color: white;
1003
+ transform: translateY(-1px);
1004
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1005
+ }
1006
+
1007
+ .search-btn:focus {
1008
+ box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
1009
+ }
1010
+
1011
+ .search-clear-btn {
1012
+ border-radius: 0 8px 8px 0;
1013
+ border-left: none;
1014
+ padding: 12px 16px;
1015
+ transition: all 0.3s ease;
1016
+ }
1017
+
1018
+ .search-clear-btn:hover {
1019
+ background-color: #dc3545;
1020
+ border-color: #dc3545;
1021
+ color: white;
1022
+ }
1023
+
1024
+ .search-results-count {
1025
+ display: block;
1026
+ margin-top: 8px;
1027
+ font-size: 12px;
1028
+ font-style: italic;
1029
+ }
575
1030
  </style>
576
1031
  </head>
577
1032
 
@@ -669,7 +1124,7 @@
669
1124
  <div class="alert alert-info mb-3" style="border-left: 4px solid #17a2b8; background-color: #f8f9fa;">
670
1125
  <small class="text-muted">
671
1126
  <i class="bi bi-info-circle me-1"></i>
672
- <strong>Traversal Count Explanation:</strong>
1127
+ <strong>Visit Count Explanation:</strong>
673
1128
  The number after the <i class="bi bi-eye"></i> icon indicates how many times each Activity was visited during testing.
674
1129
  </small>
675
1130
  </div>
@@ -701,7 +1156,54 @@
701
1156
  <h5 class="mb-0 text-success">
702
1157
  <i class="bi bi-check-circle-fill"></i> Tested Activities
703
1158
  </h5>
704
- <span class="badge bg-success">{{ tested_activities|length }} / {{ total_activities_count }}</span>
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>
705
1207
  </div>
706
1208
 
707
1209
  <div class="activities-container">
@@ -711,7 +1213,7 @@
711
1213
  {% for activity in tested_activities %}
712
1214
  <div class="activity-item tested-activity" data-page="1">
713
1215
  <div class="activity-content">
714
- <i class="bi bi-check-circle-fill text-success me-2"></i>
1216
+ <i class="bi bi-check-circle-fill text-success me-2"></i>
715
1217
  <span class="activity-name">{{ activity }}</span>
716
1218
  </div>
717
1219
  {% if activity in activity_count_history %}
@@ -756,21 +1258,68 @@
756
1258
  <h5 class="mb-0 text-primary">
757
1259
  <i class="bi bi-app"></i> All Activities Overview
758
1260
  </h5>
759
- <span class="badge bg-primary">Total: {{ total_activities|length }}</span>
1261
+ <span class="badge bg-primary" style="font-size: 1.0em; font-weight: 500;">Total: {{ total_activities|length }}</span>
760
1262
  </div>
761
1263
 
762
- <div class="activities-container">
763
- <div class="activity-list">
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">
764
1313
  {% if total_activities|length > 0 %}
765
1314
  <div id="all-activities-container">
766
1315
  {% for activity in total_activities %}
767
1316
  <div class="activity-item all-activity" data-page="1">
768
1317
  <div class="activity-content">
769
- {% if activity in tested_activities %}
770
- <i class="bi bi-check-circle-fill text-success me-2"></i>
771
- {% else %}
772
- <i class="bi bi-dash-circle text-secondary me-2"></i>
773
- {% endif %}
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 %}
774
1323
  <span class="activity-name">{{ activity }}</span>
775
1324
  </div>
776
1325
  {% if activity in activity_count_history %}
@@ -833,6 +1382,140 @@
833
1382
  </div>
834
1383
  {% endif %}
835
1384
 
1385
+ <!-- Crash Analysis Section -->
1386
+ {% if crash_events or anr_events %}
1387
+ <div class="section-block">
1388
+ <h2 class="section-title">
1389
+ <i class="bi bi-exclamation-triangle text-danger"></i> Crash Analysis
1390
+ </h2>
1391
+
1392
+ <!-- Detailed Crash Information -->
1393
+ <div class="card">
1394
+ <div class="card-header bg-danger text-white">
1395
+ <div class="d-flex justify-content-between align-items-center">
1396
+ <span><i class="bi bi-bug"></i> Crash & ANR Events</span>
1397
+ <span class="badge bg-light text-dark" style="font-size: 1.1em; font-weight: 600;">{{ (crash_events|length) + (anr_events|length) }} events</span>
1398
+ </div>
1399
+ </div>
1400
+ <div class="card-body">
1401
+ <!-- Event Filter -->
1402
+ <div class="mb-3">
1403
+ <div class="btn-group" role="group" aria-label="Event filter">
1404
+ <input type="radio" class="btn-check" name="event-filter" id="all-events" autocomplete="off" checked>
1405
+ <label class="btn btn-outline-primary" for="all-events">All Events ({{ (crash_events|length) + (anr_events|length) }})</label>
1406
+
1407
+ <input type="radio" class="btn-check" name="event-filter" id="crashes-only" autocomplete="off">
1408
+ <label class="btn btn-outline-danger" for="crashes-only">Crashes Only ({{ crash_events|length }})</label>
1409
+
1410
+ <input type="radio" class="btn-check" name="event-filter" id="anr-only" autocomplete="off">
1411
+ <label class="btn btn-outline-warning" for="anr-only">ANR Only ({{ anr_events|length }})</label>
1412
+ </div>
1413
+ </div>
1414
+
1415
+ <!-- Events Table -->
1416
+ <div class="table-responsive">
1417
+ <table class="table table-custom">
1418
+ <thead>
1419
+ <tr>
1420
+ <th>Type</th>
1421
+ <th>Time</th>
1422
+ <th>Exception</th>
1423
+ <th>Process</th>
1424
+ <th>Details</th>
1425
+ </tr>
1426
+ </thead>
1427
+ <tbody id="crash-events-container">
1428
+ {% for crash in crash_events %}
1429
+ <tr class="event-row" data-type="crash" data-page="1">
1430
+ <td><span class="badge bg-danger">CRASH</span></td>
1431
+ <td>{{ crash.time }}</td>
1432
+ <td>{{ crash.exception_type }}</td>
1433
+ <td>{{ crash.process }}</td>
1434
+ <td>
1435
+ <button class="btn btn-sm btn-outline-primary" type="button"
1436
+ data-bs-toggle="collapse" data-bs-target="#crash-detail-{{ loop.index }}"
1437
+ aria-expanded="false" aria-controls="crash-detail-{{ loop.index }}">
1438
+ <i class="bi bi-eye"></i> Details
1439
+ </button>
1440
+ <button class="btn btn-sm btn-outline-secondary copy-stack-btn"
1441
+ data-stack-index="{{ loop.index }}">
1442
+ <i class="bi bi-clipboard"></i> Copy
1443
+ </button>
1444
+ </td>
1445
+ </tr>
1446
+ <tr class="collapse" id="crash-detail-{{ loop.index }}">
1447
+ <td colspan="5">
1448
+ <div class="bg-light p-3 rounded">
1449
+ <h6 class="text-danger">Stack Trace:</h6>
1450
+ <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>
1451
+ </div>
1452
+ </td>
1453
+ </tr>
1454
+ {% endfor %}
1455
+
1456
+ {% for anr in anr_events %}
1457
+ <tr class="event-row" data-type="anr" data-page="1">
1458
+ <td><span class="badge bg-warning text-dark">ANR</span></td>
1459
+ <td>{{ anr.time }}</td>
1460
+ <td>{{ anr.reason }}</td>
1461
+ <td>{{ anr.process }}</td>
1462
+ <td>
1463
+ <button class="btn btn-sm btn-outline-primary" type="button"
1464
+ data-bs-toggle="collapse" data-bs-target="#anr-detail-{{ loop.index }}"
1465
+ aria-expanded="false" aria-controls="anr-detail-{{ loop.index }}">
1466
+ <i class="bi bi-eye"></i> Details
1467
+ </button>
1468
+ <button class="btn btn-sm btn-outline-secondary copy-stack-btn"
1469
+ data-stack-index="anr-{{ loop.index }}">
1470
+ <i class="bi bi-clipboard"></i> Copy
1471
+ </button>
1472
+ </td>
1473
+ </tr>
1474
+ <tr class="collapse" id="anr-detail-{{ loop.index }}">
1475
+ <td colspan="5">
1476
+ <div class="bg-light p-3 rounded">
1477
+ <h6 class="text-dark">ANR Details:</h6>
1478
+ <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>
1479
+ </div>
1480
+ </td>
1481
+ </tr>
1482
+ {% endfor %}
1483
+ </tbody>
1484
+ </table>
1485
+ </div>
1486
+
1487
+ <!-- Pagination for Crash Events -->
1488
+ <div class="pagination-container d-flex justify-content-between align-items-center mt-3">
1489
+ <div class="d-flex align-items-center">
1490
+ <label for="events-page-size" class="form-label me-2 mb-0">Show:</label>
1491
+ <select class="form-select form-select-sm" id="events-page-size" style="width: auto;">
1492
+ <option value="5">5</option>
1493
+ <option value="10" selected>10</option>
1494
+ <option value="20">20</option>
1495
+ <option value="50">50</option>
1496
+ <option value="100">100</option>
1497
+ </select>
1498
+ </div>
1499
+ <nav aria-label="Crash Events Pagination">
1500
+ <ul class="pagination pagination-sm mb-0" id="events-pagination">
1501
+ <!-- Pagination will be generated by JavaScript -->
1502
+ </ul>
1503
+ </nav>
1504
+ </div>
1505
+ </div>
1506
+ </div>
1507
+ </div>
1508
+ {% else %}
1509
+ <div class="section-block">
1510
+ <h2 class="section-title">
1511
+ <i class="bi bi-exclamation-triangle text-danger"></i> Crash Analysis
1512
+ </h2>
1513
+ <div class="alert alert-info text-center">
1514
+ <i class="bi bi-info-circle"></i> No crash or ANR events detected in this test session.
1515
+ </div>
1516
+ </div>
1517
+ {% endif %}
1518
+
836
1519
  <!-- Property Violations List -->
837
1520
  {% if take_screenshots %}
838
1521
  <div class="section-block">
@@ -887,6 +1570,35 @@
887
1570
  <div class="section-block">
888
1571
  <h2 class="section-title">Property Checking Statistics</h2>
889
1572
 
1573
+ <!-- Search Controls for Property Statistics -->
1574
+ <div class="search-controls-modern mb-4">
1575
+ <div class="d-flex align-items-center">
1576
+ <div class="search-icon-wrapper me-3">
1577
+ <i class="bi bi-search"></i>
1578
+ </div>
1579
+ <div class="flex-grow-1">
1580
+ <div class="input-group">
1581
+ <input type="text" class="form-control property-search-input"
1582
+ id="property-stats-search"
1583
+ placeholder="Search properties by name..."
1584
+ data-target="property-stats-container"
1585
+ data-item-class="property-stat-row"
1586
+ data-pagination="stats-pagination"
1587
+ data-page-size="stats-page-size">
1588
+ <button class="btn btn-primary search-btn" type="button"
1589
+ data-target="property-stats-search">
1590
+ <i class="bi bi-search"></i>
1591
+ </button>
1592
+ <button class="btn btn-outline-secondary search-clear-btn" type="button"
1593
+ data-target="property-stats-search">
1594
+ <i class="bi bi-x-lg"></i>
1595
+ </button>
1596
+ </div>
1597
+ <small class="text-muted search-results-count" id="property-search-results"></small>
1598
+ </div>
1599
+ </div>
1600
+ </div>
1601
+
890
1602
  <div class="table-responsive">
891
1603
  <table class="table table-custom">
892
1604
  <thead>
@@ -1376,10 +2088,28 @@
1376
2088
  }
1377
2089
  });
1378
2090
 
2091
+ // Draw crash timeline chart
2092
+ var crashTimelineData = {{ crash_timeline_data|default('{}')|safe }};
2093
+ if (crashTimelineData && Object.keys(crashTimelineData).length > 0) {
2094
+ drawCrashTimelineChart(crashTimelineData);
2095
+ }
2096
+
2097
+ // Initialize crash events functionality
2098
+ initCrashAnalysis();
2099
+
1379
2100
  // Initialize pagination for Activities lists
1380
2101
  initPagination('tested-activities-container', 'tested-activity', 'tested-pagination', 'tested-page-size');
1381
2102
  initPagination('all-activities-container', 'all-activity', 'all-pagination', 'all-page-size');
1382
2103
 
2104
+ // Initialize activity sorting
2105
+ initActivitySorting();
2106
+
2107
+ // Initialize activity searching
2108
+ initActivitySearching();
2109
+
2110
+ // Initialize property statistics searching
2111
+ initPropertySearching();
2112
+
1383
2113
  // Initialize pagination for Property tables
1384
2114
  initPagination('property-violations-container', 'property-violation-row', 'violations-pagination', 'violations-page-size');
1385
2115
  initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', 'stats-page-size');
@@ -1387,6 +2117,77 @@
1387
2117
  // Initialize sorting for Property Checking Statistics
1388
2118
  initSorting();
1389
2119
 
2120
+ // Activity sorting function
2121
+ function initActivitySorting() {
2122
+ const sortButtons = document.querySelectorAll('.activity-sort-btn');
2123
+
2124
+ sortButtons.forEach(function(button) {
2125
+ button.addEventListener('click', function() {
2126
+ const sortType = this.dataset.sort;
2127
+ const currentOrder = this.dataset.order;
2128
+ const parentTab = this.closest('.tab-pane');
2129
+ const isTestedTab = parentTab.id === 'tested-activities';
2130
+
2131
+ // Toggle sort order
2132
+ const newOrder = currentOrder === 'asc' ? 'desc' : 'asc';
2133
+
2134
+ this.dataset.order = newOrder;
2135
+ const icon = this.querySelector('.sort-icon');
2136
+
2137
+ if (newOrder === 'asc') {
2138
+ icon.className = 'bi bi-arrow-up sort-icon btn-arrow';
2139
+ } else {
2140
+ icon.className = 'bi bi-arrow-down sort-icon btn-arrow';
2141
+ }
2142
+
2143
+ // Sort activities
2144
+ sortActivities(sortType, newOrder, isTestedTab);
2145
+ });
2146
+ });
2147
+
2148
+ function sortActivities(sortType, order, isTestedTab) {
2149
+ const containerId = isTestedTab ? 'tested-activities-container' : 'all-activities-container';
2150
+ const itemClass = isTestedTab ? 'tested-activity' : 'all-activity';
2151
+ const paginationId = isTestedTab ? 'tested-pagination' : 'all-pagination';
2152
+ const pageSizeSelectId = isTestedTab ? 'tested-page-size' : 'all-page-size';
2153
+
2154
+ const container = document.getElementById(containerId);
2155
+ const items = Array.from(container.getElementsByClassName(itemClass));
2156
+
2157
+ items.sort(function(a, b) {
2158
+ const badgeA = a.querySelector('.traversal-badge');
2159
+ const badgeB = b.querySelector('.traversal-badge');
2160
+
2161
+ // Extract traversal count from badge text like "👁 5 times"
2162
+ const valueA = badgeA ? parseInt(badgeA.textContent.match(/\d+/)[0]) || 0 : 0;
2163
+ const valueB = badgeB ? parseInt(badgeB.textContent.match(/\d+/)[0]) || 0 : 0;
2164
+
2165
+ if (order === 'asc') {
2166
+ return valueA - valueB;
2167
+ } else {
2168
+ return valueB - valueA;
2169
+ }
2170
+ });
2171
+
2172
+ // Clear container and append sorted items
2173
+ container.innerHTML = '';
2174
+ items.forEach(function(item) {
2175
+ container.appendChild(item);
2176
+ });
2177
+
2178
+ // Check if there's an active search and reapply it
2179
+ const searchInputId = isTestedTab ? 'tested-activity-search' : 'all-activity-search';
2180
+ const searchInput = document.getElementById(searchInputId);
2181
+ if (searchInput && searchInput.value.trim() !== '') {
2182
+ // Reapply search filter after sorting
2183
+ performActivitySearch(searchInput);
2184
+ } else {
2185
+ // No active search, just re-initialize pagination
2186
+ initPagination(containerId, itemClass, paginationId, pageSizeSelectId);
2187
+ }
2188
+ }
2189
+ }
2190
+
1390
2191
  // Simplified sorting function for Fails and Errors columns
1391
2192
  function initSorting() {
1392
2193
  const sortIcons = document.querySelectorAll('.sort-icon');
@@ -1458,9 +2259,24 @@
1458
2259
  // Function to handle page navigation
1459
2260
  function goToPage(pageNumber, containerId, itemClass, itemsPerPage, paginationId) {
1460
2261
  const container = document.getElementById(containerId);
1461
- const items = container.getElementsByClassName(itemClass);
2262
+ const allItems = Array.from(container.getElementsByClassName(itemClass));
1462
2263
  const paginationElement = document.getElementById(paginationId);
1463
2264
 
2265
+ console.log('goToPage called with:', {pageNumber, containerId, itemClass, itemsPerPage, paginationId});
2266
+ console.log('Total items found:', allItems.length);
2267
+
2268
+ // Get items that should be visible based on search filter
2269
+ const filteredItems = allItems.filter(item => {
2270
+ const searchVisible = item.getAttribute('data-search-visible');
2271
+ const shouldShow = searchVisible === null || searchVisible === 'true';
2272
+ return shouldShow;
2273
+ });
2274
+
2275
+ console.log('Filtered items count:', filteredItems.length);
2276
+ console.log('Items with search-visible=true:', allItems.filter(item => item.getAttribute('data-search-visible') === 'true').length);
2277
+ console.log('Items with search-visible=false:', allItems.filter(item => item.getAttribute('data-search-visible') === 'false').length);
2278
+ console.log('Items with search-visible=null:', allItems.filter(item => item.getAttribute('data-search-visible') === null).length);
2279
+
1464
2280
  // Update pagination active state
1465
2281
  if (paginationElement) {
1466
2282
  const pageItems = paginationElement.getElementsByClassName('page-item');
@@ -1471,33 +2287,28 @@
1471
2287
  pageItems[i].className = 'page-item';
1472
2288
  }
1473
2289
  }
1474
-
1475
- // Update prev/next buttons
1476
- if (pageNumber === 1) {
1477
- paginationElement.firstChild.className = 'page-item disabled';
1478
- } else {
1479
- paginationElement.firstChild.className = 'page-item';
1480
- }
1481
-
1482
- const totalPages = Math.ceil(items.length / itemsPerPage);
1483
- if (pageNumber === totalPages) {
1484
- paginationElement.lastChild.className = 'page-item disabled';
1485
- } else {
1486
- paginationElement.lastChild.className = 'page-item';
1487
- }
1488
2290
  }
1489
2291
 
1490
- // Show/hide items based on page number
2292
+ // Hide all items first
2293
+ console.log('Hiding all items...');
2294
+ allItems.forEach((item, index) => {
2295
+ item.style.display = 'none';
2296
+ });
2297
+
2298
+ // Show items for current page (only from filtered items)
1491
2299
  const startIndex = (pageNumber - 1) * itemsPerPage;
1492
- const endIndex = Math.min(startIndex + itemsPerPage, items.length);
2300
+ const endIndex = Math.min(startIndex + itemsPerPage, filteredItems.length);
1493
2301
 
1494
- for (let i = 0; i < items.length; i++) {
1495
- if (i >= startIndex && i < endIndex) {
1496
- items[i].style.display = '';
1497
- } else {
1498
- items[i].style.display = 'none';
2302
+ console.log('Showing items from index', startIndex, 'to', endIndex - 1);
2303
+
2304
+ for (let i = startIndex; i < endIndex; i++) {
2305
+ if (filteredItems[i]) {
2306
+ filteredItems[i].style.display = '';
2307
+ console.log('Showing item', i, ':', filteredItems[i].querySelector('.activity-name')?.textContent);
1499
2308
  }
1500
2309
  }
2310
+
2311
+ console.log('goToPage completed');
1501
2312
  }
1502
2313
 
1503
2314
  // Pagination function
@@ -1506,22 +2317,33 @@
1506
2317
  const pageSizeSelect = document.getElementById(pageSizeSelectId);
1507
2318
  if (!container) return;
1508
2319
 
1509
- // Get initial page size
1510
- let itemsPerPage = pageSizeSelect ? parseInt(pageSizeSelect.value) : 10;
1511
-
1512
- // Add event listener for page size changes
1513
- if (pageSizeSelect) {
2320
+ // Remove existing event listener to prevent duplicate bindings
2321
+ if (pageSizeSelect && !pageSizeSelect.hasAttribute('data-listener-bound')) {
1514
2322
  pageSizeSelect.addEventListener('change', function() {
1515
- itemsPerPage = parseInt(this.value);
2323
+ const newItemsPerPage = parseInt(this.value);
1516
2324
  renderPagination();
1517
- goToPage(1, containerId, itemClass, itemsPerPage, paginationId);
2325
+ goToPage(1, containerId, itemClass, newItemsPerPage, paginationId);
1518
2326
  });
2327
+ // Mark as having listener bound
2328
+ pageSizeSelect.setAttribute('data-listener-bound', 'true');
1519
2329
  }
1520
2330
 
1521
2331
  function renderPagination() {
1522
- const items = container.getElementsByClassName(itemClass);
1523
- const totalItems = items.length;
1524
- const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage));
2332
+ const allItems = Array.from(container.getElementsByClassName(itemClass));
2333
+
2334
+ // Always get current page size from select element
2335
+ const currentPageSizeSelect = document.getElementById(pageSizeSelectId);
2336
+ const currentItemsPerPage = currentPageSizeSelect ? parseInt(currentPageSizeSelect.value) : 10;
2337
+
2338
+ // Use same filtering logic as goToPage function
2339
+ const filteredItems = allItems.filter(item => {
2340
+ const searchVisible = item.getAttribute('data-search-visible');
2341
+ // If no search filter is applied, show all items
2342
+ return searchVisible === null || searchVisible === 'true';
2343
+ });
2344
+
2345
+ const totalItems = filteredItems.length;
2346
+ const totalPages = Math.max(1, Math.ceil(totalItems / currentItemsPerPage));
1525
2347
 
1526
2348
  // Create pagination
1527
2349
  const paginationElement = document.getElementById(paginationId);
@@ -1530,6 +2352,22 @@
1530
2352
  // Clear pagination
1531
2353
  paginationElement.innerHTML = '';
1532
2354
 
2355
+ // Don't show pagination if there's only one page or no items
2356
+ if (totalPages <= 1) {
2357
+ // Hide the entire pagination container when not needed
2358
+ const paginationContainer = paginationElement.closest('.pagination-container');
2359
+ if (paginationContainer) {
2360
+ paginationContainer.style.display = 'none';
2361
+ }
2362
+ return;
2363
+ }
2364
+
2365
+ // Show the pagination container when needed
2366
+ const paginationContainer = paginationElement.closest('.pagination-container');
2367
+ if (paginationContainer) {
2368
+ paginationContainer.style.display = '';
2369
+ }
2370
+
1533
2371
  // Previous button
1534
2372
  const prevLi = document.createElement('li');
1535
2373
  prevLi.className = 'page-item disabled';
@@ -1543,7 +2381,7 @@
1543
2381
  pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
1544
2382
  pageLi.addEventListener('click', function(e) {
1545
2383
  e.preventDefault();
1546
- goToPage(i, containerId, itemClass, itemsPerPage, paginationId);
2384
+ goToPage(i, containerId, itemClass, currentItemsPerPage, paginationId);
1547
2385
  });
1548
2386
  paginationElement.appendChild(pageLi);
1549
2387
  }
@@ -1562,7 +2400,7 @@
1562
2400
  if (activePage && activePage.previousElementSibling && activePage.previousElementSibling.previousElementSibling) {
1563
2401
  const pageNum = parseInt(activePage.textContent) - 1;
1564
2402
  if (pageNum >= 1) {
1565
- goToPage(pageNum, containerId, itemClass, itemsPerPage, paginationId);
2403
+ goToPage(pageNum, containerId, itemClass, currentItemsPerPage, paginationId);
1566
2404
  }
1567
2405
  }
1568
2406
  });
@@ -1574,9 +2412,8 @@
1574
2412
  const activePage = paginationElement.querySelector('.active');
1575
2413
  if (activePage && activePage.nextElementSibling && activePage.nextElementSibling.nextElementSibling) {
1576
2414
  const pageNum = parseInt(activePage.textContent) + 1;
1577
- const totalPages = Math.ceil(container.getElementsByClassName(itemClass).length / itemsPerPage);
1578
2415
  if (pageNum <= totalPages) {
1579
- goToPage(pageNum, containerId, itemClass, itemsPerPage, paginationId);
2416
+ goToPage(pageNum, containerId, itemClass, currentItemsPerPage, paginationId);
1580
2417
  }
1581
2418
  }
1582
2419
  });
@@ -1585,7 +2422,9 @@
1585
2422
 
1586
2423
  // Initial render
1587
2424
  renderPagination();
1588
- goToPage(1, containerId, itemClass, itemsPerPage, paginationId);
2425
+ // Always call goToPage to ensure items are displayed correctly, even if no pagination is shown
2426
+ const currentItemsPerPage = pageSizeSelect ? parseInt(pageSizeSelect.value) : 10;
2427
+ goToPage(1, containerId, itemClass, currentItemsPerPage, paginationId);
1589
2428
  }
1590
2429
 
1591
2430
  // Scroll functionality
@@ -1648,7 +2487,683 @@
1648
2487
 
1649
2488
  // Call beautify function after DOM is ready
1650
2489
  beautifyScreenshotCaptions();
2490
+
2491
+ // Crash analysis functions
2492
+ function initCrashAnalysis() {
2493
+ // Initialize event filtering
2494
+ var filterButtons = document.querySelectorAll('input[name="event-filter"]');
2495
+ filterButtons.forEach(function(button) {
2496
+ button.addEventListener('change', function() {
2497
+ filterCrashEvents(this.id);
2498
+ });
2499
+ });
2500
+
2501
+ // Initialize copy buttons for stack traces
2502
+ var copyButtons = document.querySelectorAll('.copy-stack-btn');
2503
+ copyButtons.forEach(function(button) {
2504
+ button.addEventListener('click', function() {
2505
+ var stackIndex = this.dataset.stackIndex;
2506
+ copyStackTrace(stackIndex);
2507
+ });
2508
+ });
2509
+
2510
+ // Initialize page size selector for crash events
2511
+ var pageSizeSelect = document.getElementById('events-page-size');
2512
+ if (pageSizeSelect) {
2513
+ pageSizeSelect.addEventListener('change', function() {
2514
+ updateCrashEventsPagination();
2515
+ });
2516
+ }
2517
+
2518
+ // Initialize pagination for crash events
2519
+ updateCrashEventsPagination();
2520
+ }
2521
+
2522
+ function filterCrashEvents(filterType) {
2523
+ // Simply update pagination, which will handle filtering and display
2524
+ updateCrashEventsPagination();
2525
+ }
2526
+
2527
+ function updateCrashEventsPagination() {
2528
+ var container = document.getElementById('crash-events-container');
2529
+ var pagination = document.getElementById('events-pagination');
2530
+ var pageSizeSelect = document.getElementById('events-page-size');
2531
+
2532
+ if (!container || !pagination || !pageSizeSelect) return;
2533
+
2534
+ // Get all rows and determine which should be visible based on current filter
2535
+ var allRows = Array.from(container.querySelectorAll('.event-row'));
2536
+ var currentFilter = getCurrentEventFilter();
2537
+
2538
+ var visibleRows = allRows.filter(function(row) {
2539
+ var rowType = row.dataset.type;
2540
+ switch(currentFilter) {
2541
+ case 'all-events':
2542
+ return true;
2543
+ case 'crashes-only':
2544
+ return rowType === 'crash';
2545
+ case 'anr-only':
2546
+ return rowType === 'anr';
2547
+ default:
2548
+ return true;
2549
+ }
2550
+ });
2551
+
2552
+ var pageSize = parseInt(pageSizeSelect.value) || 10;
2553
+ var totalPages = Math.ceil(visibleRows.length / pageSize);
2554
+ var currentPage = 1;
2555
+
2556
+ // Hide all rows first
2557
+ allRows.forEach(function(row) {
2558
+ row.style.display = 'none';
2559
+ var detailRow = row.nextElementSibling;
2560
+ if (detailRow && detailRow.classList.contains('collapse')) {
2561
+ detailRow.style.display = 'none';
2562
+ }
2563
+ });
2564
+
2565
+ // Show rows for current page
2566
+ var startIndex = (currentPage - 1) * pageSize;
2567
+ var endIndex = Math.min(startIndex + pageSize, visibleRows.length);
2568
+
2569
+ for (var i = startIndex; i < endIndex; i++) {
2570
+ visibleRows[i].style.display = '';
2571
+ var detailRow = visibleRows[i].nextElementSibling;
2572
+ if (detailRow && detailRow.classList.contains('collapse')) {
2573
+ detailRow.style.display = '';
2574
+ }
2575
+ }
2576
+
2577
+ // Store current state for pagination callback
2578
+ window.crashEventsState = {
2579
+ visibleRows: visibleRows,
2580
+ pageSize: pageSize,
2581
+ totalPages: totalPages
2582
+ };
2583
+
2584
+ // Update pagination controls
2585
+ updatePaginationControls('events-pagination', currentPage, totalPages, function(page) {
2586
+ showCrashEventsPage(page, window.crashEventsState.visibleRows, window.crashEventsState.pageSize);
2587
+ });
2588
+ }
2589
+
2590
+ function showCrashEventsPage(page, visibleRows, pageSize) {
2591
+ // Hide all visible rows
2592
+ visibleRows.forEach(function(row) {
2593
+ row.style.display = 'none';
2594
+ var detailRow = row.nextElementSibling;
2595
+ if (detailRow && detailRow.classList.contains('collapse')) {
2596
+ detailRow.style.display = 'none';
2597
+ }
2598
+ });
2599
+
2600
+ // Show rows for the specified page
2601
+ var startIndex = (page - 1) * pageSize;
2602
+ var endIndex = Math.min(startIndex + pageSize, visibleRows.length);
2603
+
2604
+ for (var i = startIndex; i < endIndex; i++) {
2605
+ visibleRows[i].style.display = '';
2606
+ var detailRow = visibleRows[i].nextElementSibling;
2607
+ if (detailRow && detailRow.classList.contains('collapse')) {
2608
+ detailRow.style.display = '';
2609
+ }
2610
+ }
2611
+
2612
+ // Update pagination controls to reflect current page
2613
+ var totalPages = Math.ceil(visibleRows.length / pageSize);
2614
+ var pagination = document.getElementById('events-pagination');
2615
+ if (pagination) {
2616
+ // Update active page
2617
+ var pageItems = pagination.querySelectorAll('.page-item');
2618
+ pageItems.forEach(function(item, index) {
2619
+ // Skip first (previous) and last (next) buttons
2620
+ if (index > 0 && index < pageItems.length - 1) {
2621
+ var pageNum = parseInt(item.textContent);
2622
+ if (pageNum === page) {
2623
+ item.classList.add('active');
2624
+ } else {
2625
+ item.classList.remove('active');
2626
+ }
2627
+ }
2628
+ });
2629
+
2630
+ // Update previous button state
2631
+ if (pageItems.length > 0) {
2632
+ var prevButton = pageItems[0];
2633
+ if (page === 1) {
2634
+ prevButton.classList.add('disabled');
2635
+ } else {
2636
+ prevButton.classList.remove('disabled');
2637
+ }
2638
+ }
2639
+
2640
+ // Update next button state
2641
+ if (pageItems.length > 1) {
2642
+ var nextButton = pageItems[pageItems.length - 1];
2643
+ if (page === totalPages) {
2644
+ nextButton.classList.add('disabled');
2645
+ } else {
2646
+ nextButton.classList.remove('disabled');
2647
+ }
2648
+ }
2649
+ }
2650
+ }
2651
+
2652
+ function getCurrentEventFilter() {
2653
+ var filterButtons = document.querySelectorAll('input[name="event-filter"]');
2654
+ for (var i = 0; i < filterButtons.length; i++) {
2655
+ if (filterButtons[i].checked) {
2656
+ return filterButtons[i].id;
2657
+ }
2658
+ }
2659
+ return 'all-events'; // default
2660
+ }
2661
+
2662
+ function updatePaginationControls(paginationId, currentPage, totalPages, onPageClick) {
2663
+ var pagination = document.getElementById(paginationId);
2664
+ if (!pagination) return;
2665
+
2666
+ pagination.innerHTML = '';
2667
+
2668
+ if (totalPages <= 1) {
2669
+ // Hide the entire pagination container when not needed
2670
+ var paginationContainer = pagination.closest('.pagination-container');
2671
+ if (paginationContainer) {
2672
+ paginationContainer.style.display = 'none';
2673
+ }
2674
+ return;
2675
+ }
2676
+
2677
+ // Show the pagination container when needed
2678
+ var paginationContainer = pagination.closest('.pagination-container');
2679
+ if (paginationContainer) {
2680
+ paginationContainer.style.display = '';
2681
+ }
2682
+
2683
+ // Previous button
2684
+ var prevLi = document.createElement('li');
2685
+ prevLi.className = 'page-item' + (currentPage === 1 ? ' disabled' : '');
2686
+ var prevLink = document.createElement('a');
2687
+ prevLink.className = 'page-link';
2688
+ prevLink.href = '#';
2689
+ prevLink.setAttribute('aria-label', 'Previous');
2690
+ prevLink.innerHTML = '<span aria-hidden="true">&laquo;</span>';
2691
+
2692
+ prevLink.addEventListener('click', function(e) {
2693
+ e.preventDefault();
2694
+ e.stopPropagation();
2695
+ // Get current page dynamically to avoid stale closure values
2696
+ var currentActivePage = getCurrentPageNumber(paginationId);
2697
+ if (currentActivePage > 1 && !prevLi.classList.contains('disabled')) {
2698
+ onPageClick(currentActivePage - 1);
2699
+ }
2700
+ });
2701
+
2702
+ prevLi.appendChild(prevLink);
2703
+ pagination.appendChild(prevLi);
2704
+
2705
+ // Page numbers
2706
+ for (var i = 1; i <= totalPages; i++) {
2707
+ var li = document.createElement('li');
2708
+ li.className = 'page-item' + (i === currentPage ? ' active' : '');
2709
+ var link = document.createElement('a');
2710
+ link.className = 'page-link';
2711
+ link.href = '#';
2712
+ link.textContent = i;
2713
+ link.addEventListener('click', (function(page) {
2714
+ return function(e) {
2715
+ e.preventDefault();
2716
+ e.stopPropagation();
2717
+ onPageClick(page);
2718
+ };
2719
+ })(i));
2720
+ li.appendChild(link);
2721
+ pagination.appendChild(li);
2722
+ }
2723
+
2724
+ // Next button
2725
+ var nextLi = document.createElement('li');
2726
+ nextLi.className = 'page-item' + (currentPage === totalPages ? ' disabled' : '');
2727
+ var nextLink = document.createElement('a');
2728
+ nextLink.className = 'page-link';
2729
+ nextLink.href = '#';
2730
+ nextLink.setAttribute('aria-label', 'Next');
2731
+ nextLink.innerHTML = '<span aria-hidden="true">&raquo;</span>';
2732
+
2733
+ nextLink.addEventListener('click', function(e) {
2734
+ e.preventDefault();
2735
+ e.stopPropagation();
2736
+ // Get current page dynamically to avoid stale closure values
2737
+ var currentActivePage = getCurrentPageNumber(paginationId);
2738
+ if (currentActivePage < totalPages && !nextLi.classList.contains('disabled')) {
2739
+ onPageClick(currentActivePage + 1);
2740
+ }
2741
+ });
2742
+
2743
+ nextLi.appendChild(nextLink);
2744
+ pagination.appendChild(nextLi);
2745
+ }
2746
+
2747
+ function getCurrentPageNumber(paginationId) {
2748
+ var pagination = document.getElementById(paginationId);
2749
+ if (!pagination) return 1;
2750
+
2751
+ var activeItem = pagination.querySelector('.page-item.active');
2752
+ if (activeItem) {
2753
+ var pageText = activeItem.textContent.trim();
2754
+ var pageNum = parseInt(pageText);
2755
+ return isNaN(pageNum) ? 1 : pageNum;
2756
+ }
2757
+ return 1;
2758
+ }
2759
+
2760
+ function copyStackTrace(index) {
2761
+ var stackTraceElement = document.getElementById('stack-trace-' + index);
2762
+ if (!stackTraceElement) return;
2763
+
2764
+ var stackTrace = stackTraceElement.textContent;
2765
+
2766
+ if (navigator.clipboard && navigator.clipboard.writeText) {
2767
+ navigator.clipboard.writeText(stackTrace).then(function() {
2768
+ showCopyNotification('Stack trace copied to clipboard!');
2769
+ }).catch(function(err) {
2770
+ console.error('Failed to copy stack trace: ', err);
2771
+ showCopyNotification('Failed to copy stack trace', 'error');
2772
+ });
2773
+ } else {
2774
+ // Fallback for older browsers
2775
+ var textArea = document.createElement('textarea');
2776
+ textArea.value = stackTrace;
2777
+ document.body.appendChild(textArea);
2778
+ textArea.select();
2779
+ try {
2780
+ document.execCommand('copy');
2781
+ showCopyNotification('Stack trace copied to clipboard!');
2782
+ } catch (err) {
2783
+ console.error('Fallback copy failed: ', err);
2784
+ showCopyNotification('Failed to copy stack trace', 'error');
2785
+ }
2786
+ document.body.removeChild(textArea);
2787
+ }
2788
+ }
2789
+
2790
+ function showCopyNotification(message, type) {
2791
+ type = type || 'success';
2792
+
2793
+ // Create notification element
2794
+ var notification = document.createElement('div');
2795
+ notification.className = 'alert alert-' + (type === 'error' ? 'danger' : 'success') + ' position-fixed';
2796
+ notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
2797
+ notification.innerHTML = '<i class="bi bi-' + (type === 'error' ? 'exclamation-triangle' : 'check-circle') + '"></i> ' + message;
2798
+
2799
+ document.body.appendChild(notification);
2800
+
2801
+ // Auto remove after 3 seconds
2802
+ setTimeout(function() {
2803
+ if (notification.parentNode) {
2804
+ notification.parentNode.removeChild(notification);
2805
+ }
2806
+ }, 3000);
2807
+ }
1651
2808
  });
2809
+
2810
+ // Global activity search functions
2811
+ function performActivitySearch(searchInput) {
2812
+ const searchTerm = searchInput.value.toLowerCase().trim();
2813
+ const containerId = searchInput.dataset.target;
2814
+ const itemClass = searchInput.dataset.itemClass;
2815
+ const paginationId = searchInput.dataset.pagination;
2816
+ const pageSizeSelectId = searchInput.dataset.pageSize;
2817
+
2818
+ const container = document.getElementById(containerId);
2819
+ const items = Array.from(container.getElementsByClassName(itemClass));
2820
+
2821
+ // Filter and count matching items
2822
+ let visibleCount = 0;
2823
+ let totalCount = items.length;
2824
+
2825
+ items.forEach(function(item, index) {
2826
+ const activityName = item.querySelector('.activity-name');
2827
+ if (activityName) {
2828
+ const text = activityName.textContent.toLowerCase();
2829
+ const matches = searchTerm === '' || text.includes(searchTerm);
2830
+
2831
+ if (matches) {
2832
+ item.setAttribute('data-search-visible', 'true');
2833
+ visibleCount++;
2834
+ } else {
2835
+ item.setAttribute('data-search-visible', 'false');
2836
+ }
2837
+ } else {
2838
+ // If no activity name found, hide the item
2839
+ item.setAttribute('data-search-visible', 'false');
2840
+ }
2841
+ });
2842
+
2843
+ // Update search results count
2844
+ updateSearchResultsCount(searchInput, visibleCount, totalCount, searchTerm);
2845
+
2846
+ // Re-initialize pagination and go to first page
2847
+ initPagination(containerId, itemClass, paginationId, pageSizeSelectId);
2848
+
2849
+ // Always go to page 1 after search
2850
+ const currentPageSize = document.getElementById(pageSizeSelectId) ?
2851
+ parseInt(document.getElementById(pageSizeSelectId).value) : 10;
2852
+
2853
+ goToPage(1, containerId, itemClass, currentPageSize, paginationId);
2854
+ }
2855
+
2856
+ function updateSearchResultsCount(searchInput, visibleCount, totalCount, searchTerm) {
2857
+ const isTestedTab = searchInput.id.includes('tested');
2858
+ const resultsElementId = isTestedTab ? 'tested-search-results' : 'all-search-results';
2859
+ const resultsElement = document.getElementById(resultsElementId);
2860
+
2861
+ if (resultsElement) {
2862
+ if (searchTerm === '') {
2863
+ resultsElement.innerHTML = `<span class="text-muted">Showing all ${totalCount} activities - Type and click <i class="bi bi-search"></i> or press Enter to search</span>`;
2864
+ } else if (visibleCount === 0) {
2865
+ resultsElement.innerHTML = `<span class="text-danger">No activities found for "${searchTerm}"</span>`;
2866
+ } else if (visibleCount === totalCount) {
2867
+ resultsElement.innerHTML = `<span class="text-success">All ${totalCount} activities match "${searchTerm}"</span>`;
2868
+ } else {
2869
+ resultsElement.innerHTML = `<span class="text-info">Found ${visibleCount} of ${totalCount} activities for "${searchTerm}"</span>`;
2870
+ }
2871
+ }
2872
+ }
2873
+
2874
+ // Activity searching function
2875
+ function initActivitySearching() {
2876
+ const searchInputs = document.querySelectorAll('.activity-search-input');
2877
+ const searchButtons = document.querySelectorAll('.search-btn');
2878
+ const clearButtons = document.querySelectorAll('.search-clear-btn');
2879
+
2880
+ // Initialize search functionality for each input
2881
+ searchInputs.forEach(function(input) {
2882
+ // Search on Enter key
2883
+ input.addEventListener('keydown', function(e) {
2884
+ if (e.key === 'Enter') {
2885
+ e.preventDefault();
2886
+ performActivitySearch(this);
2887
+ } else if (e.key === 'Escape') {
2888
+ clearActivitySearch(this);
2889
+ }
2890
+ });
2891
+ });
2892
+
2893
+ // Initialize search button functionality
2894
+ searchButtons.forEach(function(button) {
2895
+ button.addEventListener('click', function() {
2896
+ const targetInputId = this.dataset.target;
2897
+ const targetInput = document.getElementById(targetInputId);
2898
+ if (targetInput) {
2899
+ performActivitySearch(targetInput);
2900
+ }
2901
+ });
2902
+ });
2903
+
2904
+ // Initialize clear button functionality
2905
+ clearButtons.forEach(function(button) {
2906
+ button.addEventListener('click', function() {
2907
+ const targetInputId = this.dataset.target;
2908
+ const targetInput = document.getElementById(targetInputId);
2909
+ if (targetInput) {
2910
+ clearActivitySearch(targetInput);
2911
+ }
2912
+ });
2913
+ });
2914
+
2915
+ function clearActivitySearch(searchInput) {
2916
+ searchInput.value = '';
2917
+ performActivitySearch(searchInput);
2918
+ searchInput.focus();
2919
+ }
2920
+ }
2921
+
2922
+ // Property statistics searching function
2923
+ function initPropertySearching() {
2924
+ const searchInput = document.getElementById('property-stats-search');
2925
+ const searchButton = document.querySelector('.search-btn[data-target="property-stats-search"]');
2926
+ const clearButton = document.querySelector('[data-target="property-stats-search"].search-clear-btn');
2927
+
2928
+ if (!searchInput) return;
2929
+
2930
+ // Search on Enter key
2931
+ searchInput.addEventListener('keydown', function(e) {
2932
+ if (e.key === 'Enter') {
2933
+ e.preventDefault();
2934
+ performPropertySearch(this);
2935
+ } else if (e.key === 'Escape') {
2936
+ clearPropertySearch(this);
2937
+ }
2938
+ });
2939
+
2940
+ // Search button functionality
2941
+ if (searchButton) {
2942
+ searchButton.addEventListener('click', function() {
2943
+ performPropertySearch(searchInput);
2944
+ });
2945
+ }
2946
+
2947
+ // Clear button functionality
2948
+ if (clearButton) {
2949
+ clearButton.addEventListener('click', function() {
2950
+ clearPropertySearch(searchInput);
2951
+ });
2952
+ }
2953
+
2954
+ function performPropertySearch(searchInput) {
2955
+ const searchTerm = searchInput.value.toLowerCase().trim();
2956
+ const container = document.getElementById('property-stats-container');
2957
+ const items = Array.from(container.getElementsByClassName('property-stat-row'));
2958
+
2959
+ let visibleCount = 0;
2960
+ let totalCount = items.length;
2961
+
2962
+ // Filter items based on search term
2963
+ items.forEach(function(item) {
2964
+ const propertyNameElement = item.querySelector('.badge-custom');
2965
+ if (propertyNameElement) {
2966
+ const propertyName = propertyNameElement.textContent.toLowerCase();
2967
+
2968
+ if (searchTerm === '' || propertyName.includes(searchTerm)) {
2969
+ item.setAttribute('data-search-visible', 'true');
2970
+ visibleCount++;
2971
+ } else {
2972
+ item.setAttribute('data-search-visible', 'false');
2973
+ }
2974
+ }
2975
+ });
2976
+
2977
+ // Update search results count
2978
+ updatePropertySearchResults(visibleCount, totalCount, searchTerm);
2979
+
2980
+ // Re-initialize pagination and go to first page
2981
+ initPagination('property-stats-container', 'property-stat-row', 'stats-pagination', 'stats-page-size');
2982
+
2983
+ // Always go to page 1 after search
2984
+ const currentPageSize = document.getElementById('stats-page-size') ?
2985
+ parseInt(document.getElementById('stats-page-size').value) : 10;
2986
+ goToPage(1, 'property-stats-container', 'property-stat-row', currentPageSize, 'stats-pagination');
2987
+ }
2988
+
2989
+ function clearPropertySearch(searchInput) {
2990
+ searchInput.value = '';
2991
+ performPropertySearch(searchInput);
2992
+ searchInput.focus();
2993
+ }
2994
+
2995
+ function updatePropertySearchResults(visibleCount, totalCount, searchTerm) {
2996
+ const resultsElement = document.getElementById('property-search-results');
2997
+
2998
+ if (resultsElement) {
2999
+ if (searchTerm === '') {
3000
+ resultsElement.innerHTML = `<span class="text-muted">Showing all ${totalCount} properties - Type and click <i class="bi bi-search"></i> or press Enter to search</span>`;
3001
+ } else if (visibleCount === 0) {
3002
+ resultsElement.innerHTML = `<span class="text-danger">No properties found for "${searchTerm}"</span>`;
3003
+ } else if (visibleCount === totalCount) {
3004
+ resultsElement.innerHTML = `<span class="text-success">All ${totalCount} properties match "${searchTerm}"</span>`;
3005
+ } else {
3006
+ resultsElement.innerHTML = `<span class="text-info">Found ${visibleCount} of ${totalCount} properties for "${searchTerm}"</span>`;
3007
+ }
3008
+ }
3009
+ }
3010
+ }
3011
+
3012
+ // Global pagination and navigation functions
3013
+ function goToPage(pageNumber, containerId, itemClass, itemsPerPage, paginationId) {
3014
+ const container = document.getElementById(containerId);
3015
+ const allItems = Array.from(container.getElementsByClassName(itemClass));
3016
+ const paginationElement = document.getElementById(paginationId);
3017
+
3018
+ // Get items that should be visible based on search filter
3019
+ const filteredItems = allItems.filter(item => {
3020
+ const searchVisible = item.getAttribute('data-search-visible');
3021
+ const shouldShow = searchVisible === null || searchVisible === 'true';
3022
+ return shouldShow;
3023
+ });
3024
+
3025
+ // Update pagination active state
3026
+ if (paginationElement) {
3027
+ const pageItems = paginationElement.getElementsByClassName('page-item');
3028
+ for (let i = 0; i < pageItems.length; i++) {
3029
+ if (pageItems[i].textContent.trim() === pageNumber.toString()) {
3030
+ pageItems[i].className = 'page-item active';
3031
+ } else if (pageItems[i].textContent.trim() !== '«' && pageItems[i].textContent.trim() !== '»') {
3032
+ pageItems[i].className = 'page-item';
3033
+ }
3034
+ }
3035
+ }
3036
+
3037
+ // Hide all items first
3038
+ allItems.forEach((item, index) => {
3039
+ item.style.display = 'none';
3040
+ });
3041
+
3042
+ // Show items for current page (only from filtered items)
3043
+ const startIndex = (pageNumber - 1) * itemsPerPage;
3044
+ const endIndex = Math.min(startIndex + itemsPerPage, filteredItems.length);
3045
+
3046
+ for (let i = startIndex; i < endIndex; i++) {
3047
+ if (filteredItems[i]) {
3048
+ filteredItems[i].style.display = '';
3049
+ }
3050
+ }
3051
+ }
3052
+
3053
+ function initPagination(containerId, itemClass, paginationId, pageSizeSelectId) {
3054
+ const container = document.getElementById(containerId);
3055
+ const pageSizeSelect = document.getElementById(pageSizeSelectId);
3056
+ if (!container) return;
3057
+
3058
+ // Remove existing event listener to prevent duplicate bindings
3059
+ if (pageSizeSelect && !pageSizeSelect.hasAttribute('data-listener-bound')) {
3060
+ pageSizeSelect.addEventListener('change', function() {
3061
+ const newItemsPerPage = parseInt(this.value);
3062
+ renderPagination();
3063
+ goToPage(1, containerId, itemClass, newItemsPerPage, paginationId);
3064
+ });
3065
+ // Mark as having listener bound
3066
+ pageSizeSelect.setAttribute('data-listener-bound', 'true');
3067
+ }
3068
+
3069
+ function renderPagination() {
3070
+ const allItems = Array.from(container.getElementsByClassName(itemClass));
3071
+
3072
+ // Always get current page size from select element
3073
+ const currentPageSizeSelect = document.getElementById(pageSizeSelectId);
3074
+ const currentItemsPerPage = currentPageSizeSelect ? parseInt(currentPageSizeSelect.value) : 10;
3075
+
3076
+ // Use same filtering logic as goToPage function
3077
+ const filteredItems = allItems.filter(item => {
3078
+ const searchVisible = item.getAttribute('data-search-visible');
3079
+ // If no search filter is applied, show all items
3080
+ return searchVisible === null || searchVisible === 'true';
3081
+ });
3082
+
3083
+ const totalItems = filteredItems.length;
3084
+ const totalPages = Math.max(1, Math.ceil(totalItems / currentItemsPerPage));
3085
+
3086
+ // Create pagination
3087
+ const paginationElement = document.getElementById(paginationId);
3088
+ if (!paginationElement) return;
3089
+
3090
+ // Clear pagination
3091
+ paginationElement.innerHTML = '';
3092
+
3093
+ // Don't show pagination if there's only one page or no items
3094
+ if (totalPages <= 1) {
3095
+ // Hide the entire pagination container when not needed
3096
+ const paginationContainer = paginationElement.closest('.pagination-container');
3097
+ if (paginationContainer) {
3098
+ paginationContainer.style.display = 'none';
3099
+ }
3100
+ return;
3101
+ }
3102
+
3103
+ // Show the pagination container when needed
3104
+ const paginationContainer = paginationElement.closest('.pagination-container');
3105
+ if (paginationContainer) {
3106
+ paginationContainer.style.display = '';
3107
+ }
3108
+
3109
+ // Previous button
3110
+ const prevLi = document.createElement('li');
3111
+ prevLi.className = 'page-item disabled';
3112
+ prevLi.innerHTML = '<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>';
3113
+ paginationElement.appendChild(prevLi);
3114
+
3115
+ // Add page numbers
3116
+ for (let i = 1; i <= totalPages; i++) {
3117
+ const pageLi = document.createElement('li');
3118
+ pageLi.className = i === 1 ? 'page-item active' : 'page-item';
3119
+ pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
3120
+ pageLi.addEventListener('click', function(e) {
3121
+ e.preventDefault();
3122
+ goToPage(i, containerId, itemClass, currentItemsPerPage, paginationId);
3123
+ });
3124
+ paginationElement.appendChild(pageLi);
3125
+ }
3126
+
3127
+ // Next button
3128
+ const nextLi = document.createElement('li');
3129
+ nextLi.className = totalPages <= 1 ? 'page-item disabled' : 'page-item';
3130
+ nextLi.innerHTML = '<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>';
3131
+ paginationElement.appendChild(nextLi);
3132
+
3133
+ // Add event listeners to prev/next buttons
3134
+ if (paginationElement.firstChild) {
3135
+ paginationElement.firstChild.addEventListener('click', function(e) {
3136
+ e.preventDefault();
3137
+ const activePage = paginationElement.querySelector('.active');
3138
+ if (activePage && activePage.previousElementSibling && activePage.previousElementSibling.previousElementSibling) {
3139
+ const pageNum = parseInt(activePage.textContent) - 1;
3140
+ if (pageNum >= 1) {
3141
+ goToPage(pageNum, containerId, itemClass, currentItemsPerPage, paginationId);
3142
+ }
3143
+ }
3144
+ });
3145
+ }
3146
+
3147
+ if (paginationElement.lastChild) {
3148
+ paginationElement.lastChild.addEventListener('click', function(e) {
3149
+ e.preventDefault();
3150
+ const activePage = paginationElement.querySelector('.active');
3151
+ if (activePage && activePage.nextElementSibling && activePage.nextElementSibling.nextElementSibling) {
3152
+ const pageNum = parseInt(activePage.textContent) + 1;
3153
+ if (pageNum <= totalPages) {
3154
+ goToPage(pageNum, containerId, itemClass, currentItemsPerPage, paginationId);
3155
+ }
3156
+ }
3157
+ });
3158
+ }
3159
+ }
3160
+
3161
+ // Initial render
3162
+ renderPagination();
3163
+ // Always call goToPage to ensure items are displayed correctly, even if no pagination is shown
3164
+ const currentItemsPerPage = pageSizeSelect ? parseInt(pageSizeSelect.value) : 10;
3165
+ goToPage(1, containerId, itemClass, currentItemsPerPage, paginationId);
3166
+ }
1652
3167
  </script>
1653
3168
  </body>
1654
3169
  </html>