solidstats 2.0.0 → 3.0.0.beta.1

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.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -63
  3. data/README.md +27 -33
  4. data/Rakefile +3 -3
  5. data/app/assets/stylesheets/solidstats/application.css +1 -6
  6. data/app/assets/stylesheets/solidstats/dashboard.css +48 -0
  7. data/app/controllers/solidstats/dashboard_controller.rb +81 -62
  8. data/app/controllers/solidstats/logs_controller.rb +72 -0
  9. data/app/controllers/solidstats/performance_controller.rb +25 -0
  10. data/app/controllers/solidstats/productivity_controller.rb +39 -0
  11. data/app/controllers/solidstats/quality_controller.rb +152 -0
  12. data/app/controllers/solidstats/securities_controller.rb +30 -0
  13. data/app/helpers/solidstats/application_helper.rb +124 -11
  14. data/app/helpers/solidstats/performance_helper.rb +87 -0
  15. data/app/helpers/solidstats/productivity_helper.rb +38 -0
  16. data/app/services/solidstats/bundler_audit_service.rb +206 -0
  17. data/app/services/solidstats/coverage_compass_service.rb +335 -0
  18. data/app/services/solidstats/load_lens_service.rb +454 -0
  19. data/app/services/solidstats/log_size_monitor_service.rb +207 -76
  20. data/app/services/solidstats/my_todo_service.rb +242 -0
  21. data/app/services/solidstats/style_patrol_service.rb +319 -0
  22. data/app/views/layouts/solidstats/application.html.erb +8 -2
  23. data/app/views/layouts/solidstats/dashboard.html.erb +84 -0
  24. data/app/views/solidstats/dashboard/dashboard.html.erb +39 -0
  25. data/app/views/solidstats/logs/logs_size.html.erb +409 -0
  26. data/app/views/solidstats/performance/load_lens.html.erb +158 -0
  27. data/app/views/solidstats/productivity/_todo_list.html.erb +49 -0
  28. data/app/views/solidstats/productivity/my_todos.html.erb +84 -0
  29. data/app/views/solidstats/quality/coverage_compass.html.erb +420 -0
  30. data/app/views/solidstats/quality/style_patrol.html.erb +463 -0
  31. data/app/views/solidstats/securities/bundler_audit.html.erb +345 -0
  32. data/app/views/solidstats/shared/_dashboard_card.html.erb +160 -0
  33. data/app/views/solidstats/shared/_quick_actions.html.erb +26 -0
  34. data/config/routes.rb +32 -7
  35. data/lib/generators/solidstats/install/install_generator.rb +28 -2
  36. data/lib/generators/solidstats/install/templates/README +7 -0
  37. data/lib/solidstats/engine.rb +9 -114
  38. data/lib/solidstats/version.rb +1 -1
  39. data/lib/solidstats.rb +2 -299
  40. data/lib/tasks/solidstats_install.rake +2 -122
  41. data/lib/tasks/solidstats_performance.rake +84 -0
  42. metadata +32 -103
  43. data/app/assets/javascripts/solidstats/application.js +0 -257
  44. data/app/assets/javascripts/solidstats/dashboard.js +0 -225
  45. data/app/assets/javascripts/solidstats/gem_metadata.js +0 -554
  46. data/app/assets/stylesheets/solidstats/components/action_button.css +0 -99
  47. data/app/assets/stylesheets/solidstats/components/dashboard.css +0 -151
  48. data/app/assets/stylesheets/solidstats/components/dashboard_header.css +0 -93
  49. data/app/assets/stylesheets/solidstats/components/dashboard_layout.css +0 -97
  50. data/app/assets/stylesheets/solidstats/components/gem_metadata.css +0 -1403
  51. data/app/assets/stylesheets/solidstats/components/navigation.css +0 -80
  52. data/app/assets/stylesheets/solidstats/components/quick_navigation.css +0 -54
  53. data/app/assets/stylesheets/solidstats/components/security.css +0 -332
  54. data/app/assets/stylesheets/solidstats/components/status_badge.css +0 -58
  55. data/app/assets/stylesheets/solidstats/components/summary_card.css +0 -66
  56. data/app/assets/stylesheets/solidstats/components/tab_navigation.css +0 -95
  57. data/app/components/solidstats/base_component.rb +0 -88
  58. data/app/components/solidstats/code_quality/code_quality_section_component.html.erb +0 -0
  59. data/app/components/solidstats/code_quality/code_quality_section_component.rb +0 -0
  60. data/app/components/solidstats/code_quality/section_component.html.erb +0 -45
  61. data/app/components/solidstats/code_quality/section_component.rb +0 -34
  62. data/app/components/solidstats/dashboard_header_component.html.erb +0 -39
  63. data/app/components/solidstats/dashboard_header_component.rb +0 -33
  64. data/app/components/solidstats/previews/action_button_component_preview/button_vs_link.html.erb +0 -6
  65. data/app/components/solidstats/previews/action_button_component_preview/sizes.html.erb +0 -6
  66. data/app/components/solidstats/previews/action_button_component_preview/variants.html.erb +0 -6
  67. data/app/components/solidstats/previews/action_button_component_preview/with_icons.html.erb +0 -6
  68. data/app/components/solidstats/previews/action_button_component_preview.rb +0 -64
  69. data/app/components/solidstats/previews/navigation_component_preview.rb +0 -74
  70. data/app/components/solidstats/previews/stats_overview_component_preview.rb +0 -100
  71. data/app/components/solidstats/previews/status_badge_component_preview/sizes.html.erb +0 -6
  72. data/app/components/solidstats/previews/status_badge_component_preview/statuses.html.erb +0 -6
  73. data/app/components/solidstats/previews/status_badge_component_preview/with_icons.html.erb +0 -6
  74. data/app/components/solidstats/previews/status_badge_component_preview.rb +0 -49
  75. data/app/components/solidstats/previews/summary_card_component_preview/clickable.html.erb +0 -9
  76. data/app/components/solidstats/previews/summary_card_component_preview/dashboard_layout.html.erb +0 -9
  77. data/app/components/solidstats/previews/summary_card_component_preview/statuses.html.erb +0 -6
  78. data/app/components/solidstats/previews/summary_card_component_preview/value_formats.html.erb +0 -6
  79. data/app/components/solidstats/previews/summary_card_component_preview.rb +0 -67
  80. data/app/components/solidstats/quick_navigation_component.html.erb +0 -8
  81. data/app/components/solidstats/quick_navigation_component.rb +0 -21
  82. data/app/components/solidstats/security/gem_impact_analysis_component.html.erb +0 -44
  83. data/app/components/solidstats/security/gem_impact_analysis_component.rb +0 -45
  84. data/app/components/solidstats/security/overview_component.html.erb +0 -21
  85. data/app/components/solidstats/security/overview_component.rb +0 -104
  86. data/app/components/solidstats/security/section_component.html.erb +0 -26
  87. data/app/components/solidstats/security/section_component.rb +0 -52
  88. data/app/components/solidstats/security/timeline_component.html.erb +0 -39
  89. data/app/components/solidstats/security/timeline_component.rb +0 -43
  90. data/app/components/solidstats/tasks_section_component.html.erb +0 -17
  91. data/app/components/solidstats/tasks_section_component.rb +0 -22
  92. data/app/components/solidstats/ui/action_button_component.html.erb +0 -6
  93. data/app/components/solidstats/ui/action_button_component.rb +0 -71
  94. data/app/components/solidstats/ui/dashboard_layout_component.html.erb +0 -19
  95. data/app/components/solidstats/ui/dashboard_layout_component.rb +0 -85
  96. data/app/components/solidstats/ui/navigation_component.html.erb +0 -34
  97. data/app/components/solidstats/ui/navigation_component.rb +0 -72
  98. data/app/components/solidstats/ui/stats_overview_component.html.erb +0 -14
  99. data/app/components/solidstats/ui/stats_overview_component.rb +0 -78
  100. data/app/components/solidstats/ui/status_badge_component.html.erb +0 -6
  101. data/app/components/solidstats/ui/status_badge_component.rb +0 -42
  102. data/app/components/solidstats/ui/summary_card_component.html.erb +0 -12
  103. data/app/components/solidstats/ui/summary_card_component.rb +0 -63
  104. data/app/components/solidstats/ui/tab_navigation_component.html.erb +0 -22
  105. data/app/components/solidstats/ui/tab_navigation_component.rb +0 -79
  106. data/app/controllers/solidstats/gem_metadata_controller.rb +0 -12
  107. data/app/services/solidstats/audit_service.rb +0 -56
  108. data/app/services/solidstats/data_collector_service.rb +0 -83
  109. data/app/services/solidstats/gem_metadata/fetcher_service.rb +0 -136
  110. data/app/services/solidstats/todo_service.rb +0 -114
  111. data/app/views/solidstats/dashboard/_log_monitor.html.erb +0 -759
  112. data/app/views/solidstats/dashboard/_todos.html.erb +0 -151
  113. data/app/views/solidstats/dashboard/audit/_additional_styles.css +0 -22
  114. data/app/views/solidstats/dashboard/audit/_audit_badge.html.erb +0 -5
  115. data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +0 -495
  116. data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +0 -26
  117. data/app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb +0 -3
  118. data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -14
  119. data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +0 -1120
  120. data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +0 -63
  121. data/app/views/solidstats/dashboard/index.html.erb +0 -81
  122. data/app/views/solidstats/gem_metadata/_panel.html.erb +0 -419
  123. data/lib/generators/solidstats/feature/feature_generator.rb +0 -170
  124. data/lib/generators/solidstats/feature/templates/component.html.erb +0 -84
  125. data/lib/generators/solidstats/feature/templates/component.rb.erb +0 -103
  126. data/lib/generators/solidstats/feature/templates/component.scss +0 -243
  127. data/lib/generators/solidstats/feature/templates/component_test.rb.erb +0 -183
  128. data/lib/generators/solidstats/feature/templates/controller.rb.erb +0 -44
  129. data/lib/generators/solidstats/feature/templates/controller_test.rb.erb +0 -111
  130. data/lib/generators/solidstats/feature/templates/detail_view.html.erb +0 -755
  131. data/lib/generators/solidstats/feature/templates/preview.rb.erb +0 -107
  132. data/lib/generators/solidstats/feature/templates/service.rb.erb +0 -132
  133. data/lib/generators/solidstats/feature/templates/service_test.rb.erb +0 -109
  134. data/lib/generators/solidstats/install_generator.rb +0 -109
  135. data/lib/generators/solidstats/templates/initializer.rb +0 -112
  136. data/lib/solidstats/asset_compatibility.rb +0 -238
  137. data/lib/solidstats/asset_manifest.rb +0 -205
  138. data/lib/tasks/solidstats_tasks.rake +0 -4
