metripy 0.3.2__py3-none-any.whl → 0.3.6__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 metripy might be problematic. Click here for more details.

@@ -0,0 +1,1080 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Files - Code Metrics Dashboard</title>
7
+ <link rel="stylesheet" href="css/styles.css">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <style>
11
+ /* File Tree Styling */
12
+ .file-tree {
13
+ max-height: 600px;
14
+ overflow-y: auto;
15
+ padding: var(--spacing-lg);
16
+ background: linear-gradient(135deg, var(--bg-primary) 0%, rgba(255, 255, 255, 0.95) 100%);
17
+ border: 1px solid var(--border-light);
18
+ border-radius: var(--radius-lg);
19
+ font-size: 0.875rem;
20
+ box-shadow: var(--shadow-sm);
21
+ }
22
+
23
+ .file-tree-legend {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: var(--spacing-sm);
27
+ padding: var(--spacing-sm) var(--spacing-md);
28
+ margin-bottom: var(--spacing-md);
29
+ background: var(--bg-tertiary);
30
+ border-radius: var(--radius-sm);
31
+ font-size: 0.75rem;
32
+ color: var(--text-secondary);
33
+ flex-wrap: wrap;
34
+ }
35
+
36
+ .legend-title {
37
+ font-weight: 600;
38
+ color: var(--text-primary);
39
+ font-size: 0.75rem;
40
+ white-space: nowrap;
41
+ }
42
+
43
+ .filter-controls {
44
+ display: flex;
45
+ gap: 0.375rem;
46
+ flex-wrap: wrap;
47
+ flex: 1;
48
+ }
49
+
50
+ .filter-btn {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ gap: 0.25rem;
54
+ padding: 0.25rem 0.5rem;
55
+ border-radius: 12px;
56
+ font-size: 0.6875rem;
57
+ font-weight: 600;
58
+ cursor: pointer;
59
+ transition: all var(--transition-fast);
60
+ border: 1.5px solid;
61
+ background: white;
62
+ user-select: none;
63
+ white-space: nowrap;
64
+ }
65
+
66
+ .filter-btn:hover {
67
+ transform: translateY(-1px);
68
+ box-shadow: var(--shadow-sm);
69
+ }
70
+
71
+ .filter-btn.active {
72
+ box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
73
+ }
74
+
75
+ .filter-btn .filter-count {
76
+ background: rgba(0,0,0,0.1);
77
+ padding: 0.0625rem 0.25rem;
78
+ border-radius: 6px;
79
+ font-size: 0.625rem;
80
+ margin-left: 0.125rem;
81
+ min-width: 1.25rem;
82
+ text-align: center;
83
+ }
84
+
85
+ .filter-btn.good {
86
+ border-color: #10b981;
87
+ color: #059669;
88
+ }
89
+
90
+ .filter-btn.good.active {
91
+ background: linear-gradient(135deg, #10b981, #059669);
92
+ color: white;
93
+ }
94
+
95
+ .filter-btn.good.active .filter-count {
96
+ background: rgba(255,255,255,0.3);
97
+ }
98
+
99
+ .filter-btn.ok {
100
+ border-color: #f59e0b;
101
+ color: #d97706;
102
+ }
103
+
104
+ .filter-btn.ok.active {
105
+ background: linear-gradient(135deg, #f59e0b, #d97706);
106
+ color: white;
107
+ }
108
+
109
+ .filter-btn.ok.active .filter-count {
110
+ background: rgba(255,255,255,0.3);
111
+ }
112
+
113
+ .filter-btn.warning {
114
+ border-color: #f97316;
115
+ color: #ea580c;
116
+ }
117
+
118
+ .filter-btn.warning.active {
119
+ background: linear-gradient(135deg, #f97316, #ea580c);
120
+ color: white;
121
+ }
122
+
123
+ .filter-btn.warning.active .filter-count {
124
+ background: rgba(255,255,255,0.3);
125
+ }
126
+
127
+ .filter-btn.critical {
128
+ border-color: #ef4444;
129
+ color: #dc2626;
130
+ }
131
+
132
+ .filter-btn.critical.active {
133
+ background: linear-gradient(135deg, #ef4444, #dc2626);
134
+ color: white;
135
+ }
136
+
137
+ .filter-btn.critical.active .filter-count {
138
+ background: rgba(255,255,255,0.3);
139
+ }
140
+
141
+ .legend-dot {
142
+ width: 8px;
143
+ height: 8px;
144
+ border-radius: 50%;
145
+ flex-shrink: 0;
146
+ }
147
+
148
+ .legend-dot.good { background: #10b981; }
149
+ .legend-dot.ok { background: #f59e0b; }
150
+ .legend-dot.warning { background: #f97316; }
151
+ .legend-dot.critical { background: #ef4444; }
152
+
153
+ /* Hidden files */
154
+ .file-tree li.filtered-out {
155
+ display: none;
156
+ }
157
+
158
+ .file-tree::-webkit-scrollbar {
159
+ width: 8px;
160
+ }
161
+
162
+ .file-tree::-webkit-scrollbar-track {
163
+ background: var(--bg-tertiary);
164
+ border-radius: 4px;
165
+ }
166
+
167
+ .file-tree::-webkit-scrollbar-thumb {
168
+ background: var(--border-medium);
169
+ border-radius: 4px;
170
+ }
171
+
172
+ .file-tree::-webkit-scrollbar-thumb:hover {
173
+ background: var(--text-secondary);
174
+ }
175
+
176
+ .file-tree ul {
177
+ list-style: none;
178
+ padding-left: 1.5rem;
179
+ margin: 0.25rem 0;
180
+ position: relative;
181
+ }
182
+
183
+ .file-tree ul::before {
184
+ content: '';
185
+ position: absolute;
186
+ top: 0;
187
+ left: 0.5rem;
188
+ bottom: 0;
189
+ width: 2px;
190
+ background: linear-gradient(180deg, var(--border-light), transparent);
191
+ }
192
+
193
+ .file-tree > ul {
194
+ padding-left: 0;
195
+ }
196
+
197
+ .file-tree > ul::before {
198
+ display: none;
199
+ }
200
+
201
+ .file-tree li {
202
+ position: relative;
203
+ margin: 0.125rem 0;
204
+ padding-left: 1rem;
205
+ }
206
+
207
+ .file-tree li::before {
208
+ content: '';
209
+ position: absolute;
210
+ top: 0.875rem;
211
+ left: 0.5rem;
212
+ width: 0.75rem;
213
+ height: 2px;
214
+ background: var(--border-light);
215
+ }
216
+
217
+ .file-tree > ul > li::before {
218
+ display: none;
219
+ }
220
+
221
+ .file-tree li > i {
222
+ margin-right: 0.5rem;
223
+ color: var(--text-secondary);
224
+ font-size: 0.875rem;
225
+ transition: color var(--transition-fast);
226
+ }
227
+
228
+ .file-tree .folder,
229
+ .file-tree .file {
230
+ display: inline-block;
231
+ cursor: pointer;
232
+ padding: 0.375rem 0.625rem;
233
+ padding-left: 0.75rem;
234
+ border-radius: var(--radius-md);
235
+ transition: all var(--transition-fast);
236
+ color: var(--text-primary);
237
+ font-weight: 500;
238
+ border-left: 3px solid transparent;
239
+ margin-left: -3px;
240
+ }
241
+
242
+ /* File health borders */
243
+ .file-tree .file.health-good {
244
+ border-left-color: #10b981;
245
+ }
246
+
247
+ .file-tree .file.health-ok {
248
+ border-left-color: #f59e0b;
249
+ }
250
+
251
+ .file-tree .file.health-warning {
252
+ border-left-color: #f97316;
253
+ }
254
+
255
+ .file-tree .file.health-critical {
256
+ border-left-color: #ef4444;
257
+ }
258
+
259
+ .file-tree .folder {
260
+ color: var(--primary-color);
261
+ font-weight: 600;
262
+ }
263
+
264
+ .file-tree .folder:hover {
265
+ background-color: rgba(59, 130, 246, 0.08);
266
+ color: var(--primary-dark);
267
+ transform: translateX(2px);
268
+ }
269
+
270
+ .file-tree .folder:hover + ul {
271
+ border-left-color: var(--primary-color);
272
+ }
273
+
274
+ .file-tree .file:hover {
275
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.05), rgba(59, 130, 246, 0.1));
276
+ color: var(--primary-color);
277
+ transform: translateX(2px);
278
+ }
279
+
280
+ /* Enhanced hover for health borders */
281
+ .file-tree .file.health-good:hover {
282
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.05), rgba(16, 185, 129, 0.1));
283
+ box-shadow: inset 3px 0 0 0 #10b981;
284
+ }
285
+
286
+ .file-tree .file.health-ok:hover {
287
+ background: linear-gradient(135deg, rgba(245, 158, 11, 0.05), rgba(245, 158, 11, 0.1));
288
+ box-shadow: inset 3px 0 0 0 #f59e0b;
289
+ }
290
+
291
+ .file-tree .file.health-warning:hover {
292
+ background: linear-gradient(135deg, rgba(249, 115, 22, 0.05), rgba(249, 115, 22, 0.1));
293
+ box-shadow: inset 3px 0 0 0 #f97316;
294
+ }
295
+
296
+ .file-tree .file.health-critical:hover {
297
+ background: linear-gradient(135deg, rgba(239, 68, 68, 0.05), rgba(239, 68, 68, 0.1));
298
+ box-shadow: inset 3px 0 0 0 #ef4444;
299
+ }
300
+
301
+ .file-tree .folder i {
302
+ color: var(--warning-color);
303
+ }
304
+
305
+ .file-tree .file i {
306
+ color: var(--info-color);
307
+ }
308
+
309
+ /* File health indicators */
310
+ .file-health-indicator {
311
+ display: inline-block;
312
+ width: 8px;
313
+ height: 8px;
314
+ border-radius: 50%;
315
+ margin-left: 0.5rem;
316
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9);
317
+ transition: all var(--transition-fast);
318
+ }
319
+
320
+ .file-health-indicator.good {
321
+ background: #10b981;
322
+ }
323
+
324
+ .file-health-indicator.ok {
325
+ background: #f59e0b;
326
+ }
327
+
328
+ .file-health-indicator.warning {
329
+ background: #f97316;
330
+ }
331
+
332
+ .file-health-indicator.critical {
333
+ background: #ef4444;
334
+ animation: pulse-critical 2s infinite;
335
+ }
336
+
337
+ @keyframes pulse-critical {
338
+ 0%, 100% {
339
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9), 0 0 0 4px rgba(239, 68, 68, 0.3);
340
+ }
341
+ 50% {
342
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9), 0 0 0 6px rgba(239, 68, 68, 0.1);
343
+ }
344
+ }
345
+
346
+ .file-tree .file:hover .file-health-indicator {
347
+ transform: scale(1.3);
348
+ }
349
+
350
+ /* Priority badge for files */
351
+ .file-priority-badge {
352
+ display: inline-block;
353
+ margin-left: 0.5rem;
354
+ padding: 0.125rem 0.375rem;
355
+ border-radius: 8px;
356
+ font-size: 0.625rem;
357
+ font-weight: 700;
358
+ text-transform: uppercase;
359
+ letter-spacing: 0.05em;
360
+ vertical-align: middle;
361
+ }
362
+
363
+ .file-priority-badge.critical {
364
+ background: #ef4444;
365
+ color: white;
366
+ }
367
+
368
+ .file-priority-badge.warning {
369
+ background: #f97316;
370
+ color: white;
371
+ }
372
+
373
+ .file-priority-badge.ok {
374
+ background: #f59e0b;
375
+ color: white;
376
+ }
377
+
378
+ /* File Details Styling */
379
+ .file-view {
380
+ min-height: 400px;
381
+ }
382
+
383
+ .file-details {
384
+ background: linear-gradient(135deg, var(--bg-primary) 0%, rgba(255, 255, 255, 0.98) 100%);
385
+ border: 1px solid var(--border-light);
386
+ padding: var(--spacing-xl);
387
+ border-radius: var(--radius-lg);
388
+ box-shadow: var(--shadow-sm);
389
+ }
390
+
391
+ .file-details h2 {
392
+ font-size: 1.5rem;
393
+ font-weight: 600;
394
+ color: var(--text-primary);
395
+ margin-bottom: var(--spacing-lg);
396
+ padding-bottom: var(--spacing-md);
397
+ border-bottom: 2px solid var(--primary-color);
398
+ display: flex;
399
+ align-items: center;
400
+ gap: var(--spacing-sm);
401
+ }
402
+
403
+ .file-details h2 i {
404
+ color: var(--primary-color);
405
+ font-size: 1.75rem;
406
+ }
407
+
408
+ .file-details > p {
409
+ display: inline-flex;
410
+ align-items: center;
411
+ padding: var(--spacing-sm) var(--spacing-md);
412
+ margin: 0 var(--spacing-sm) var(--spacing-sm) 0;
413
+ background-color: var(--bg-tertiary);
414
+ border-radius: var(--radius-md);
415
+ font-size: 0.875rem;
416
+ color: var(--text-secondary);
417
+ border: 1px solid var(--border-light);
418
+ }
419
+
420
+ .file-details > p strong {
421
+ color: var(--text-primary);
422
+ margin-right: 0.5rem;
423
+ font-weight: 600;
424
+ }
425
+
426
+ /* Metric badges with color coding */
427
+ .metric-badge {
428
+ display: inline-flex;
429
+ align-items: center;
430
+ gap: var(--spacing-sm);
431
+ padding: var(--spacing-sm) var(--spacing-md);
432
+ border-radius: var(--radius-md);
433
+ font-size: 0.875rem;
434
+ font-weight: 500;
435
+ border: 2px solid;
436
+ transition: all var(--transition-fast);
437
+ flex: 0 1 auto;
438
+ }
439
+
440
+ .metric-badge:hover {
441
+ transform: translateY(-2px);
442
+ box-shadow: var(--shadow-md);
443
+ }
444
+
445
+ .metric-badge .label {
446
+ font-weight: 600;
447
+ margin-right: 0.25rem;
448
+ }
449
+
450
+ .metric-badge .value {
451
+ font-weight: 700;
452
+ }
453
+
454
+ .metric-badge .segment-indicator {
455
+ margin-left: 0.5rem;
456
+ padding: 0.125rem 0.5rem;
457
+ border-radius: 10px;
458
+ font-size: 0.75rem;
459
+ font-weight: 700;
460
+ text-transform: uppercase;
461
+ letter-spacing: 0.025em;
462
+ }
463
+
464
+ /* Good segment */
465
+ .metric-badge.good {
466
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.1), rgba(16, 185, 129, 0.15));
467
+ border-color: #10b981;
468
+ color: #059669;
469
+ }
470
+
471
+ .metric-badge.good .segment-indicator {
472
+ background: #10b981;
473
+ color: white;
474
+ }
475
+
476
+ /* Ok segment */
477
+ .metric-badge.ok {
478
+ background: linear-gradient(135deg, rgba(245, 158, 11, 0.1), rgba(245, 158, 11, 0.15));
479
+ border-color: #f59e0b;
480
+ color: #d97706;
481
+ }
482
+
483
+ .metric-badge.ok .segment-indicator {
484
+ background: #f59e0b;
485
+ color: white;
486
+ }
487
+
488
+ /* Warning segment */
489
+ .metric-badge.warning {
490
+ background: linear-gradient(135deg, rgba(249, 115, 22, 0.1), rgba(249, 115, 22, 0.15));
491
+ border-color: #f97316;
492
+ color: #ea580c;
493
+ }
494
+
495
+ .metric-badge.warning .segment-indicator {
496
+ background: #f97316;
497
+ color: white;
498
+ }
499
+
500
+ /* Critical segment */
501
+ .metric-badge.critical {
502
+ background: linear-gradient(135deg, rgba(239, 68, 68, 0.1), rgba(239, 68, 68, 0.15));
503
+ border-color: #ef4444;
504
+ color: #dc2626;
505
+ }
506
+
507
+ .metric-badge.critical .segment-indicator {
508
+ background: #ef4444;
509
+ color: white;
510
+ }
511
+
512
+ .file-details hr {
513
+ border: none;
514
+ height: 1px;
515
+ background: linear-gradient(90deg, transparent, var(--border-light), transparent);
516
+ margin: var(--spacing-xl) 0 var(--spacing-lg) 0;
517
+ }
518
+
519
+ .file-details h3 {
520
+ font-size: 1.125rem;
521
+ font-weight: 600;
522
+ color: var(--text-primary);
523
+ margin: var(--spacing-lg) 0 var(--spacing-md) 0;
524
+ display: flex;
525
+ align-items: center;
526
+ gap: var(--spacing-sm);
527
+ }
528
+
529
+ .file-details h3::before {
530
+ content: '';
531
+ width: 4px;
532
+ height: 1.125rem;
533
+ background: linear-gradient(180deg, var(--primary-color), var(--info-color));
534
+ border-radius: 2px;
535
+ }
536
+
537
+ .file-details ul {
538
+ list-style: none;
539
+ padding-left: 0;
540
+ margin: var(--spacing-md) 0;
541
+ }
542
+
543
+ .file-details > ul > li {
544
+ margin-bottom: var(--spacing-md);
545
+ padding: var(--spacing-md);
546
+ background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
547
+ border-radius: var(--radius-md);
548
+ border-left: 4px solid var(--primary-color);
549
+ transition: all var(--transition-fast);
550
+ }
551
+
552
+ .file-details > ul > li:hover {
553
+ transform: translateX(4px);
554
+ box-shadow: var(--shadow-md);
555
+ border-left-color: var(--primary-dark);
556
+ }
557
+
558
+ .file-details > ul > li > strong {
559
+ color: var(--primary-color);
560
+ font-weight: 600;
561
+ font-size: 0.9375rem;
562
+ }
563
+
564
+ .file-details .methods {
565
+ list-style: none;
566
+ padding-left: var(--spacing-lg);
567
+ margin-top: var(--spacing-sm);
568
+ }
569
+
570
+ .file-details .methods li {
571
+ padding: var(--spacing-sm) var(--spacing-md);
572
+ margin: var(--spacing-xs) 0;
573
+ background-color: var(--bg-primary);
574
+ border-radius: var(--radius-sm);
575
+ font-size: 0.875rem;
576
+ color: var(--text-secondary);
577
+ border-left: 3px solid var(--info-color);
578
+ transition: all var(--transition-fast);
579
+ }
580
+
581
+ .file-details .methods li:hover {
582
+ background-color: rgba(6, 182, 212, 0.05);
583
+ border-left-color: var(--info-color);
584
+ transform: translateX(4px);
585
+ }
586
+
587
+ /* Complexity indicators */
588
+ .complexity-indicator {
589
+ display: inline-flex;
590
+ align-items: center;
591
+ padding: 0.125rem 0.5rem;
592
+ border-radius: 10px;
593
+ font-size: 0.75rem;
594
+ font-weight: 700;
595
+ margin-left: 0.5rem;
596
+ }
597
+
598
+ .complexity-indicator.good {
599
+ background: #10b981;
600
+ color: white;
601
+ }
602
+
603
+ .complexity-indicator.ok {
604
+ background: #f59e0b;
605
+ color: white;
606
+ }
607
+
608
+ .complexity-indicator.warning {
609
+ background: #f97316;
610
+ color: white;
611
+ }
612
+
613
+ .complexity-indicator.critical {
614
+ background: #ef4444;
615
+ color: white;
616
+ }
617
+
618
+ /* Empty state */
619
+ .file-view:empty::before {
620
+ content: '👈 Select a file from the tree to view details';
621
+ display: flex;
622
+ align-items: center;
623
+ justify-content: center;
624
+ height: 400px;
625
+ color: var(--text-muted);
626
+ font-size: 1rem;
627
+ text-align: center;
628
+ background: linear-gradient(135deg, var(--bg-secondary), var(--bg-tertiary));
629
+ border-radius: var(--radius-lg);
630
+ border: 2px dashed var(--border-light);
631
+ }
632
+
633
+ /* Responsive adjustments */
634
+ @media (max-width: 768px) {
635
+ .file-tree {
636
+ max-height: 400px;
637
+ }
638
+
639
+ .file-details {
640
+ padding: var(--spacing-lg);
641
+ }
642
+
643
+ .file-details h2 {
644
+ font-size: 1.25rem;
645
+ }
646
+
647
+ .metrics-summary {
648
+ flex-direction: column !important;
649
+ }
650
+
651
+ .metric-badge {
652
+ width: 100%;
653
+ justify-content: space-between;
654
+ }
655
+
656
+ .metric-badge .segment-indicator {
657
+ margin-left: auto;
658
+ }
659
+
660
+ .file-tree-legend {
661
+ flex-direction: column;
662
+ align-items: flex-start;
663
+ }
664
+
665
+ .legend-title {
666
+ width: 100%;
667
+ }
668
+
669
+ .filter-controls {
670
+ width: 100%;
671
+ }
672
+
673
+ .filter-btn {
674
+ flex: 1;
675
+ min-width: 0;
676
+ justify-content: center;
677
+ padding: 0.25rem 0.375rem;
678
+ }
679
+ }
680
+ </style>
681
+ </head>
682
+ <body>
683
+ <div class="dashboard">
684
+ <!-- Sidebar -->
685
+ <aside class="sidebar">
686
+ <div class="sidebar-header">
687
+ <div class="logo">
688
+ <img src="images/logo.svg" alt="Metripy Logo" class="logo-icon">
689
+ <h2>Metripy</h2>
690
+ </div>
691
+ </div>
692
+
693
+ <nav class="sidebar-nav">
694
+ <ul>
695
+ <li class="nav-item">
696
+ <a href="index.html" class="nav-link">
697
+ <i class="fas fa-tachometer-alt"></i>
698
+ <span>Overview</span>
699
+ </a>
700
+ </li>
701
+ <li class="nav-item active">
702
+ <a href="files.html" class="nav-link">
703
+ <i class="fas fa-file-code"></i>
704
+ <span>Files</span>
705
+ </a>
706
+ </li>
707
+ <li class="nav-item">
708
+ <a href="top_offenders.html" class="nav-link">
709
+ <i class="fa-solid fa-stethoscope"></i>
710
+ <span>Top Offenders</span>
711
+ </a>
712
+ </li>
713
+ <li class="nav-item">
714
+ <a href="git_analysis.html" class="nav-link">
715
+ <i class="fab fa-git-alt"></i>
716
+ <span>Git Analysis</span>
717
+ </a>
718
+ </li>
719
+ <li class="nav-item">
720
+ <a href="dependencies.html" class="nav-link">
721
+ <i class="fas fa-cubes"></i>
722
+ <span>Dependencies</span>
723
+ </a>
724
+ </li>
725
+ <li class="nav-item">
726
+ <a href="trends.html" class="nav-link">
727
+ <i class="fas fa-chart-line"></i>
728
+ <span>Trends</span>
729
+ </a>
730
+ </li>
731
+ </ul>
732
+ </nav>
733
+
734
+ <div class="sidebar-footer">
735
+ <div class="project-info">
736
+ <h4>Project: {{project_name}}</h4>
737
+ <p>Last updated: {{last_updated}}</p>
738
+ </div>
739
+ </div>
740
+ </aside>
741
+
742
+ <!-- Main Content -->
743
+ <main class="main-content">
744
+ <header class="page-header">
745
+ <div class="header-content">
746
+ <h1>File Details</h1>
747
+ <p class="subtitle">File details and analysis</p>
748
+ </div>
749
+ </header>
750
+
751
+ <section class="two-column-section">
752
+ <div class="info-card">
753
+ <h4>File List</h4>
754
+ <div class="file-tree" id="filetree">
755
+ <!-- filled by JS-->
756
+ </div>
757
+ </div>
758
+ <div class="info-card">
759
+ <h4>File View</h4>
760
+ <div class="file-view" id="fileview">
761
+ <!-- filled by JS-->
762
+ </div>
763
+ </div>
764
+ </section>
765
+ </main>
766
+ </div>
767
+
768
+ <!-- Scripts -->
769
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
770
+ <script>
771
+ // Embed data directly from template engine
772
+ </script>
773
+ <script>
774
+ const fileTreeData = {{filetree}};
775
+ const fileDetails = {{file_details}};
776
+
777
+ function getIconClass(name, numChildren) {
778
+ const file_ext = name.split(".").pop();
779
+ if (numChildren) {
780
+ return 'fa-regular fa-folder';
781
+ } else if (file_ext === "php") {
782
+ return 'fa-brands fa-php';
783
+ } else if (file_ext === "py") {
784
+ return 'fa-brands fa-python';
785
+ } else {
786
+ return 'fa-regular fa-file';
787
+ }
788
+ }
789
+
790
+ // Helper function to determine the worst segment for a file
791
+ function getWorstSegment(fullName) {
792
+ const details = fileDetails[fullName];
793
+ if (!details) return null;
794
+
795
+ const segments = [
796
+ details.loc_segment,
797
+ details.complexity_segment,
798
+ details.maintainability_segment,
799
+ details.method_size_segment
800
+ ];
801
+
802
+ // Priority: critical > warning > ok > good
803
+ if (segments.includes('critical')) return 'critical';
804
+ if (segments.includes('warning')) return 'warning';
805
+ if (segments.includes('ok')) return 'ok';
806
+ return 'good';
807
+ }
808
+
809
+ // Helper function to create health indicator HTML
810
+ function getHealthIndicatorHTML(fullName) {
811
+ const segment = getWorstSegment(fullName);
812
+ if (!segment) return '';
813
+
814
+ if (segment === 'critical') {
815
+ return `<span class="file-health-indicator ${segment}" title="Needs immediate attention"></span><span class="file-priority-badge critical">!</span>`;
816
+ } else if (segment === 'warning') {
817
+ return `<span class="file-health-indicator ${segment}" title="Needs attention"></span>`;
818
+ } else if (segment === 'ok') {
819
+ return `<span class="file-health-indicator ${segment}" title="Could be improved"></span>`;
820
+ } else {
821
+ return `<span class="file-health-indicator ${segment}" title="Healthy"></span>`;
822
+ }
823
+ }
824
+
825
+ function renderTree(node) {
826
+ if (!node) return '';
827
+
828
+ const icon = getIconClass(node.name, node.children.length);
829
+ const isFile = node.children.length === 0;
830
+ const healthIndicator = isFile ? getHealthIndicatorHTML(node.full_name) : '';
831
+
832
+ // Add health class to files
833
+ let className = node.children.length ? 'folder' : 'file';
834
+ if (isFile) {
835
+ const segment = getWorstSegment(node.full_name);
836
+ if (segment) {
837
+ className += ` health-${segment}`;
838
+ }
839
+ }
840
+
841
+ let html = `<li><i class="${icon}"></i><span class="${className}" id="${node.full_name}"> ${node.name}</span>${healthIndicator}`;
842
+ if (node.children.length > 0) {
843
+ html += `<ul>`;
844
+ node.children.forEach(child => {
845
+ html += renderTree(child);
846
+ });
847
+ html += `</ul>`;
848
+ }
849
+
850
+ html += `</li>`;
851
+ return html;
852
+ }
853
+
854
+ // Count files by health status
855
+ function countFilesByHealth() {
856
+ const counts = { good: 0, ok: 0, warning: 0, critical: 0, total: 0 };
857
+
858
+ function countInNode(node) {
859
+ if (!node) return;
860
+
861
+ if (node.children.length === 0) {
862
+ // It's a file
863
+ const segment = getWorstSegment(node.full_name);
864
+ if (segment) {
865
+ counts[segment]++;
866
+ counts.total++;
867
+ }
868
+ } else {
869
+ // It's a folder, recurse
870
+ node.children.forEach(child => countInNode(child));
871
+ }
872
+ }
873
+
874
+ countInNode(fileTreeData);
875
+ return counts;
876
+ }
877
+
878
+ const healthCounts = countFilesByHealth();
879
+
880
+ const fileTree = document.getElementById('filetree');
881
+ const legendHTML = `
882
+ <div class="file-tree-legend">
883
+ <span class="legend-title">Filter:</span>
884
+ <div class="filter-controls">
885
+ <div class="filter-btn good active" data-filter="good">
886
+ <span class="legend-dot good"></span>
887
+ <span>Healthy</span>
888
+ <span class="filter-count">${healthCounts.good}</span>
889
+ </div>
890
+ <div class="filter-btn ok active" data-filter="ok">
891
+ <span class="legend-dot ok"></span>
892
+ <span>OK</span>
893
+ <span class="filter-count">${healthCounts.ok}</span>
894
+ </div>
895
+ <div class="filter-btn warning active" data-filter="warning">
896
+ <span class="legend-dot warning"></span>
897
+ <span>Needs Attention</span>
898
+ <span class="filter-count">${healthCounts.warning}</span>
899
+ </div>
900
+ <div class="filter-btn critical active" data-filter="critical">
901
+ <span class="legend-dot critical"></span>
902
+ <span>Critical</span>
903
+ <span class="filter-count">${healthCounts.critical}</span>
904
+ </div>
905
+ </div>
906
+ </div>
907
+ `;
908
+ fileTree.innerHTML = legendHTML + `<ul>${renderTree(fileTreeData)}</ul>`
909
+
910
+ // Set up filtering functionality
911
+ const activeFilters = new Set(['good', 'ok', 'warning', 'critical']);
912
+
913
+ function applyFilters() {
914
+ const allFiles = fileTree.querySelectorAll('li');
915
+
916
+ allFiles.forEach(li => {
917
+ const fileSpan = li.querySelector('.file');
918
+ if (!fileSpan) {
919
+ // It's a folder or has no file, check if it has visible children
920
+ li.classList.remove('filtered-out');
921
+ return;
922
+ }
923
+
924
+ // Get the health class from the file span
925
+ let fileHealth = null;
926
+ if (fileSpan.classList.contains('health-good')) fileHealth = 'good';
927
+ else if (fileSpan.classList.contains('health-ok')) fileHealth = 'ok';
928
+ else if (fileSpan.classList.contains('health-warning')) fileHealth = 'warning';
929
+ else if (fileSpan.classList.contains('health-critical')) fileHealth = 'critical';
930
+
931
+ // Show or hide based on active filters
932
+ if (fileHealth && activeFilters.has(fileHealth)) {
933
+ li.classList.remove('filtered-out');
934
+ } else {
935
+ li.classList.add('filtered-out');
936
+ }
937
+ });
938
+
939
+ // Hide empty folders
940
+ const allFolders = fileTree.querySelectorAll('li');
941
+ allFolders.forEach(li => {
942
+ const folderSpan = li.querySelector('.folder');
943
+ if (folderSpan) {
944
+ const ul = li.querySelector('ul');
945
+ if (ul) {
946
+ const visibleChildren = Array.from(ul.children).filter(
947
+ child => !child.classList.contains('filtered-out')
948
+ );
949
+ if (visibleChildren.length === 0) {
950
+ li.classList.add('filtered-out');
951
+ } else {
952
+ li.classList.remove('filtered-out');
953
+ }
954
+ }
955
+ }
956
+ });
957
+ }
958
+
959
+ // Add click handlers to filter buttons
960
+ document.querySelectorAll('.filter-btn').forEach(btn => {
961
+ btn.addEventListener('click', () => {
962
+ const filter = btn.dataset.filter;
963
+
964
+ // Toggle individual filter
965
+ if (activeFilters.has(filter)) {
966
+ activeFilters.delete(filter);
967
+ btn.classList.remove('active');
968
+ } else {
969
+ activeFilters.add(filter);
970
+ btn.classList.add('active');
971
+ }
972
+
973
+ applyFilters();
974
+ });
975
+ });
976
+
977
+ document.querySelectorAll('.folder').forEach(folder => {
978
+ folder.addEventListener('click', () => {
979
+ const nextUl = folder.nextElementSibling;
980
+ if (nextUl && nextUl.tagName === 'UL') {
981
+ nextUl.style.display = nextUl.style.display === 'none' ? 'block' : 'none';
982
+ }
983
+ });
984
+ });
985
+
986
+ const fileView = document.getElementById('fileview');
987
+ document.querySelectorAll('.file').forEach(file => {
988
+ file.addEventListener('click', () => {
989
+ console.log('file clicked', file.id);
990
+ const details = fileDetails[file.id];
991
+ console.log(details);
992
+
993
+ // Extract file name from full path
994
+ const fileName = file.id.split('/').pop();
995
+
996
+ // Collect all method names to filter from global functions
997
+ const methodNames = new Set();
998
+
999
+ const icon = getIconClass(fileName, 0);
1000
+
1001
+ // Helper function to get segment label
1002
+ const getSegmentLabel = (segment) => {
1003
+ const labels = {
1004
+ 'good': '✓ Good',
1005
+ 'ok': '○ OK',
1006
+ 'warning': '⚠ Warning',
1007
+ 'critical': '✗ Critical'
1008
+ };
1009
+ return labels[segment] || segment;
1010
+ };
1011
+
1012
+ let html = `
1013
+ <div class="file-details">
1014
+ <h2><i class="${icon}"></i> ${fileName}</h2>
1015
+ <div class="metrics-summary" style="margin-bottom: var(--spacing-lg); display: flex; flex-wrap: wrap; gap: var(--spacing-sm);">
1016
+ <div class="metric-badge ${details.loc_segment}">
1017
+ <span class="label">Lines of Code:</span>
1018
+ <span class="value">${details.loc}</span>
1019
+ <span class="segment-indicator">${getSegmentLabel(details.loc_segment)}</span>
1020
+ </div>
1021
+ <div class="metric-badge ${details.maintainability_segment}">
1022
+ <span class="label">Maintainability:</span>
1023
+ <span class="value">${details.maintainabilityIndex}</span>
1024
+ <span class="segment-indicator">${getSegmentLabel(details.maintainability_segment)}</span>
1025
+ </div>
1026
+ <div class="metric-badge ${details.complexity_segment}">
1027
+ <span class="label">Avg Complexity:</span>
1028
+ <span class="value">${details.avgCcPerFunction.toFixed(2)}</span>
1029
+ <span class="segment-indicator">${getSegmentLabel(details.complexity_segment)}</span>
1030
+ </div>
1031
+ <div class="metric-badge ${details.method_size_segment}">
1032
+ <span class="label">Avg Method Size:</span>
1033
+ <span class="value">${details.avgLocPerFunction.toFixed(2)}</span>
1034
+ <span class="segment-indicator">${getSegmentLabel(details.method_size_segment)}</span>
1035
+ </div>
1036
+ </div>
1037
+
1038
+ <hr/>
1039
+ <h3>Classes</h3>
1040
+ <ul>
1041
+ `;
1042
+
1043
+ details.class_nodes.forEach(node => {
1044
+ const classComplexitySegment = node.real_complexity;
1045
+ html += `<li><strong>${node.name}</strong> — Complexity: ${node.real_complexity}<span class="complexity-indicator ${classComplexitySegment}">${getSegmentLabel(classComplexitySegment)}</span>`;
1046
+ if (node.functions && node.functions.length > 0) {
1047
+ html += `<ul class="methods">`;
1048
+ node.functions.forEach(method => {
1049
+ const methodComplexitySegment = method.complexity_segment;
1050
+ html += `<li>${method.name}() — Complexity: ${method.complexity}<span class="complexity-indicator ${methodComplexitySegment}">${getSegmentLabel(methodComplexitySegment)}</span></li>`;
1051
+ methodNames.add(method.name)
1052
+ });
1053
+ html += `</ul>`;
1054
+ }
1055
+ html += `</li>`;
1056
+ });
1057
+
1058
+ html += `
1059
+ </ul>
1060
+ <hr/>
1061
+ <h3>Global Functions</h3>
1062
+ <ul>
1063
+ `;
1064
+
1065
+ details.function_nodes.forEach(node => {
1066
+ if (!methodNames.has(node.name)) {
1067
+ const functionComplexitySegment = node.complexity_segment;
1068
+ html += `<li><strong>${node.name}</strong> — Complexity: ${node.complexity}<span class="complexity-indicator ${functionComplexitySegment}">${getSegmentLabel(functionComplexitySegment)}</span></li>`;
1069
+ }
1070
+ });
1071
+
1072
+ html += `</ul></div>`;
1073
+
1074
+ fileView.innerHTML = html;
1075
+
1076
+ });
1077
+ });
1078
+ </script>
1079
+ </body>
1080
+ </html>