Kea2-python 0.3.1__py3-none-any.whl → 0.3.2__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.

@@ -161,7 +161,73 @@
161
161
  .table-custom tbody tr:hover {
162
162
  background-color: rgba(0, 0, 0, 0.05);
163
163
  }
164
-
164
+
165
+ /* Specific column widths for property source mapping table */
166
+ #property-source-container tr th:nth-child(1),
167
+ #property-source-container tr td:nth-child(1) { /* Index */
168
+ width: 8%;
169
+ min-width: 55px;
170
+ }
171
+
172
+ #property-source-container tr th:nth-child(2),
173
+ #property-source-container tr td:nth-child(2) { /* Property Name */
174
+ width: 35%;
175
+ min-width: 200px;
176
+ text-align: left;
177
+ }
178
+
179
+ #property-source-container tr th:nth-child(3),
180
+ #property-source-container tr td:nth-child(3) { /* Source Directories */
181
+ width: 57%;
182
+ min-width: 300px;
183
+ text-align: center;
184
+ }
185
+
186
+ /* Property Source Mapping specific styles */
187
+ .property-source-row .badge-custom {
188
+ max-width: 100%;
189
+ word-break: break-word;
190
+ white-space: normal;
191
+ }
192
+
193
+ .property-source-row td:last-child {
194
+ line-height: 1.8;
195
+ }
196
+
197
+ .property-source-row .badge.bg-info {
198
+ transition: all 0.2s ease;
199
+ }
200
+
201
+ .property-source-row .badge.bg-info:hover {
202
+ background-color: #0d6efd !important;
203
+ transform: translateY(-1px);
204
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
205
+ }
206
+
207
+ /* Source directories toggle button styles */
208
+ .source-dirs-toggle {
209
+ border-radius: 12px;
210
+ transition: all 0.2s ease;
211
+ vertical-align: middle;
212
+ }
213
+
214
+ .source-dirs-toggle:hover {
215
+ transform: translateY(-1px);
216
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
217
+ }
218
+
219
+ .source-dirs-toggle .bi {
220
+ transition: transform 0.2s ease;
221
+ }
222
+
223
+ .source-dirs-hidden {
224
+ display: inline;
225
+ }
226
+
227
+ .source-dirs-container {
228
+ line-height: 1.8;
229
+ }
230
+
165
231
  .stat-value {
166
232
  font-size: 2.2rem;
167
233
  font-weight: 700;
@@ -846,91 +912,228 @@
846
912
  font-size: 15px;
847
913
  }
848
914
 