@@ -1,1120 +0,0 @@
1
- <% results = @audit_output.dig("results") || [] %>
2
- <div class="vulnerabilities-controls">
3
- <div class="vulnerability-filters">
4
- <div class="filter-group">
5
- <label>Severity:</label>
6
- <button class="filter-btn active" data-filter="all">All</button>
7
- <button class="filter-btn critical-filter" data-filter="critical">Critical</button>
8
- <button class="filter-btn high-filter" data-filter="high">High</button>
9
- <button class="filter-btn" data-filter="medium">Medium</button>
10
- <button class="filter-btn" data-filter="low">Low</button>
11
- <button class="filter-btn" data-filter="unknown">Unknown</button>
12
- </div>
13
- <div class="search-container">
14
- <input type="text" id="vulnerability-search" placeholder="Search vulnerabilities..." class="search-input">
15
- <div class="search-icon">🔍</div>
16
- </div>
17
- </div>
18
- <div class="vulnerability-actions">
19
- <button class="action-button bulk-action-button" disabled>
20
- <span class="action-icon">✓</span> Bulk Update
21
- </button>
22
- <button class="action-button export-csv-button" disabled>
23
- <span class="action-icon">↓</span> Export CSV
24
- </button>
25
- </div>
26
- </div>
27
-
28
- <div class="vulnerability-summary">
29
- <div class="donut-chart">
30
- <div class="chart-container" id="severity-donut-chart">
31
- <!-- CSS-based donut chart -->
32
- <%
33
- # Calculate counts for each severity
34
- critical_count = results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "critical" }
35
- high_count = results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "high" }
36
- medium_count = results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "medium" }
37
- low_count = results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "low" }
38
- unknown_count = results.count { |r| ["", nil].include?(r.dig("advisory", "criticality")) ||
39
- r.dig("advisory", "criticality").to_s.downcase == "unknown" }
40
-
41
- # Calculate total for percentages
42
- total = critical_count + high_count + medium_count + low_count + unknown_count
43
-
44
- # Calculate percentages (avoid division by zero)
45
- critical_pct = total > 0 ? critical_count.to_f / total : 0
46
- high_pct = total > 0 ? high_count.to_f / total : 0
47
- medium_pct = total > 0 ? medium_count.to_f / total : 0
48
- low_pct = total > 0 ? low_count.to_f / total : 0
49
- unknown_pct = total > 0 ? unknown_count.to_f / total : 0
50
- %>
51
- <div class="donut"
52
- style="--critical: <%= critical_pct %>;
53
- --high: <%= high_pct %>;
54
- --medium: <%= medium_pct %>;
55
- --low: <%= low_pct %>;
56
- --unknown: <%= unknown_pct %>;"
57
- data-count="<%= total %>"
58
- data-critical="<%= critical_count %>"
59
- data-high="<%= high_count %>"
60
- data-medium="<%= medium_count %>"
61
- data-low="<%= low_count %>"
62
- data-unknown="<%= unknown_count %>">
63
- </div>
64
- </div>
65
- <div class="chart-legend">
66
- <div class="legend-item">
67
- <span class="legend-color critical"></span>
68
- <span class="legend-label">Critical</span>
69
- </div>
70
- <div class="legend-item">
71
- <span class="legend-color high"></span>
72
- <span class="legend-label">High</span>
73
- </div>
74
- <div class="legend-item">
75
- <span class="legend-color medium"></span>
76
- <span class="legend-label">Medium</span>
77
- </div>
78
- <div class="legend-item">
79
- <span class="legend-color low"></span>
80
- <span class="legend-label">Low</span>
81
- </div>
82
- <div class="legend-item">
83
- <span class="legend-color unknown"></span>
84
- <span class="legend-label">Unknown</span>
85
- </div>
86
- </div>
87
- </div>
88
-
89
- <div class="vulnerability-stats">
90
- <div class="stat-item">
91
- <div class="stat-value"><%= results.count { |r| %w[critical high].include?(r.dig("advisory", "criticality").to_s.downcase) } %></div>
92
- <div class="stat-label">Critical/High</div>
93
- </div>
94
- <div class="stat-item">
95
- <div class="stat-value"><%= results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "medium" } %></div>
96
- <div class="stat-label">Medium</div>
97
- </div>
98
- <div class="stat-item">
99
- <div class="stat-value"><%= results.count { |r| r.dig("advisory", "criticality").to_s.downcase == "low" } %></div>
100
- <div class="stat-label">Low</div>
101
- </div>
102
- </div>
103
- </div>
104
-
105
- <div class="vulnerabilities-table">
106
- <table class="table">
107
- <thead>
108
- <tr>
109
- <th class="severity-column">Severity</th>
110
- <th class="gem-column">Gem</th>
111
- <th class="version-column">Version</th>
112
- <th class="cve-column">CVE</th>
113
- <th class="description-column">Description</th>
114
- <th class="remediation-column">Remediation</th>
115
- </tr>
116
- </thead>
117
- <tbody>
118
- <% # We already defined results at the top of the file %>
119
- <% results.each_with_index do |result, index| %>
120
- <%
121
- gem = result.dig("gem") || {}
122
- advisory = result.dig("advisory") || {}
123
- criticality = advisory["criticality"] || "unknown"
124
- criticality = criticality.to_s.downcase # Ensure consistent lowercase
125
- is_high = %w[high critical].include?(criticality)
126
-
127
- # Extract CVE or GHSA ID
128
- vuln_id = advisory["cve"].present? ? "CVE-#{advisory["cve"]}" :
129
- (advisory["ghsa"].present? ? "GHSA-#{advisory["ghsa"]}" : "N/A")
130
-
131
- # Format the link
132
- vuln_link = advisory["cve"].present? ? "https://nvd.nist.gov/vuln/detail/CVE-#{advisory["cve"]}" :
133
- (advisory["ghsa"].present? ? "https://github.com/advisories/#{advisory["ghsa"]}" : "#")
134
-
135
- # Extract published date and format it
136
- published_date = advisory["date"] || "Unknown"
137
- formatted_date = published_date != "Unknown" ? Date.parse(published_date).strftime("%b %d, %Y") : "Unknown"
138
- %>
139
- <tr class="vulnerability-row <%= criticality.to_s.downcase %>" data-severity="<%= criticality.to_s.downcase %>" data-gem="<%= gem["name"] %>" data-index="<%= index %>">
140
- <td class="severity-column">
141
- <div class="severity-badge <%= criticality.to_s.downcase %>">
142
- <%= criticality %>
143
- </div>
144
- </td>
145
- <td class="gem-column">
146
- <div class="gem-name"><%= gem["name"] %></div>
147
- <% if gem["source_url"].present? %>
148
- <a href="<%= gem["source_url"] %>" target="_blank" class="gem-source-link">
149
- <small>Repository <span class="external-link-icon">↗</span></small>
150
- </a>
151
- <% end %>
152
- </td>
153
- <td class="version-column">
154
- <div class="version-info">
155
- <span class="current-version"><%= gem["version"] %></span>
156
- </div>
157
- <div class="vulnerability-meta">
158
- <small>Published: <%= formatted_date %></small>
159
- </div>
160
- </td>
161
- <td class="cve-column">
162
- <% if vuln_id != "N/A" %>
163
- <a href="<%= vuln_link %>" target="_blank" class="vuln-link">
164
- <%= vuln_id %>
165
- <span class="external-link-icon">↗</span>
166
- </a>
167
- <% else %>
168
- N/A
169
- <% end %>
170
- </td>
171
- <td class="description-column">
172
- <div class="vulnerability-title"><%= advisory["title"] %></div>
173
- <% if advisory["description"].present? %>
174
- <a href="#vulnerability-<%= index %>" class="details-link-btn scroll-to-details">
175
- <span class="details-icon">⤵</span> More details
176
- </a>
177
- <% end %>
178
- </td>
179
- <td class="remediation-column">
180
- <% if advisory["patched_versions"].present? %>
181
- <div class="remediation-action">
182
- <div class="patched-versions-pill">
183
- <div class="versions-container">
184
- <% advisory["patched_versions"].each_with_index do |version_text, index| %>
185
- <% version_display = version_text.include?(">=") ? version_text : ">= #{version_text}" %>
186
- <span class="solution-version<%= index == advisory["patched_versions"].size - 1 ? ' last-version' : '' %>"><%= version_display %></span>
187
- <% end %>
188
- </div>
189
- <button class="copy-btn" data-solution="<%= "#{gem["name"]}, #{advisory["patched_versions"].join(", ")}" %>" title="Copy gem name and patched versions">
190
- <span class="copy-icon">📋</span>
191
- </button>
192
- </div>
193
- </div>
194
- <% else %>
195
- <span class="no-patch">No patch available</span>
196
- <% end %>
197
- </td>
198
- </tr>
199
- <% end %>
200
- </tbody>
201
- </table>
202
- </div>
203
-
204
- <style>
205
- .vulnerabilities-controls {
206
- display: flex;
207
- justify-content: space-between;
208
- align-items: center;
209
- margin-bottom: 1.5rem;
210
- flex-wrap: wrap;
211
- gap: 1rem;
212
- }
213
-
214
- .vulnerability-filters {
215
- display: flex;
216
- align-items: center;
217
- gap: 1.5rem;
218
- flex-wrap: wrap;
219
- }
220
-
221
- .filter-group {
222
- display: flex;
223
- align-items: center;
224
- gap: 0.5rem;
225
- }
226
-
227
- .filter-group label {
228
- font-size: 0.9rem;
229
- color: #555;
230
- }
231
-
232
- .filter-btn {
233
- background: #f8f9fa;
234
- border: 1px solid #dee2e6;
235
- padding: 0.35rem 0.75rem;
236
- border-radius: 4px;
237
- font-size: 0.85rem;
238
- cursor: pointer;
239
- transition: all 0.2s;
240
- }
241
-
242
- .filter-btn:hover {
243
- background-color: #e9ecef;
244
- }
245
-
246
- .filter-btn.active {
247
- background-color: #e9f5ff;
248
- color: #0366d6;
249
- border-color: #0366d6;
250
- }
251
-
252
- .critical-filter {
253
- border-left: 3px solid #dc3545;
254
- }
255
-
256
- .high-filter {
257
- border-left: 3px solid #fd7e14;
258
- }
259
-
260
- .search-container {
261
- position: relative;
262
- }
263
-
264
- .search-input {
265
- padding: 0.5rem 0.75rem 0.5rem 2rem;
266
- border: 1px solid #dee2e6;
267
- border-radius: 4px;
268
- font-size: 0.9rem;
269
- width: 250px;
270
- }
271
-
272
- .search-icon {
273
- position: absolute;
274
- left: 0.75rem;
275
- top: 50%;
276
- transform: translateY(-50%);
277
- color: #6c757d;
278
- font-size: 0.9rem;
279
- }
280
-
281
- .vulnerability-summary {
282
- display: flex;
283
- gap: 1.5rem;
284
- margin-bottom: 1.5rem;
285
- background-color: #f9fafb;
286
- border-radius: 8px;
287
- padding: 1.5rem;
288
- align-items: center;
289
- }
290
-
291
- .donut-chart {
292
- display: flex;
293
- align-items: center;
294
- gap: 1.5rem;
295
- }
296
-
297
- .chart-container {
298
- width: 100px;
299
- height: 100px;
300
- position: relative;
301
- }
302
-
303
- .donut {
304
- width: 100%;
305
- height: 100%;
306
- border-radius: 50%;
307
- background: conic-gradient(
308
- #dc3545 0% calc(var(--critical) * 100%),
309
- #fd7e14 calc(var(--critical) * 100%) calc((var(--critical) + var(--high)) * 100%),
310
- #ffc107 calc((var(--critical) + var(--high)) * 100%) calc((var(--critical) + var(--high) + var(--medium)) * 100%),
311
- #28a745 calc((var(--critical) + var(--high) + var(--medium)) * 100%) calc((var(--critical) + var(--high) + var(--medium) + var(--low)) * 100%),
312
- #adb5bd calc((var(--critical) + var(--high) + var(--medium) + var(--low)) * 100%) 100%
313
- );
314
- display: flex;
315
- align-items: center;
316
- justify-content: center;
317
- position: relative;
318
- }
319
-
320
- .donut::after {
321
- content: attr(data-count);
322
- width: 60px;
323
- height: 60px;
324
- background: white;
325
- border-radius: 50%;
326
- position: absolute;
327
- display: flex;
328
- align-items: center;
329
- justify-content: center;
330
- font-weight: bold;
331
- font-size: 16px;
332
- color: #333;
333
- }
334
-
335
- .chart-legend {
336
- display: flex;
337
- flex-direction: column;
338
- gap: 0.5rem;
339
- }
340
-
341
- .legend-item {
342
- display: flex;
343
- align-items: center;
344
- gap: 0.5rem;
345
- font-size: 0.85rem;
346
- }
347
-
348
- .legend-color {
349
- width: 12px;
350
- height: 12px;
351
- border-radius: 2px;
352
- }
353
-
354
- .legend-color.critical {
355
- background-color: #dc3545;
356
- }
357
-
358
- .legend-color.high {
359
- background-color: #fd7e14;
360
- }
361
-
362
- .legend-color.medium {
363
- background-color: #ffc107;
364
- }
365
-
366
- .legend-color.low {
367
- background-color: #28a745;
368
- }
369
-
370
- .legend-color.unknown {
371
- background-color: #adb5bd;
372
- }
373
-
374
- .vulnerability-stats {
375
- display: flex;
376
- gap: 1.5rem;
377
- margin-left: auto;
378
- }
379
-
380
- .stat-item {
381
- text-align: center;
382
- min-width: 80px;
383
- }
384
-
385
- .stat-value {
386
- font-size: 2rem;
387
- font-weight: 700;
388
- line-height: 1;
389
- }
390
-
391
- .stat-label {
392
- font-size: 0.85rem;
393
- color: #6c757d;
394
- margin-top: 0.5rem;
395
- }
396
-
397
- .vulnerabilities-table {
398
- margin-bottom: 1.5rem;
399
- width: 100%;
400
- }
401
-
402
- .table {
403
- width: 100%;
404
- border-collapse: separate;
405
- border-spacing: 0;
406
- font-size: 0.9rem;
407
- table-layout: fixed;
408
- }
409
-
410
- .table th {
411
- background-color: #f8f9fa;
412
- padding: 0.75rem;
413
- text-align: left;
414
- font-weight: 600;
415
- border-bottom: 2px solid #dee2e6;
416
- }
417
-
418
- .table td {
419
- padding: 0.65rem 0.75rem;
420
- border-bottom: 1px solid #dee2e6;
421
- vertical-align: middle;
422
- word-break: break-word;
423
- overflow-wrap: break-word;
424
- }
425
-
426
- .severity-column {
427
- width: 8%;
428
- min-width: 90px;
429
- }
430
-
431
- .gem-column {
432
- width: 15%;
433
- min-width: 120px;
434
- }
435
-
436
- .version-column {
437
- width: 10%;
438
- min-width: 100px;
439
- }
440
-
441
- .cve-column {
442
- width: 12%;
443
- min-width: 110px;
444
- }
445
-
446
- .description-column {
447
- width: 30%;
448
- min-width: 180px;
449
- }
450
-
451
- .remediation-column {
452
- width: 20%;
453
- min-width: 160px;
454
- }
455
-
456
- .vulnerability-row {
457
- transition: background-color 0.2s;
458
- }
459
-
460
- .vulnerability-row:hover {
461
- background-color: #f8f9fa;
462
- }
463
-
464
- .vulnerability-row.critical {
465
- border-left: 4px solid #dc3545;
466
- }
467
-
468
- .vulnerability-row.high {
469
- border-left: 4px solid #fd7e14;
470
- }
471
-
472
- .vulnerability-row.medium {
473
- border-left: 4px solid #ffc107;
474
- }
475
-
476
- .vulnerability-row.low {
477
- border-left: 4px solid #28a745;
478
- }
479
-
480
- .vulnerability-row.unknown {
481
- border-left: 4px solid #adb5bd;
482
- }
483
-
484
- .severity-badge {
485
- display: inline-block;
486
- padding: 0.3rem 0.6rem;
487
- border-radius: 50px;
488
- font-weight: 600;
489
- font-size: 0.8rem;
490
- text-align: center;
491
- white-space: nowrap;
492
- text-transform: uppercase;
493
- letter-spacing: 0.03em;
494
- min-width: 70px;
495
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
496
- }
497
-
498
- .severity-badge.critical {
499
- background-color: #dc3545;
500
- color: white;
501
- }
502
-
503
- .severity-badge.high {
504
- background-color: #fd7e14;
505
- color: white;
506
- }
507
-
508
- .severity-badge.medium {
509
- background-color: #ffc107;
510
- color: #212529;
511
- }
512
-
513
- .severity-badge.low {
514
- background-color: #28a745;
515
- color: white;
516
- }
517
-
518
- .severity-badge.unknown {
519
- background-color: #adb5bd;
520
- color: white;
521
- }
522
-
523
- .details-icon {
524
- font-size: 0.9rem;
525
- font-weight: bold;
526
- }
527
-
528
- .gem-name {
529
- font-weight: 500;
530
- overflow-wrap: break-word;
531
- word-wrap: break-word;
532
- word-break: break-word;
533
- hyphens: auto;
534
- }
535
-
536
- .version-info {
537
- display: flex;
538
- align-items: center;
539
- gap: 0.35rem;
540
- flex-wrap: wrap;
541
- }
542
-
543
- .current-version {
544
- font-family: monospace;
545
- font-size: 0.85rem;
546
- word-break: break-all;
547
- }
548
-
549
- .solution-version {
550
- font-family: monospace;
551
- font-size: 0.85rem;
552
- color: #28a745;
553
- word-break: break-all;
554
- font-weight: 500;
555
- padding: 0.1rem 0.25rem;
556
- display: inline-block;
557
- background-color: rgba(40, 167, 69, 0.08);
558
- border-radius: 3px;
559
- margin-right: 0.25rem;
560
- }
561
-
562
- .solution-version.last-version {
563
- margin-bottom: 0;
564
- }
565
-
566
- .solution-version.single-version {
567
- margin-right: 0;
568
- }
569
-
570
- .vuln-link {
571
- color: #0366d6;
572
- text-decoration: none;
573
- display: flex;
574
- align-items: center;
575
- gap: 0.35rem;
576
- word-break: break-all;
577
- }
578
-
579
- .external-link-icon {
580
- font-size: 0.75rem;
581
- opacity: 0.75;
582
- }
583
-
584
- .vulnerability-title {
585
- font-weight: 500;
586
- margin-bottom: 0.5rem;
587
- overflow-wrap: break-word;
588
- word-wrap: break-word;
589
- hyphens: auto;
590
- }
591
-
592
- .details-toggle-btn {
593
- background: none;
594
- border: none;
595
- color: #0366d6;
596
- font-size: 0.85rem;
597
- padding: 0;
598
- cursor: pointer;
599
- display: flex;
600
- align-items: center;
601
- gap: 0.25rem;
602
- }
603
-
604
- .toggle-icon {
605
- transition: transform 0.2s;
606
- }
607
-
608
- .vulnerability-details-container {
609
- position: relative;
610
- z-index: 5;
611
- }
612
-
613
- .vulnerability-details {
614
- max-height: 0;
615
- overflow: hidden;
616
- transition: max-height 0.3s ease, opacity 0.3s ease;
617
- margin-top: 0.75rem;
618
- font-size: 0.85rem;
619
- opacity: 0;
620
- }
621
-
622
- .vulnerability-details.expanded {
623
- max-height: 300px;
624
- overflow-y: auto;
625
- width: 100%;
626
- opacity: 1;
627
- position: relative;
628
- z-index: 10;
629
- }
630
-
631
- .details-content {
632
- background-color: #f8f9fa;
633
- padding: 0.75rem;
634
- border-radius: 4px;
635
- white-space: normal;
636
- color: #495057;
637
- overflow-wrap: break-word;
638
- word-wrap: break-word;
639
- border: 1px solid #e9ecef;
640
- box-shadow: 0 2px 5px rgba(0,0,0,0.05);
641
- }
642
-
643
- .remediation-action {
644
- display: flex;
645
- align-items: center;
646
- gap: 0.5rem;
647
- flex-wrap: wrap;
648
- }
649
-
650
- .patched-versions-pill {
651
- display: flex;
652
- align-items: center;
653
- background-color: rgba(40, 167, 69, 0.08);
654
- border-radius: 4px;
655
- padding: 0.35rem 0.5rem;
656
- max-width: 100%;
657
- overflow-wrap: break-word;
658
- border-left: 3px solid #28a745;
659
- }
660
-
661
- .versions-container {
662
- display: flex;
663
- flex-direction: column;
664
- gap: 0.25rem;
665
- flex-grow: 1;
666
- }
667
-
668
- .more-versions {
669
- font-size: 0.75rem;
670
- background-color: rgba(40, 167, 69, 0.2);
671
- color: #28a745;
672
- font-weight: 500;
673
- border-radius: 3px;
674
- padding: 0.15rem 0.35rem;
675
- cursor: help;
676
- margin-left: 0.25rem;
677
- display: inline-block;
678
- min-width: 1.5rem;
679
- text-align: center;
680
- }
681
-
682
- .copy-btn {
683
- background-color: transparent;
684
- color: #495057;
685
- border: none;
686
- padding: 0.25rem;
687
- margin-left: 0.25rem;
688
- border-radius: 4px;
689
- font-size: 0.85rem;
690
- cursor: pointer;
691
- transition: all 0.2s;
692
- }
693
-
694
- .copy-btn:hover {
695
- background-color: #e9ecef;
696
- }
697
-
698
- .action-button:disabled {
699
- opacity: 0.6;
700
- cursor: not-allowed;
701
- position: relative;
702
- }
703
-
704
- .action-button:disabled:hover::after {
705
- content: "Currently unavailable";
706
- position: absolute;
707
- up: -150px; /* Position tooltip to the right of the button */
708
- top: 50%;
709
- transform: translateY(-50%);
710
- background-color: #333;
711
- color: white;
712
- padding: 4px 8px;
713
- border-radius: 4px;
714
- font-size: 12px;
715
- white-space: nowrap;
716
- z-index: 100;
717
- }
718
-
719
- .no-patch {
720
- color: #6c757d;
721
- font-style: italic;
722
- font-size: 0.85rem;
723
- }
724
-
725
- .vulnerabilities-pagination {
726
- display: flex;
727
- justify-content: space-between;
728
- align-items: center;
729
- margin-top: 1.5rem;
730
- padding-top: 1rem;
731
- border-top: 1px solid #dee2e6;
732
- }
733
-
734
- .pagination-text {
735
- color: #6c757d;
736
- font-size: 0.85rem;
737
- }
738
-
739
- .pagination-controls {
740
- display: flex;
741
- align-items: center;
742
- gap: 1rem;
743
- }
744
-
745
- .pagination-pages {
746
- font-size: 0.9rem;
747
- }
748
-
749
- .pagination-btn {
750
- background-color: #f8f9fa;
751
- border: 1px solid #dee2e6;
752
- padding: 0.35rem 0.75rem;
753
- border-radius: 4px;
754
- font-size: 0.85rem;
755
- cursor: pointer;
756
- transition: all 0.2s;
757
- }
758
-
759
- .pagination-btn:hover:not(:disabled) {
760
- background-color: #e9ecef;
761
- }
762
-
763
- .pagination-btn:disabled {
764
- opacity: 0.5;
765
- cursor: not-allowed;
766
- }
767
-
768
- .details-link-btn {
769
- color: #0366d6;
770
- font-size: 0.85rem;
771
- text-decoration: none;
772
- display: inline-flex;
773
- align-items: center;
774
- gap: 0.4rem;
775
- padding: 0.2rem 0.5rem;
776
- border-radius: 4px;
777
- transition: background-color 0.2s;
778
- }
779
-
780
- .details-link-btn:hover {
781
- background-color: rgba(3, 102, 214, 0.1);
782
- text-decoration: none;
783
- }
784
-
785
- .scroll-to-details {
786
- cursor: pointer;
787
- }
788
-
789
- /* Responsive styles for smaller screens */
790
- @media (max-width: 992px) {
791
- .vulnerability-summary {
792
- flex-direction: column;
793
- align-items: flex-start;
794
- }
795
-
796
- .vulnerability-stats {
797
- margin-left: 0;
798
- margin-top: 1rem;
799
- width: 100%;
800
- justify-content: space-around;
801
- }
802
- }
803
-
804
- @media (max-width: 768px) {
805
- .severity-column {
806
- width: 12%;
807
- text-align: center;
808
- }
809
-
810
- .severity-badge {
811
- min-width: 60px;
812
- padding: 0.25rem 0.4rem;
813
- font-size: 0.75rem;
814
- }
815
-
816
- .gem-column {
817
- width: 18%;
818
- }
819
-
820
- .version-column {
821
- width: 12%;
822
- }
823
-
824
- .cve-column {
825
- width: 15%;
826
- }
827
-
828
- .description-column {
829
- width: 25%;
830
- }
831
-
832
- .remediation-column {
833
- width: 15%;
834
- }
835
-
836
- .vulnerability-filters {
837
- flex-direction: column;
838
- align-items: flex-start;
839
- gap: 0.75rem;
840
- }
841
-
842
- .search-input {
843
- width: 100%;
844
- }
845
- }
846
-
847
- /* Extra small devices */
848
- @media (max-width: 576px) {
849
- .table {
850
- table-layout: auto;
851
- }
852
-
853
- .severity-column {
854
- width: 15%;
855
- }
856
-
857
- .vulnerability-stats {
858
- justify-content: space-between;
859
- width: 100%;
860
- }
861
-
862
- .stat-item {
863
- min-width: auto;
864
- }
865
-
866
- /* Ensure text doesn't overflow in vulnerability details */
867
- .vulnerability-details .details-content {
868
- max-width: 100%;
869
- white-space: normal;
870
- }
871
-
872
- /* Make the action buttons more touchable on mobile */
873
- .update-btn, .copy-btn {
874
- padding: 0.5rem;
875
- min-height: 44px; /* Better touch target */
876
- }
877
-
878
- /* Enhance remediation column display on small screens */
879
- .remediation-column .patched-versions-pill {
880
- flex-direction: column;
881
- align-items: flex-start;
882
- }
883
-
884
- .remediation-column .versions-container {
885
- width: 100%;
886
- margin-bottom: 0.25rem;
887
- }
888
-
889
- .copy-btn {
890
- align-self: flex-end;
891
- }
892
- }
893
- </style>
894
-
895
- <script>
896
- document.addEventListener('DOMContentLoaded', function() {
897
- // Filter buttons functionality
898
- document.querySelectorAll('.filter-btn').forEach(function(button) {
899
- button.addEventListener('click', function() {
900
- // Update active state
901
- document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
902
- this.classList.add('active');
903
-
904
- const filter = this.getAttribute('data-filter');
905
- const rows = document.querySelectorAll('.vulnerability-row');
906
-
907
- // Counts for updating donut chart
908
- let visibleCritical = 0;
909
- let visibleHigh = 0;
910
- let visibleMedium = 0;
911
- let visibleLow = 0;
912
- let visibleUnknown = 0;
913
- let visibleTotal = 0;
914
-
915
- rows.forEach(function(row) {
916
- const severity = row.getAttribute('data-severity');
917
-
918
- if (filter === 'all') {
919
- row.style.display = '';
920
-
921
- // Count by severity for all visible rows
922
- if (severity === 'critical') visibleCritical++;
923
- else if (severity === 'high') visibleHigh++;
924
- else if (severity === 'medium') visibleMedium++;
925
- else if (severity === 'low') visibleLow++;
926
- else visibleUnknown++;
927
-
928
- visibleTotal++;
929
-
930
- } else {
931
- if (severity === filter) {
932
- row.style.display = '';
933
-
934
- // For filtered view, only count matching severity
935
- if (severity === 'critical') visibleCritical++;
936
- else if (severity === 'high') visibleHigh++;
937
- else if (severity === 'medium') visibleMedium++;
938
- else if (severity === 'low') visibleLow++;
939
- else visibleUnknown++;
940
-
941
- visibleTotal++;
942
- } else {
943
- row.style.display = 'none';
944
- }
945
- }
946
- });
947
-
948
- // Update donut chart percentages based on visible rows
949
- const donut = document.querySelector('.donut');
950
- if (donut) {
951
- // Calculate percentages
952
- const criticalPct = visibleTotal > 0 ? visibleCritical / visibleTotal : 0;
953
- const highPct = visibleTotal > 0 ? visibleHigh / visibleTotal : 0;
954
- const mediumPct = visibleTotal > 0 ? visibleMedium / visibleTotal : 0;
955
- const lowPct = visibleTotal > 0 ? visibleLow / visibleTotal : 0;
956
- const unknownPct = visibleTotal > 0 ? visibleUnknown / visibleTotal : 0;
957
-
958
- // Update CSS variables
959
- donut.style.setProperty('--critical', criticalPct);
960
- donut.style.setProperty('--high', highPct);
961
- donut.style.setProperty('--medium', mediumPct);
962
- donut.style.setProperty('--low', lowPct);
963
- donut.style.setProperty('--unknown', unknownPct);
964
-
965
- // Update data attributes
966
- donut.setAttribute('data-count', visibleTotal);
967
- donut.setAttribute('data-critical', visibleCritical);
968
- donut.setAttribute('data-high', visibleHigh);
969
- donut.setAttribute('data-medium', visibleMedium);
970
- donut.setAttribute('data-low', visibleLow);
971
- donut.setAttribute('data-unknown', visibleUnknown);
972
- }
973
-
974
- // Update stats counters
975
- const critHighCounter = document.querySelector('.vulnerability-stats .stat-value:nth-child(1)');
976
- const mediumCounter = document.querySelector('.vulnerability-stats .stat-item:nth-child(2) .stat-value');
977
- const lowCounter = document.querySelector('.vulnerability-stats .stat-item:nth-child(3) .stat-value');
978
-
979
- if (critHighCounter) {
980
- critHighCounter.textContent = visibleCritical + visibleHigh;
981
- }
982
- if (mediumCounter) {
983
- mediumCounter.textContent = visibleMedium;
984
- }
985
- if (lowCounter) {
986
- lowCounter.textContent = visibleLow;
987
- }
988
- });
989
- });
990
-
991
- // Search functionality
992
- const searchInput = document.getElementById('vulnerability-search');
993
- if (searchInput) {
994
- searchInput.addEventListener('input', function() {
995
- const searchTerm = this.value.toLowerCase();
996
- const rows = document.querySelectorAll('.vulnerability-row');
997
-
998
- rows.forEach(function(row) {
999
- const text = row.textContent.toLowerCase();
1000
- const gemName = row.getAttribute('data-gem').toLowerCase();
1001
-
1002
- if (text.includes(searchTerm) || gemName.includes(searchTerm)) {
1003
- row.style.display = '';
1004
- } else {
1005
- row.style.display = 'none';
1006
- }
1007
- });
1008
- });
1009
- }
1010
-
1011
- // Scroll to vulnerability details when clicking "More details"
1012
- document.querySelectorAll('.scroll-to-details').forEach(function(link) {
1013
- link.addEventListener('click', function(e) {
1014
- e.preventDefault();
1015
- const targetId = this.getAttribute('href');
1016
- const detailsSection = document.querySelector('.vulnerabilities-details-section');
1017
-
1018
- // Ensure the details section is visible
1019
- if (detailsSection.classList.contains('hidden')) {
1020
- detailsSection.classList.remove('hidden');
1021
- const toggleBtn = document.querySelector('.toggle-details-btn');
1022
- if (toggleBtn) {
1023
- toggleBtn.textContent = 'Hide Details';
1024
- toggleBtn.setAttribute('aria-expanded', 'true');
1025
- }
1026
- }
1027
-
1028
- // Scroll to the specific vulnerability detail
1029
- setTimeout(() => {
1030
- const targetElement = document.querySelector(targetId);
1031
- if (targetElement) {
1032
- const headerHeight = 60; // Approximate height of sticky header
1033
- const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - headerHeight - 20;
1034
- window.scrollTo({
1035
- top: targetPosition,
1036
- behavior: 'smooth'
1037
- });
1038
-
1039
- // Highlight the target element briefly
1040
- targetElement.classList.add('highlight-detail');
1041
- setTimeout(() => {
1042
- targetElement.classList.remove('highlight-detail');
1043
- }, 1500);
1044
- }
1045
- }, 100);
1046
- });
1047
- });
1048
-
1049
- // Tooltip functionality for patched versions
1050
- document.querySelectorAll('.more-versions').forEach(function(element) {
1051
- element.addEventListener('mouseenter', function() {
1052
- // Could implement custom tooltip here if needed
1053
- });
1054
- });
1055
-
1056
- // Copy button functionality
1057
- document.querySelectorAll('.copy-btn').forEach(function(button) {
1058
- button.addEventListener('click', function() {
1059
- const solution = this.getAttribute('data-solution');
1060
-
1061
- // Create a temporary input element
1062
- const tempInput = document.createElement('input');
1063
- tempInput.value = solution;
1064
- document.body.appendChild(tempInput);
1065
- tempInput.select();
1066
- document.execCommand('copy');
1067
- document.body.removeChild(tempInput);
1068
-
1069
- // Show feedback
1070
- const originalIcon = this.innerHTML;
1071
- this.innerHTML = '<span>✓</span>';
1072
- setTimeout(() => {
1073
- this.innerHTML = originalIcon;
1074
-
1075
- // Show toast notification
1076
- if (typeof showNotification === 'function') {
1077
- showNotification('Copied to clipboard!', 'success');
1078
- }
1079
- }, 1500);
1080
- });
1081
- });
1082
-
1083
- // No select-all checkbox or update buttons anymore
1084
-
1085
- // Ensure table fits within viewport
1086
- function adjustTableResponsiveness() {
1087
- const table = document.querySelector('.vulnerabilities-table table');
1088
- const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
1089
-
1090
- // If viewport is very small, change display approach
1091
- if (viewportWidth < 576) {
1092
- document.querySelectorAll('.vulnerability-row').forEach(row => {
1093
- // Add more bottom padding for mobile view
1094
- row.style.paddingBottom = '10px';
1095
- });
1096
-
1097
- // Ensure description column takes necessary space
1098
- const descriptionCells = document.querySelectorAll('.description-column');
1099
- descriptionCells.forEach(cell => {
1100
- cell.style.maxWidth = '100%';
1101
- cell.style.width = 'auto';
1102
- });
1103
-
1104
- // Make severity column more compact
1105
- const severityCells = document.querySelectorAll('.severity-column');
1106
- severityCells.forEach(cell => {
1107
- const badge = cell.querySelector('.severity-badge');
1108
- if (badge) {
1109
- badge.style.padding = '0.2rem 0.4rem';
1110
- badge.style.minWidth = 'auto';
1111
- }
1112
- });
1113
- }
1114
- }
1115
-
1116
- // Run on page load and on resize
1117
- adjustTableResponsiveness();
1118
- window.addEventListener('resize', adjustTableResponsiveness);
1119
- });
1120
- </script>