849
- .merged-directories-grid {
850
- display: grid;
851
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
852
- gap: 12px;
853
- max-height: 300px;
915
+ .merged-directories-list {
916
+ max-height: 200px;
854
917
  overflow-y: auto;
855
- padding: 4px;
918
+ overflow-x: hidden;
919
+ padding: 12px;
920
+ padding-right: 8px; /* Make room for scrollbar */
921
+ background: #f8f9fa;
922
+ border-radius: 8px;
923
+ border: 1px solid #e9ecef;
924
+ font-family: 'Courier New', monospace;
925
+ line-height: 1.6;
926
+ /* Force scrollbar to always be visible when content overflows */
927
+ scrollbar-width: thin;
928
+ scrollbar-color: #c1c1c1 #f1f1f1;
856
929
  }
857
930
 
858
- .directory-item-card {
859
- background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
931
+ .directory-list-item {
932
+ display: flex;
933
+ align-items: center;
934
+ padding: 6px 8px;
935
+ margin-bottom: 4px;
936
+ background: white;
937
+ border-radius: 4px;
860
938
  border: 1px solid #e9ecef;
861
- border-radius: 8px;
862
- padding: 14px;
863
939
  transition: all 0.2s ease;
864
- cursor: default;
865
- min-height: 70px;
866
940
  }
867
941
 
868
- .directory-item-card:hover {
869
- background: linear-gradient(135deg, #e3f2fd 0%, #f0f8ff 100%);
942
+ .directory-list-item:hover {
943
+ background: #e3f2fd;
870
944
  border-color: #17a2b8;
871
- transform: translateY(-2px);
872
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
945
+ transform: translateX(4px);
873
946
  }
874
947
 
875
- .directory-item-content {
876
- display: flex;
877
- align-items: center;
948
+ .directory-list-item:last-child {
949
+ margin-bottom: 0;
878
950
  }
879
951
 
880
- .directory-icon {
881
- width: 36px;
882
- height: 36px;
883
- background: linear-gradient(135deg, #ffc107, #e0a800);
884
- border-radius: 8px;
952
+ /* Property Search Container Styles */
953
+ .property-search-container {
954
+ position: relative;
885
955
  display: flex;
886
956
  align-items: center;
887
- justify-content: center;
888
- margin-right: 14px;
889
- flex-shrink: 0;
957
+ width: 100%;
890
958
  }
891
959
 
892
- .directory-icon i {
893
- color: white;
960
+ .property-search-input {
961
+ border-radius: 25px !important;
962
+ border: 1px solid #dee2e6 !important;
963
+ padding: 8px 45px 8px 16px !important;
964
+ font-size: 14px;
965
+ transition: all 0.3s ease;
966
+ background-color: #ffffff;
967
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
968
+ }
969
+
970
+ .property-search-input:focus {
971
+ border-color: #007bff !important;
972
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
973
+ outline: none;
974
+ }
975
+
976
+ .property-search-input::placeholder {
977
+ color: #6c757d;
978
+ font-style: italic;
979
+ }
980
+
981
+ .property-search-icon-btn {
982
+ position: absolute;
983
+ right: 8px;
984
+ top: 50%;
985
+ transform: translateY(-50%);
986
+ background: none;
987
+ border: none;
988
+ color: #6c757d;
894
989
  font-size: 16px;
990
+ padding: 4px 8px;
991
+ cursor: pointer;
992
+ transition: color 0.3s ease;
993
+ z-index: 10;
895
994
  }
896
995
 
897
- .directory-info {
898
- flex: 1;
899
- min-width: 0;
996
+ .property-search-icon-btn.search-btn:hover {
997
+ color: #495057 !important;
998
+ background: rgba(0, 0, 0, 0.04) !important;
999
+ border-radius: 4px !important;
1000
+ transform: translateY(-50%) !important;
1001
+ box-shadow: none !important;
900
1002
  }
901
1003
 
902
- .directory-name {
903
- font-family: 'Courier New', monospace;
904
- font-size: 15px;
1004
+ .property-search-icon-btn.search-btn:focus {
1005
+ outline: none;
1006
+ color: #495057 !important;
1007
+ background: rgba(0, 0, 0, 0.04) !important;
1008
+ border-radius: 4px !important;
1009
+ transform: translateY(-50%) !important;
1010
+ box-shadow: none !important;
1011
+ }
1012
+
1013
+ .directory-list-item .directory-index {
905
1014
  font-weight: 600;
906
- color: #2c3e50;
907
- margin-bottom: 3px;
908
- word-break: break-all;
909
- line-height: 1.3;
1015
+ color: #17a2b8;
1016
+ margin-right: 8px;
1017
+ min-width: 25px;
1018
+ font-size: 14px;
910
1019
  }
911
1020
 
912
- .directory-index {
913
- font-size: 13px;
914
- color: #6c757d;
1021
+ /* Activities Coverage table styling */
1022
+ .table-activities thead {
1023
+ background-color: #007bff !important;
1024
+ color: white;
1025
+ }
1026
+
1027
+ .table-activities .sort-icon {
1028
+ color: #ffffff !important;
1029
+ text-shadow: 0 0 3px rgba(0,0,0,0.4);
1030
+ }
1031
+
1032
+ .activities-sort-icon {
1033
+ cursor: pointer;
1034
+ transition: all 0.3s ease;
1035
+ }
1036
+
1037
+ .activities-sort-icon:hover {
1038
+ transform: scale(1.1);
1039
+ }
1040
+
1041
+ .table-activities .sort-icon:hover {
1042
+ opacity: 1;
1043
+ transform: scale(1.2);
1044
+ text-shadow: 0 0 5px rgba(0,0,0,0.6);
1045
+ }
1046
+
1047
+ .table-activities .activities-sort-icon:hover {
1048
+ opacity: 1;
1049
+ transform: scale(1.2);
1050
+ text-shadow: 0 0 5px rgba(0,0,0,0.6);
1051
+ }
1052
+
1053
+ /* Column widths for activities table */
1054
+ .table-activities th:nth-child(1), .table-activities td:nth-child(1) { /* Activity Name */
1055
+ width: 70%;
1056
+ min-width: 300px;
1057
+ text-align: left;
1058
+ padding-left: 180px;
1059
+ }
1060
+
1061
+ .table-activities th:nth-child(2), .table-activities td:nth-child(2) { /* Visit Count */
1062
+ width: 30%;
1063
+ min-width: 150px;
1064
+ text-align: center;
1065
+ }
1066
+
1067
+ .activity-name {
915
1068
  font-weight: 500;
1069
+ color: #374151;
1070
+ font-size: 14px;
1071
+ }
1072
+
1073
+ /* Activities Coverage table styling */
1074
+ .table-activities thead {
1075
+ background-color: #007bff !important;
1076
+ color: white;
1077
+ }
1078
+
1079
+ .table-activities .sort-icon {
1080
+ color: #ffffff !important;
1081
+ text-shadow: 0 0 3px rgba(0,0,0,0.4);
1082
+ }
1083
+
1084
+ .directory-list-item .directory-name {
1085
+ font-size: 14px;
1086
+ color: #2c3e50;
1087
+ word-break: break-all;
1088
+ flex: 1;
916
1089
  }
917
1090
 
918
- .merged-directories-grid::-webkit-scrollbar {
919
- width: 6px;
1091
+ .merged-directories-list::-webkit-scrollbar {
1092
+ width: 8px;
920
1093
  }
921
1094
 
922
- .merged-directories-grid::-webkit-scrollbar-track {
1095
+ .merged-directories-list::-webkit-scrollbar-track {
923
1096
  background: #f1f1f1;
924
- border-radius: 3px;
1097
+ border-radius: 4px;
1098
+ margin: 2px;
925
1099
  }
926
1100
 
927
- .merged-directories-grid::-webkit-scrollbar-thumb {
928
- background: #c1c1c1;
929
- border-radius: 3px;
1101
+ .merged-directories-list::-webkit-scrollbar-thumb {
1102
+ background: #17a2b8;
1103
+ border-radius: 4px;
1104
+ border: 1px solid #f1f1f1;
1105
+ min-height: 20px;
1106
+ }
1107
+
1108
+ .merged-directories-list::-webkit-scrollbar-thumb:hover {
1109
+ background: #138496;
1110
+ border-color: #e9ecef;
1111
+ }
1112
+
1113
+ .merged-directories-list::-webkit-scrollbar-thumb:active {
1114
+ background: #0f6674;
1115
+ }
1116
+
1117
+ /* Scroll indicator for merged directories */
1118
+ .merged-directories-container {
1119
+ position: relative;
1120
+ }
1121
+
1122
+ .merged-directories-container::after {
1123
+ content: "";
1124
+ position: absolute;
1125
+ top: 0;
1126
+ right: 0;
1127
+ width: 2px;
1128
+ height: 100%;
1129
+ background: linear-gradient(to bottom, transparent 0%, rgba(23, 162, 184, 0.3) 50%, transparent 100%);
1130
+ pointer-events: none;
1131
+ opacity: 0;
1132
+ transition: opacity 0.3s ease;
930
1133
  }
931
1134
 
932
- .merged-directories-grid::-webkit-scrollbar-thumb:hover {
933
- background: #a8a8a8;
1135
+ .merged-directories-container:hover::after {
1136
+ opacity: 1;
934
1137
  }
935
1138
 
936
1139
  /* Animation for collapse */
@@ -940,8 +1143,12 @@
940
1143
 
941
1144
  /* Responsive adjustments */
942
1145
  @media (max-width: 768px) {
943
- .merged-directories-grid {
944
- grid-template-columns: 1fr;
1146
+ .merged-directories-list {
1147
+ font-size: 13px;
1148
+ }
1149
+
1150
+ .directory-list-item .directory-name {
1151
+ font-size: 13px;
945
1152
  }
946
1153
 
947
1154
  .merge-info-header {
@@ -1039,20 +1246,15 @@
1039
1246
  Merged Directories
1040
1247
  </h6>
1041
1248
  </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>
1249
+ <div class="merged-directories-container">
1250
+ <div class="merged-directories-list">
1251
+ {% for dir_name in merge_info.source_directories %}
1252
+ <div class="directory-list-item">
1253
+ <span class="directory-index">{{ loop.index }}.</span>
1254
+ <span class="directory-name">{{ dir_name }}</span>
1053
1255
  </div>
1256
+ {% endfor %}
1054
1257
  </div>
1055
- {% endfor %}
1056
1258
  </div>
1057
1259
  </div>
1058
1260
  </div>
@@ -1066,7 +1268,7 @@
1066
1268
  <div class="text-center">
1067
1269
  <i class="bi bi-bug text-danger" style="font-size: 2rem;"></i>
1068
1270
  <span class="stat-value value-danger">{{ bugs_found }}</span>
1069
- <span class="stat-label">Total Bugs</span>
1271
+ <span class="stat-label">Bugs Found</span>
1070
1272
  </div>
1071
1273
  </div>
1072
1274
  <div class="col-lg-2 col-md-4 col-sm-6">
@@ -1087,7 +1289,7 @@
1087
1289
  <div class="text-center">
1088
1290
  <i class="bi bi-pie-chart text-info" style="font-size: 2rem;"></i>
1089
1291
  <span class="stat-value value-highlight">{{ "%.2f"|format(coverage_percent) }}%</span>
1090
- <span class="stat-label">Coverage</span>
1292
+ <span class="stat-label">Activity Coverage</span>
1091
1293
  </div>
1092
1294
  </div>
1093
1295
  <div class="col-lg-2 col-md-4 col-sm-6">
@@ -1101,7 +1303,7 @@
1101
1303
  <div class="text-center">
1102
1304
  <i class="bi bi-check-square text-success" style="font-size: 2rem;"></i>
1103
1305
  <span class="stat-value value-success">{{ executed_properties_count }}</span>
1104
- <span class="stat-label">Executed</span>
1306
+ <span class="stat-label">Executed Properties</span>
1105
1307
  </div>
1106
1308
  </div>
1107
1309
  </div>
@@ -1109,255 +1311,94 @@
1109
1311
  </div>
1110
1312
  </div>
1111
1313
 
1112
- <!-- Tested Activities List -->
1314
+ <!-- Activities Coverage -->
1113
1315
  <div class="section-block">
1114
1316
  <h2 class="section-title">Activities Coverage</h2>
1115
1317
 
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>
1318
+ <!-- Search Controls for Activities -->
1319
+ <div class="mb-4">
1320
+ <div class="d-flex align-items-center">
1321
+ <div style="width: 33%;">
1322
+ <div class="property-search-container">
1323
+ <input type="text" class="form-control property-search-input property-stats-search-simple"
1324
+ id="activities-search"
1325
+ placeholder="Search activities by name..."
1326
+ data-target="activities-container"
1327
+ data-item-class="activity-row"
1328
+ data-pagination="activities-pagination"
1329
+ data-page-size="activities-page-size">
1330
+ <button class="property-search-icon-btn search-btn" type="button" id="activities-search-btn">
1331
+ <i class="bi bi-search"></i>
1332
+ </button>
1333
+ </div>
1121
1334
  </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>
1335
+ <div class="ms-3">
1336
+ <small class="text-muted search-results-count" id="activities-search-results"></small>
1130
1337
  </div>
1338
+ </div>
1339
+ </div>
1131
1340
 
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>
1341
+ <div class="table-responsive">
1342
+ <table class="table table-custom table-activities">
1343
+ <thead>
1344
+ <tr>
1345
+ <th>Activity Name</th>
1346
+ <th>Visit Count <i class="bi bi-arrow-down-up text-muted sort-icon activities-sort-icon" id="visit-count-sort" data-column="visit-count" data-order="none" style="cursor: pointer;"></i></th>
1347
+ </tr>
1348
+ </thead>
1349
+ <tbody id="activities-container">
1350
+ {% if total_activities|length > 0 %}
1351
+ {% for activity in total_activities %}
1352
+ <tr class="activity-row" data-page="1"
1353
+ data-activity-name="{{ activity }}"
1354
+ data-visit-count="{{ activity_count_history[activity] if activity in activity_count_history else 0 }}">
1355
+ <td>
1356
+ {% if activity in tested_activities %}
1357
+ <i class="bi bi-check-circle-fill text-success me-2"></i>
1227
1358
  {% else %}
1228
- <div class="alert alert-warning">
1229
- No tested activities detected
1230
- </div>
1359
+ <i class="bi bi-dash-circle text-secondary me-2"></i>
1231
1360
  {% 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>
1361
+ <span class="activity-name">{{ activity }}</span>
1362
+ </td>
1363
+ <td>
1364
+ {% if activity in activity_count_history %}
1365
+ <span class="badge bg-info text-white">
1366
+ <i class="bi bi-eye"></i> {{ activity_count_history[activity] }} times
1367
+ </span>
1333
1368
  {% else %}
1334
- <div class="alert alert-warning">
1335
- No activities information available
1336
- </div>
1369
+ <span class="badge bg-secondary text-white">
1370
+ <i class="bi bi-dash"></i> 0 times
1371
+ </span>
1337
1372
  {% 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>
1373
+ </td>
1374
+ </tr>
1375
+ {% endfor %}
1376
+ {% else %}
1377
+ <tr>
1378
+ <td colspan="2" class="text-center text-muted">No activities found</td>
1379
+ </tr>
1380
+ {% endif %}
1381
+ </tbody>
1382
+ </table>
1383
+ </div>
1384
+
1385
+ <!-- Pagination for Activities -->
1386
+ <div class="d-flex justify-content-between align-items-center mt-3">
1387
+ <div class="d-flex align-items-center">
1388
+ <label for="activities-page-size" class="form-label me-2 mb-0">Show:</label>
1389
+ <select class="form-select form-select-sm" id="activities-page-size" style="width: auto;">
1390
+ <option value="5">5</option>
1391
+ <option value="10" selected>10</option>
1392
+ <option value="20">20</option>
1393
+ <option value="50">50</option>
1394
+ <option value="100">100</option>
1395
+ </select>
1360
1396
  </div>
1397
+ <nav aria-label="Activities pagination">
1398
+ <ul class="pagination pagination-sm mb-0" id="activities-pagination">
1399
+ <!-- Pagination will be generated by JavaScript -->
1400
+ </ul>
1401
+ </nav>
1361
1402
  </div>
1362
1403
  </div>
1363
1404
 
@@ -1502,32 +1543,23 @@
1502
1543
  <h2 class="section-title">Property Checking Statistics</h2>
1503
1544
 
1504
1545
  <!-- 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>
1546
+ <div class="mb-4">
1547
+ <div style="width: 33%;">
1548
+ <div class="property-search-container">
1549
+ <input type="text" class="form-control property-search-input property-stats-search-simple"
1550
+ id="property-stats-search"
1551
+ placeholder="Search properties by name..."
1552
+ data-target="property-stats-container"
1553
+ data-item-class="property-stat-row"
1554
+ data-pagination="stats-pagination"
1555
+ data-page-size="stats-page-size">
1556
+ <button class="property-search-icon-btn search-btn" type="button"
1557
+ data-target="property-stats-search">
1558
+ <i class="bi bi-search"></i>
1559
+ </button>
1529
1560
  </div>
1530
1561
  </div>
1562
+ <small class="text-muted search-results-count" id="property-search-results"></small>
1531
1563
  </div>
1532
1564
 
1533
1565
  <div class="table-responsive">
@@ -1582,33 +1614,161 @@
1582
1614
  </div>
1583
1615
  </div>
1584
1616
  </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
1617
 
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');
1618
+ <!-- Property Source Mapping -->
1619
+ {% if property_source_mapping %}
1620
+ <div class="section-block">
1621
+ <h2 class="section-title">
1622
+ <i class="bi bi-folder2-open text-info"></i> Property Source Mapping
1623
+ </h2>
1604
1624
 
1605
- // Initialize activity sorting
1606
- initActivitySorting();
1625
+ <div class="alert alert-info mb-4" style="border-left: 4px solid #17a2b8; background-color: #f8f9fa;">
1626
+ <div class="d-flex align-items-start">
1627
+ <i class="bi bi-info-circle me-2 mt-1"></i>
1628
+ <div>
1629
+ <strong>Source Directory Information:</strong>
1630
+ <p class="mb-0 mt-1">This section shows which test directories contain properties with failures or errors.
1631
+ Use this information to locate the original test reports for detailed error analysis.</p>
1632
+ </div>
1633
+ </div>
1634
+ </div>
1607
1635
 
1608
- // Initialize activity searching
1609
- initActivitySearching();
1636
+ <!-- Search Controls for Property Source Mapping -->
1637
+ <div class="mb-4">
1638
+ <div style="width: 33%;">
1639
+ <div class="property-search-container">
1640
+ <input type="text" class="form-control property-search-input property-stats-search-simple"
1641
+ id="property-source-search"
1642
+ placeholder="Search properties by name..."
1643
+ data-target="property-source-container"
1644
+ data-item-class="property-source-row"
1645
+ data-pagination="source-pagination"
1646
+ data-page-size="source-page-size">
1647
+ <button class="property-search-icon-btn search-btn" type="button"
1648
+ data-target="property-source-search">
1649
+ <i class="bi bi-search"></i>
1650
+ </button>
1651
+ </div>
1652
+ </div>
1653
+ <small class="text-muted search-results-count" id="property-source-search-results"></small>
1654
+ </div>
1610
1655
 
1611
- // Initialize property statistics searching
1656
+ <div class="table-responsive">
1657
+ <table class="table table-custom">
1658
+ <thead>
1659
+ <tr>
1660
+ <th>Index</th>
1661
+ <th>Property Name</th>
1662
+ <th>Source Directories</th>
1663
+ </tr>
1664
+ </thead>
1665
+ <tbody id="property-source-container">
1666
+ {% for property_name, source_dirs in property_source_mapping.items() %}
1667
+ <tr class="property-source-row" data-page="1"
1668
+ data-index="{{ loop.index }}"
1669
+ data-property-name="{{ property_name }}">
1670
+ <td>{{ loop.index }}</td>
1671
+ <td>
1672
+ <span class="badge bg-light text-dark badge-custom">{{ property_name }}</span>
1673
+ </td>
1674
+ <td>
1675
+ {% set max_visible = 3 %}
1676
+ {% if source_dirs|length <= max_visible %}
1677
+ <!-- Show all directories if count is small -->
1678
+ {% for dir_name in source_dirs %}
1679
+ <span class="badge bg-info text-white me-2 mb-1" style="font-family: 'Courier New', monospace; font-size: 0.8em; padding: 0.4em 0.6em;">
1680
+ <i class="bi bi-folder me-1"></i>{{ dir_name }}
1681
+ </span>
1682
+ {% endfor %}
1683
+ {% else %}
1684
+ <!-- Show limited directories with expand/collapse -->
1685
+ <div class="source-dirs-container" data-property="{{ property_name }}">
1686
+ <!-- Always visible directories -->
1687
+ {% for dir_name in source_dirs[:max_visible] %}
1688
+ <span class="badge bg-info text-white me-2 mb-1" style="font-family: 'Courier New', monospace; font-size: 0.8em; padding: 0.4em 0.6em;">
1689
+ <i class="bi bi-folder me-1"></i>{{ dir_name }}
1690
+ </span>
1691
+ {% endfor %}
1692
+
1693
+ <!-- Hidden directories -->
1694
+ <div class="source-dirs-hidden" id="hidden-dirs-{{ loop.index }}" style="display: none;">
1695
+ {% for dir_name in source_dirs[max_visible:] %}
1696
+ <span class="badge bg-info text-white me-2 mb-1" style="font-family: 'Courier New', monospace; font-size: 0.8em; padding: 0.4em 0.6em;">
1697
+ <i class="bi bi-folder me-1"></i>{{ dir_name }}
1698
+ </span>
1699
+ {% endfor %}
1700
+ </div>
1701
+
1702
+ <!-- Toggle button -->
1703
+ <button class="btn btn-sm btn-outline-info source-dirs-toggle"
1704
+ data-target="hidden-dirs-{{ loop.index }}"
1705
+ data-total="{{ source_dirs|length }}"
1706
+ data-visible="{{ max_visible }}"
1707
+ style="font-size: 0.75em; padding: 0.2em 0.5em; margin-left: 0.25em;">
1708
+ <i class="bi bi-chevron-down"></i>
1709
+ <span class="toggle-text">+{{ source_dirs|length - max_visible }} more</span>
1710
+ </button>
1711
+ </div>
1712
+ {% endif %}
1713
+ </td>
1714
+ </tr>
1715
+ {% endfor %}
1716
+ </tbody>
1717
+ </table>
1718
+
1719
+ <!-- Pagination for Property Source Mapping -->
1720
+ <div class="d-flex justify-content-between align-items-center mt-3">
1721
+ <div class="d-flex align-items-center">
1722
+ <label for="source-page-size" class="form-label me-2 mb-0">Show:</label>
1723
+ <select class="form-select form-select-sm" id="source-page-size" style="width: auto;">
1724
+ <option value="5">5</option>
1725
+ <option value="10" selected>10</option>
1726
+ <option value="20">20</option>
1727
+ <option value="50">50</option>
1728
+ <option value="100">100</option>
1729
+ </select>
1730
+ </div>
1731
+ <nav aria-label="Property Source Pagination">
1732
+ <ul class="pagination pagination-sm mb-0" id="source-pagination">
1733
+ <!-- Pagination will be generated by JavaScript -->
1734
+ </ul>
1735
+ </nav>
1736
+ </div>
1737
+ </div>
1738
+ </div>
1739
+ {% endif %}
1740
+ </div>
1741
+
1742
+ <!-- Footer -->
1743
+ <footer class="bg-dark text-white text-center py-4">
1744
+ <div class="container">
1745
+ <p class="mb-0">Kea2 Merged Test Report | Generated at: {{ timestamp }}</p>
1746
+ </div>
1747
+ </footer>
1748
+
1749
+ <!-- JavaScript -->
1750
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
1751
+ <script>
1752
+ document.addEventListener('DOMContentLoaded', function() {
1753
+ // Initialize merge info collapse functionality
1754
+ initMergeInfoCollapse();
1755
+
1756
+ // Initialize merged directories scrollbar enhancement
1757
+ initMergedDirectoriesScrollbar();
1758
+
1759
+ // Initialize pagination for Activities
1760
+ initPagination('activities-container', 'activity-row', 'activities-pagination', 'activities-page-size');
1761
+
1762
+ // Initialize activity sorting
1763
+ initActivitiesSorting();
1764
+
1765
+ // Initialize activity searching
1766
+ initActivitiesSearching();
1767
+
1768
+ // Initialize activities page size selector
1769
+ initActivitiesPageSize();
1770
+
1771
+ // Initialize property statistics searching
1612
1772
  initPropertySearching();
1613
1773
 
1614
1774
  // Initialize pagination for Property tables
@@ -1617,6 +1777,13 @@
1617
1777
  // Initialize sorting for Property Checking Statistics
1618
1778
  initSorting();
1619
1779
 
1780
+ // Initialize property source mapping searching and pagination
1781
+ initPropertySourceSearching();
1782
+ initPagination('property-source-container', 'property-source-row', 'source-pagination', 'source-page-size');
1783
+
1784
+ // Initialize source directories expand/collapse functionality
1785
+ initSourceDirsToggle();
1786
+
1620
1787
  // Initialize crash events functionality (only if crash analysis section exists)
1621
1788
  setTimeout(function() {
1622
1789
  const crashContainer = document.getElementById('crash-events-container');
@@ -1910,7 +2077,10 @@
1910
2077
 
1911
2078
  // Simplified sorting function for Fails and Errors columns
1912
2079
  function initSorting() {
1913
- const sortIcons = document.querySelectorAll('.sort-icon');
2080
+ // Exclude activities sort icons to avoid conflicts
2081
+ const sortIcons = document.querySelectorAll('.sort-icon:not(.activities-sort-icon)');
2082
+
2083
+ console.log('initSorting: Found', sortIcons.length, 'sort icons (excluding activities)');
1914
2084
 
1915
2085
  sortIcons.forEach(function(icon) {
1916
2086
  icon.addEventListener('click', function() {
@@ -2291,6 +2461,468 @@
2291
2461
  }
2292
2462
  }
2293
2463
 
2464
+ // Property source mapping searching function
2465
+ function initPropertySourceSearching() {
2466
+ const searchInput = document.getElementById('property-source-search');
2467
+ const searchButton = document.querySelector('.search-btn[data-target="property-source-search"]');
2468
+ const clearButton = document.querySelector('[data-target="property-source-search"].search-clear-btn');
2469
+
2470
+ if (!searchInput) return;
2471
+
2472
+ // Search on Enter key
2473
+ searchInput.addEventListener('keydown', function(e) {
2474
+ if (e.key === 'Enter') {
2475
+ e.preventDefault();
2476
+ performPropertySourceSearch(this);
2477
+ } else if (e.key === 'Escape') {
2478
+ clearPropertySourceSearch(this);
2479
+ }
2480
+ });
2481
+
2482
+ // Search button functionality
2483
+ if (searchButton) {
2484
+ searchButton.addEventListener('click', function() {
2485
+ performPropertySourceSearch(searchInput);
2486
+ });
2487
+ }
2488
+
2489
+ // Clear button functionality
2490
+ if (clearButton) {
2491
+ clearButton.addEventListener('click', function() {
2492
+ clearPropertySourceSearch(searchInput);
2493
+ });
2494
+ }
2495
+
2496
+ function performPropertySourceSearch(searchInput) {
2497
+ const searchTerm = searchInput.value.toLowerCase().trim();
2498
+ const container = document.getElementById('property-source-container');
2499
+ const items = Array.from(container.getElementsByClassName('property-source-row'));
2500
+
2501
+ let visibleCount = 0;
2502
+ let totalCount = items.length;
2503
+
2504
+ // Filter items based on search term
2505
+ items.forEach(function(item) {
2506
+ const propertyNameElement = item.querySelector('.badge-custom');
2507
+ if (propertyNameElement) {
2508
+ const propertyName = propertyNameElement.textContent.toLowerCase();
2509
+
2510
+ if (searchTerm === '' || propertyName.includes(searchTerm)) {
2511
+ item.setAttribute('data-search-visible', 'true');
2512
+ visibleCount++;
2513
+ } else {
2514
+ item.setAttribute('data-search-visible', 'false');
2515
+ }
2516
+ }
2517
+ });
2518
+
2519
+ // Update search results count
2520
+ const resultsElement = document.getElementById('property-source-search-results');
2521
+ if (resultsElement) {
2522
+ if (searchTerm === '') {
2523
+ resultsElement.textContent = '';
2524
+ } else {
2525
+ resultsElement.textContent = `Found ${visibleCount} of ${totalCount} properties`;
2526
+ }
2527
+ }
2528
+
2529
+ // Re-initialize pagination with filtered results
2530
+ initPagination('property-source-container', 'property-source-row', 'source-pagination', 'source-page-size');
2531
+ }
2532
+
2533
+ function clearPropertySourceSearch(searchInput) {
2534
+ searchInput.value = '';
2535
+ performPropertySourceSearch(searchInput);
2536
+ }
2537
+ }
2538
+
2539
+ // Activities table searching function
2540
+ function initActivitiesSearching() {
2541
+ const searchInput = document.getElementById('activities-search');
2542
+ const searchButton = document.getElementById('activities-search-btn');
2543
+
2544
+ if (!searchInput) return;
2545
+
2546
+ // Search on Enter key and clear on Escape
2547
+ searchInput.addEventListener('keydown', function(e) {
2548
+ if (e.key === 'Enter') {
2549
+ e.preventDefault();
2550
+ performActivitiesSearch(this);
2551
+ } else if (e.key === 'Escape') {
2552
+ clearActivitiesSearch(this);
2553
+ }
2554
+ });
2555
+
2556
+ // Search button functionality
2557
+ if (searchButton) {
2558
+ searchButton.addEventListener('click', function() {
2559
+ performActivitiesSearch(searchInput);
2560
+ });
2561
+ }
2562
+
2563
+ function performActivitiesSearch(searchInput) {
2564
+ const searchTerm = searchInput.value.toLowerCase().trim();
2565
+ const container = document.getElementById('activities-container');
2566
+ const rows = container.querySelectorAll('.activity-row');
2567
+ const resultsCount = document.getElementById('activities-search-results');
2568
+
2569
+ let visibleCount = 0;
2570
+
2571
+ rows.forEach(function(row) {
2572
+ const activityName = row.querySelector('.activity-name').textContent.toLowerCase();
2573
+
2574
+ // If no search term, show all rows
2575
+ if (!searchTerm) {
2576
+ row.removeAttribute('data-search-visible');
2577
+ visibleCount++;
2578
+ } else {
2579
+ const matches = activityName.includes(searchTerm);
2580
+ if (matches) {
2581
+ row.setAttribute('data-search-visible', 'true');
2582
+ visibleCount++;
2583
+ } else {
2584
+ row.setAttribute('data-search-visible', 'false');
2585
+ }
2586
+ }
2587
+ });
2588
+
2589
+ console.log('Search performed:', {searchTerm, visibleCount, totalRows: rows.length});
2590
+
2591
+ // Update results count
2592
+ if (resultsCount) {
2593
+ if (searchTerm) {
2594
+ resultsCount.textContent = `Found ${visibleCount} of ${rows.length} activities`;
2595
+ } else {
2596
+ resultsCount.textContent = '';
2597
+ }
2598
+ }
2599
+
2600
+ // Immediately show/hide rows based on search before pagination update
2601
+ rows.forEach(function(row) {
2602
+ const searchVisible = row.getAttribute('data-search-visible');
2603
+ if (searchVisible === null || searchVisible === 'true') {
2604
+ row.style.display = '';
2605
+ } else {
2606
+ row.style.display = 'none';
2607
+ }
2608
+ });
2609
+
2610
+ console.log('Rows visibility updated, calling pagination update...');
2611
+
2612
+ // Update pagination after search
2613
+ updateActivitiesPagination();
2614
+ }
2615
+
2616
+ function clearActivitiesSearch(searchInput) {
2617
+ searchInput.value = '';
2618
+ performActivitiesSearch(searchInput);
2619
+ searchInput.focus();
2620
+ }
2621
+ }
2622
+
2623
+ // Activities table sorting function - Simplified version
2624
+ function initActivitiesSorting() {
2625
+ console.log('Initializing activities sorting...');
2626
+
2627
+ // Use event delegation to avoid losing event listeners
2628
+ document.addEventListener('click', function(e) {
2629
+ // Check if clicked element is the sort icon
2630
+ if (e.target && e.target.id === 'visit-count-sort') {
2631
+ e.preventDefault();
2632
+ e.stopPropagation();
2633
+
2634
+ console.log('=== SORT CLICK EVENT START ===');
2635
+ console.log('Sort icon clicked via delegation');
2636
+
2637
+ const sortIcon = e.target;
2638
+ const currentOrder = sortIcon.dataset.order || 'none';
2639
+
2640
+ console.log('Current order:', currentOrder);
2641
+
2642
+ // Determine new order
2643
+ let newOrder;
2644
+ if (currentOrder === 'none' || currentOrder === 'desc') {
2645
+ newOrder = 'asc';
2646
+ } else {
2647
+ newOrder = 'desc';
2648
+ }
2649
+
2650
+ console.log('New order:', newOrder);
2651
+
2652
+ // Update icon appearance
2653
+ sortIcon.classList.remove('bi-arrow-down-up', 'bi-arrow-up', 'bi-arrow-down');
2654
+ sortIcon.classList.add('text-primary');
2655
+ if (newOrder === 'asc') {
2656
+ sortIcon.classList.add('bi-arrow-up');
2657
+ } else {
2658
+ sortIcon.classList.add('bi-arrow-down');
2659
+ }
2660
+
2661
+ // Store new order
2662
+ sortIcon.dataset.order = newOrder;
2663
+
2664
+ // Get container and rows
2665
+ const container = document.getElementById('activities-container');
2666
+ const rows = Array.from(container.querySelectorAll('.activity-row'));
2667
+
2668
+ console.log('Found rows for sorting:', rows.length);
2669
+
2670
+ // Sort rows
2671
+ rows.sort((a, b) => {
2672
+ const aCount = parseInt(a.dataset.visitCount) || 0;
2673
+ const bCount = parseInt(b.dataset.visitCount) || 0;
2674
+
2675
+ if (newOrder === 'asc') {
2676
+ return aCount - bCount;
2677
+ } else {
2678
+ return bCount - aCount;
2679
+ }
2680
+ });
2681
+
2682
+ console.log('Rows sorted, reordering DOM');
2683
+
2684
+ // Clear container and re-append sorted rows
2685
+ container.innerHTML = '';
2686
+ rows.forEach(function(row) {
2687
+ container.appendChild(row);
2688
+ });
2689
+
2690
+ console.log('DOM reordered, updating pagination');
2691
+
2692
+ // Update pagination after sorting
2693
+ updateActivitiesPagination();
2694
+
2695
+ console.log('=== SORT CLICK EVENT END ===');
2696
+ }
2697
+ });
2698
+
2699
+ console.log('Activities sorting initialized with event delegation');
2700
+ }
2701
+
2702
+ // Function to update Activities pagination after sorting
2703
+ function updateActivitiesPagination() {
2704
+ const container = document.getElementById('activities-container');
2705
+ const pageSizeSelect = document.getElementById('activities-page-size');
2706
+ const paginationElement = document.getElementById('activities-pagination');
2707
+
2708
+ if (!container || !paginationElement) return;
2709
+
2710
+ const allRows = Array.from(container.querySelectorAll('.activity-row'));
2711
+ const itemsPerPage = pageSizeSelect ? parseInt(pageSizeSelect.value) : 10;
2712
+
2713
+ // Filter visible rows (considering search)
2714
+ const visibleRows = allRows.filter(row => {
2715
+ const searchVisible = row.getAttribute('data-search-visible');
2716
+ return searchVisible === null || searchVisible === 'true';
2717
+ });
2718
+
2719
+ const totalItems = visibleRows.length;
2720
+ const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage));
2721
+
2722
+ console.log('Updating pagination:', {totalItems, itemsPerPage, totalPages});
2723
+
2724
+ // Get pagination container
2725
+ const paginationContainer = paginationElement.closest('.d-flex');
2726
+
2727
+ // Don't show pagination if there's only one page or no items
2728
+ if (totalPages <= 1) {
2729
+ if (paginationContainer) {
2730
+ paginationContainer.style.display = 'none';
2731
+ }
2732
+ // Show visible rows and hide invisible rows
2733
+ allRows.forEach(row => {
2734
+ const searchVisible = row.getAttribute('data-search-visible');
2735
+ if (searchVisible === null || searchVisible === 'true') {
2736
+ row.style.display = '';
2737
+ } else {
2738
+ row.style.display = 'none';
2739
+ }
2740
+ });
2741
+ return;
2742
+ }
2743
+
2744
+ // Show pagination container
2745
+ if (paginationContainer) {
2746
+ paginationContainer.style.display = '';
2747
+ }
2748
+
2749
+ // Clear existing pagination
2750
+ paginationElement.innerHTML = '';
2751
+
2752
+ // Previous button
2753
+ const prevLi = document.createElement('li');
2754
+ prevLi.className = 'page-item disabled';
2755
+ prevLi.innerHTML = '<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>';
2756
+ paginationElement.appendChild(prevLi);
2757
+
2758
+ // Create pagination buttons
2759
+ for (let i = 1; i <= totalPages; i++) {
2760
+ const li = document.createElement('li');
2761
+ li.className = `page-item ${i === 1 ? 'active' : ''}`;
2762
+
2763
+ const a = document.createElement('a');
2764
+ a.className = 'page-link';
2765
+ a.href = '#';
2766
+ a.textContent = i;
2767
+ a.addEventListener('click', function(e) {
2768
+ e.preventDefault();
2769
+ goToActivitiesPage(i, totalPages, itemsPerPage);
2770
+ });
2771
+
2772
+ li.appendChild(a);
2773
+ paginationElement.appendChild(li);
2774
+ }
2775
+
2776
+ // Next button
2777
+ const nextLi = document.createElement('li');
2778
+ nextLi.className = totalPages <= 1 ? 'page-item disabled' : 'page-item';
2779
+ nextLi.innerHTML = '<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>';
2780
+ paginationElement.appendChild(nextLi);
2781
+
2782
+ // Add event listeners to prev/next buttons
2783
+ const prevButton = paginationElement.firstChild;
2784
+ const nextButton = paginationElement.lastChild;
2785
+
2786
+ prevButton.addEventListener('click', function(e) {
2787
+ e.preventDefault();
2788
+ const activePage = paginationElement.querySelector('.page-item.active');
2789
+ if (activePage && activePage.previousElementSibling && activePage.previousElementSibling !== prevButton) {
2790
+ const pageNum = parseInt(activePage.textContent) - 1;
2791
+ if (pageNum >= 1) {
2792
+ goToActivitiesPage(pageNum, totalPages, itemsPerPage);
2793
+ }
2794
+ }
2795
+ });
2796
+
2797
+ nextButton.addEventListener('click', function(e) {
2798
+ e.preventDefault();
2799
+ const activePage = paginationElement.querySelector('.page-item.active');
2800
+ if (activePage && activePage.nextElementSibling && activePage.nextElementSibling !== nextButton) {
2801
+ const pageNum = parseInt(activePage.textContent) + 1;
2802
+ if (pageNum <= totalPages) {
2803
+ goToActivitiesPage(pageNum, totalPages, itemsPerPage);
2804
+ }
2805
+ }
2806
+ });
2807
+
2808
+ // Show first page
2809
+ goToActivitiesPage(1, totalPages, itemsPerPage);
2810
+ }
2811
+
2812
+ // Function to navigate to specific page
2813
+ function goToActivitiesPage(pageNumber, totalPages, itemsPerPage) {
2814
+ const container = document.getElementById('activities-container');
2815
+ const allRows = Array.from(container.querySelectorAll('.activity-row'));
2816
+
2817
+ // Filter visible rows (considering search)
2818
+ const visibleRows = allRows.filter(row => {
2819
+ const searchVisible = row.getAttribute('data-search-visible');
2820
+ return searchVisible === null || searchVisible === 'true';
2821
+ });
2822
+
2823
+ const startIndex = (pageNumber - 1) * itemsPerPage;
2824
+ const endIndex = startIndex + itemsPerPage;
2825
+
2826
+ console.log('Going to page:', {pageNumber, startIndex, endIndex, totalVisible: visibleRows.length});
2827
+
2828
+ // Hide all rows first
2829
+ allRows.forEach(row => {
2830
+ row.style.display = 'none';
2831
+ });
2832
+
2833
+ // Show only rows for current page
2834
+ visibleRows.slice(startIndex, endIndex).forEach(row => {
2835
+ row.style.display = '';
2836
+ });
2837
+
2838
+ // Update pagination active state
2839
+ const paginationElement = document.getElementById('activities-pagination');
2840
+ if (paginationElement) {
2841
+ const pageItems = paginationElement.querySelectorAll('.page-item');
2842
+ const prevButton = pageItems[0]; // First item is previous button
2843
+ const nextButton = pageItems[pageItems.length - 1]; // Last item is next button
2844
+
2845
+ // Update page number buttons (skip first and last which are prev/next)
2846
+ pageItems.forEach((item, index) => {
2847
+ // Skip prev/next buttons
2848
+ if (index === 0 || index === pageItems.length - 1) return;
2849
+
2850
+ const pageNum = index; // Adjusted for prev button at index 0
2851
+ if (pageNum === pageNumber) {
2852
+ item.classList.add('active');
2853
+ } else {
2854
+ item.classList.remove('active');
2855
+ }
2856
+ });
2857
+
2858
+ // Update prev/next button states
2859
+ if (prevButton) {
2860
+ if (pageNumber <= 1) {
2861
+ prevButton.classList.add('disabled');
2862
+ } else {
2863
+ prevButton.classList.remove('disabled');
2864
+ }
2865
+ }
2866
+
2867
+ if (nextButton) {
2868
+ if (pageNumber >= totalPages) {
2869
+ nextButton.classList.add('disabled');
2870
+ } else {
2871
+ nextButton.classList.remove('disabled');
2872
+ }
2873
+ }
2874
+ }
2875
+ }
2876
+
2877
+ // Initialize Activities page size selector
2878
+ function initActivitiesPageSize() {
2879
+ const pageSizeSelect = document.getElementById('activities-page-size');
2880
+
2881
+ if (!pageSizeSelect) return;
2882
+
2883
+ // Remove existing event listener to prevent duplicate bindings
2884
+ if (!pageSizeSelect.hasAttribute('data-activities-listener-bound')) {
2885
+ pageSizeSelect.addEventListener('change', function() {
2886
+ console.log('Activities page size changed to:', this.value);
2887
+ updateActivitiesPagination();
2888
+ });
2889
+ // Mark as having listener bound
2890
+ pageSizeSelect.setAttribute('data-activities-listener-bound', 'true');
2891
+ }
2892
+ }
2893
+
2894
+ // Source directories expand/collapse functionality
2895
+ function initSourceDirsToggle() {
2896
+ const toggleButtons = document.querySelectorAll('.source-dirs-toggle');
2897
+
2898
+ toggleButtons.forEach(function(button) {
2899
+ button.addEventListener('click', function() {
2900
+ const targetId = this.getAttribute('data-target');
2901
+ const hiddenDirs = document.getElementById(targetId);
2902
+ const icon = this.querySelector('.bi');
2903
+ const toggleText = this.querySelector('.toggle-text');
2904
+ const total = parseInt(this.getAttribute('data-total'));
2905
+ const visible = parseInt(this.getAttribute('data-visible'));
2906
+
2907
+ if (hiddenDirs.style.display === 'none') {
2908
+ // Show hidden directories
2909
+ hiddenDirs.style.display = 'inline';
2910
+ icon.className = 'bi bi-chevron-up';
2911
+ toggleText.textContent = 'Show less';
2912
+ this.classList.remove('btn-outline-info');
2913
+ this.classList.add('btn-outline-secondary');
2914
+ } else {
2915
+ // Hide directories
2916
+ hiddenDirs.style.display = 'none';
2917
+ icon.className = 'bi bi-chevron-down';
2918
+ toggleText.textContent = `+${total - visible} more`;
2919
+ this.classList.remove('btn-outline-secondary');
2920
+ this.classList.add('btn-outline-info');
2921
+ }
2922
+ });
2923
+ });
2924
+ }
2925
+
2294
2926
  function updatePaginationControls(paginationId, currentPage, totalPages, onPageClick) {
2295
2927
  var pagination = document.getElementById(paginationId);
2296
2928
  if (!pagination) return;
@@ -2395,6 +3027,76 @@
2395
3027
  console.log('Merge info collapse initialized');
2396
3028
  }
2397
3029
 
3030
+ function initMergedDirectoriesScrollbar() {
3031
+ const directoriesList = document.querySelector('.merged-directories-list');
3032
+ const directoriesContainer = document.querySelector('.merged-directories-container');
3033
+
3034
+ if (!directoriesList || !directoriesContainer) return;
3035
+
3036
+ // Function to check if scrollbar is needed
3037
+ function checkScrollbar() {
3038
+ const hasScrollbar = directoriesList.scrollHeight > directoriesList.clientHeight;
3039
+
3040
+ if (hasScrollbar) {
3041
+ directoriesContainer.classList.add('has-scrollbar');
3042
+ // Add scroll indicator
3043
+ if (!directoriesContainer.querySelector('.scroll-indicator')) {
3044
+ const indicator = document.createElement('div');
3045
+ indicator.className = 'scroll-indicator';
3046
+ indicator.innerHTML = '<i class="bi bi-chevron-down"></i>';
3047
+ indicator.style.cssText = `
3048
+ position: absolute;
3049
+ bottom: 5px;
3050
+ right: 5px;
3051
+ background: rgba(23, 162, 184, 0.8);
3052
+ color: white;
3053
+ border-radius: 50%;
3054
+ width: 20px;
3055
+ height: 20px;
3056
+ display: flex;
3057
+ align-items: center;
3058
+ justify-content: center;
3059
+ font-size: 10px;
3060
+ pointer-events: none;
3061
+ opacity: 0.7;
3062
+ transition: opacity 0.3s ease;
3063
+ `;
3064
+ directoriesContainer.appendChild(indicator);
3065
+ }
3066
+ } else {
3067
+ directoriesContainer.classList.remove('has-scrollbar');
3068
+ const indicator = directoriesContainer.querySelector('.scroll-indicator');
3069
+ if (indicator) {
3070
+ indicator.remove();
3071
+ }
3072
+ }
3073
+ }
3074
+
3075
+ // Check on load
3076
+ checkScrollbar();
3077
+
3078
+ // Check on window resize
3079
+ window.addEventListener('resize', checkScrollbar);
3080
+
3081
+ // Add scroll event listener to hide indicator when scrolled to bottom
3082
+ directoriesList.addEventListener('scroll', function() {
3083
+ const indicator = directoriesContainer.querySelector('.scroll-indicator');
3084
+ if (indicator) {
3085
+ const isAtBottom = this.scrollTop + this.clientHeight >= this.scrollHeight - 5;
3086
+ indicator.style.opacity = isAtBottom ? '0' : '0.7';
3087
+ }
3088
+ });
3089
+
3090
+ // Enhance scrollbar visibility on hover
3091
+ directoriesContainer.addEventListener('mouseenter', function() {
3092
+ directoriesList.style.scrollbarWidth = 'auto';
3093
+ });
3094
+
3095
+ directoriesContainer.addEventListener('mouseleave', function() {
3096
+ directoriesList.style.scrollbarWidth = 'thin';
3097
+ });
3098
+ }
3099
+
2398
3100
  // Crash Analysis Functions
2399
3101
  function initCrashAnalysis() {
2400
3102
  // Initialize event filtering first (this will set initial visibility)