solidstats 1.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +27 -0
- data/Rakefile +3 -3
- data/app/assets/stylesheets/solidstats/dashboard.css +48 -0
- data/app/controllers/solidstats/dashboard_controller.rb +82 -60
- data/app/controllers/solidstats/logs_controller.rb +72 -0
- data/app/controllers/solidstats/performance_controller.rb +25 -0
- data/app/controllers/solidstats/productivity_controller.rb +39 -0
- data/app/controllers/solidstats/quality_controller.rb +152 -0
- data/app/controllers/solidstats/securities_controller.rb +30 -0
- data/app/helpers/solidstats/application_helper.rb +155 -0
- data/app/helpers/solidstats/performance_helper.rb +87 -0
- data/app/helpers/solidstats/productivity_helper.rb +38 -0
- data/app/services/solidstats/bundler_audit_service.rb +206 -0
- data/app/services/solidstats/coverage_compass_service.rb +335 -0
- data/app/services/solidstats/load_lens_service.rb +454 -0
- data/app/services/solidstats/log_size_monitor_service.rb +205 -74
- data/app/services/solidstats/my_todo_service.rb +242 -0
- data/app/services/solidstats/style_patrol_service.rb +319 -0
- data/app/views/layouts/solidstats/application.html.erb +9 -2
- data/app/views/layouts/solidstats/dashboard.html.erb +84 -0
- data/app/views/solidstats/dashboard/dashboard.html.erb +39 -0
- data/app/views/solidstats/logs/logs_size.html.erb +409 -0
- data/app/views/solidstats/performance/load_lens.html.erb +158 -0
- data/app/views/solidstats/productivity/_todo_list.html.erb +49 -0
- data/app/views/solidstats/productivity/my_todos.html.erb +84 -0
- data/app/views/solidstats/quality/coverage_compass.html.erb +420 -0
- data/app/views/solidstats/quality/style_patrol.html.erb +463 -0
- data/app/views/solidstats/securities/bundler_audit.html.erb +345 -0
- data/app/views/solidstats/shared/_dashboard_card.html.erb +160 -0
- data/app/views/solidstats/shared/_quick_actions.html.erb +26 -0
- data/config/routes.rb +32 -4
- data/lib/generators/solidstats/install/install_generator.rb +28 -2
- data/lib/generators/solidstats/install/templates/README +7 -0
- data/lib/solidstats/version.rb +1 -1
- data/lib/tasks/solidstats_performance.rake +84 -0
- metadata +43 -19
- data/app/services/solidstats/audit_service.rb +0 -56
- data/app/services/solidstats/data_collector_service.rb +0 -83
- data/app/services/solidstats/todo_service.rb +0 -114
- data/app/views/solidstats/dashboard/_log_monitor.html.erb +0 -759
- data/app/views/solidstats/dashboard/_todos.html.erb +0 -151
- data/app/views/solidstats/dashboard/audit/_additional_styles.css +0 -22
- data/app/views/solidstats/dashboard/audit/_audit_badge.html.erb +0 -5
- data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +0 -495
- data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +0 -26
- data/app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb +0 -3
- data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -14
- data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +0 -1120
- data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +0 -63
- data/app/views/solidstats/dashboard/index.html.erb +0 -1351
- data/lib/tasks/solidstats_tasks.rake +0 -4
@@ -1,151 +0,0 @@
|
|
1
|
-
<div class="stat-card todo-card">
|
2
|
-
<h2><span class="icon">📝</span> TODO Items</h2>
|
3
|
-
<div class="card-content">
|
4
|
-
<% if @todo_items.nil? %>
|
5
|
-
<div class="status-badge danger">Error loading TODO data</div>
|
6
|
-
<% elsif @todo_items.empty? %>
|
7
|
-
<div class="status-badge success">All Clear</div>
|
8
|
-
<% else %>
|
9
|
-
<div class="status-badge warning"><%= @todo_items.count %> <%= "Item".pluralize(@todo_items.count) %> Found</div>
|
10
|
-
<% end %>
|
11
|
-
|
12
|
-
<% if @todo_items.present? && @todo_summary.present? %>
|
13
|
-
<div class="metrics-group">
|
14
|
-
<div class="metric">
|
15
|
-
<span class="metric-label">TODO:</span>
|
16
|
-
<span class="metric-value"><%= @todo_summary[:by_type]["TODO"] %></span>
|
17
|
-
</div>
|
18
|
-
<div class="metric">
|
19
|
-
<span class="metric-label">FIXME:</span>
|
20
|
-
<span class="metric-value text-danger"><%= @todo_summary[:by_type]["FIXME"] %></span>
|
21
|
-
</div>
|
22
|
-
<div class="metric">
|
23
|
-
<span class="metric-label">HACK:</span>
|
24
|
-
<span class="metric-value text-warning"><%= @todo_summary[:by_type]["HACK"] %></span>
|
25
|
-
</div>
|
26
|
-
</div>
|
27
|
-
|
28
|
-
<a href="#" class="toggle-details" data-target="todo-details">Show Details</a>
|
29
|
-
<% end %>
|
30
|
-
</div>
|
31
|
-
|
32
|
-
<% if @todo_items.present? && @todo_summary.present? %>
|
33
|
-
<div id="todo-details" class="details-panel hidden">
|
34
|
-
<h3 class="details-section-title">Files with Most TODOs</h3>
|
35
|
-
<div class="table-responsive mb-4">
|
36
|
-
<table class="table">
|
37
|
-
<thead>
|
38
|
-
<tr>
|
39
|
-
<th>File</th>
|
40
|
-
<th class="text-end">Count</th>
|
41
|
-
</tr>
|
42
|
-
</thead>
|
43
|
-
<tbody>
|
44
|
-
<% hotspots = @todo_summary[:hotspots] %>
|
45
|
-
<% if hotspots.present? %>
|
46
|
-
<% hotspots.each do |file, count| %>
|
47
|
-
<tr>
|
48
|
-
<td><code><%= file.nil? ? "Unknown File" : file.to_s %></code></td>
|
49
|
-
<td class="text-end"><%= count.to_i %></td>
|
50
|
-
</tr>
|
51
|
-
<% end %>
|
52
|
-
<% else %>
|
53
|
-
<tr>
|
54
|
-
<td colspan="2">No hotspot data available</td>
|
55
|
-
</tr>
|
56
|
-
<% end %>
|
57
|
-
</tbody>
|
58
|
-
</table>
|
59
|
-
</div>
|
60
|
-
|
61
|
-
<h3 class="details-section-title">All TODO Items</h3>
|
62
|
-
<div class="accordion" id="todoAccordion">
|
63
|
-
<% @todo_items.each_with_index do |item, index| %>
|
64
|
-
<%
|
65
|
-
# Convert item to a hash with symbol keys if it's a hash with string keys
|
66
|
-
item = item.symbolize_keys if item.respond_to?(:symbolize_keys)
|
67
|
-
%>
|
68
|
-
<div class="accordion-item">
|
69
|
-
<h4 class="accordion-header">
|
70
|
-
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#todo-<%= index %>">
|
71
|
-
<span class="badge <%= item[:type] == 'FIXME' ? 'bg-danger' : (item[:type] == 'HACK' ? 'bg-warning' : 'bg-primary') %> me-2"><%= item[:type] || "TODO" %></span>
|
72
|
-
<%= File.basename(item[:file].to_s) %>:<%= item[:line] || '?' %>
|
73
|
-
</button>
|
74
|
-
</h4>
|
75
|
-
<div id="todo-<%= index %>" class="accordion-collapse collapse" data-bs-parent="#todoAccordion">
|
76
|
-
<div class="accordion-body">
|
77
|
-
<p><code class="d-block"><%= item[:content].to_s %></code></p>
|
78
|
-
<small class="text-muted">File: <%= item[:file].to_s %></small>
|
79
|
-
</div>
|
80
|
-
</div>
|
81
|
-
</div>
|
82
|
-
<% end %>
|
83
|
-
</div>
|
84
|
-
</div>
|
85
|
-
<% end %>
|
86
|
-
</div>
|
87
|
-
|
88
|
-
<style>
|
89
|
-
/* Additional styles for todo card */
|
90
|
-
.todo-card.expanded {
|
91
|
-
grid-column: 1 / -1;
|
92
|
-
width: 100%;
|
93
|
-
}
|
94
|
-
|
95
|
-
.details-section-title {
|
96
|
-
font-size: 1.1rem;
|
97
|
-
font-weight: 600;
|
98
|
-
margin: 1.5rem 0 1rem 0;
|
99
|
-
}
|
100
|
-
|
101
|
-
.details-section-title:first-child {
|
102
|
-
margin-top: 0;
|
103
|
-
}
|
104
|
-
|
105
|
-
/* Fix for accordion within the card */
|
106
|
-
.todo-card .accordion {
|
107
|
-
margin-top: 1rem;
|
108
|
-
}
|
109
|
-
|
110
|
-
.todo-card .accordion-item {
|
111
|
-
border: 1px solid rgba(0, 0, 0, 0.125);
|
112
|
-
margin-bottom: 0.5rem;
|
113
|
-
border-radius: 4px;
|
114
|
-
overflow: hidden;
|
115
|
-
}
|
116
|
-
|
117
|
-
.todo-card .accordion-button {
|
118
|
-
padding: 0.75rem 1rem;
|
119
|
-
font-size: 0.9rem;
|
120
|
-
background-color: #f8f9fa;
|
121
|
-
}
|
122
|
-
|
123
|
-
.todo-card .accordion-button:not(.collapsed) {
|
124
|
-
background-color: #e7f1ff;
|
125
|
-
}
|
126
|
-
|
127
|
-
.todo-card .accordion-body {
|
128
|
-
padding: 1rem;
|
129
|
-
background-color: #fff;
|
130
|
-
}
|
131
|
-
|
132
|
-
.todo-card code {
|
133
|
-
font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
134
|
-
font-size: 0.85rem;
|
135
|
-
padding: 0.2rem 0.4rem;
|
136
|
-
background: #f6f8fa;
|
137
|
-
border-radius: 3px;
|
138
|
-
color: #24292e;
|
139
|
-
}
|
140
|
-
|
141
|
-
.todo-card code.d-block {
|
142
|
-
display: block;
|
143
|
-
padding: 0.75rem;
|
144
|
-
margin-bottom: 0.5rem;
|
145
|
-
white-space: pre-wrap;
|
146
|
-
word-break: break-word;
|
147
|
-
background: #f6f8fa;
|
148
|
-
border-radius: 4px;
|
149
|
-
border: 1px solid #e1e4e8;
|
150
|
-
}
|
151
|
-
</style>
|
@@ -1,22 +0,0 @@
|
|
1
|
-
.details-link-btn {
|
2
|
-
color: #0366d6;
|
3
|
-
font-size: 0.85rem;
|
4
|
-
text-decoration: none;
|
5
|
-
display: inline-flex;
|
6
|
-
align-items: center;
|
7
|
-
gap: 0.25rem;
|
8
|
-
}
|
9
|
-
|
10
|
-
.details-link-btn:hover {
|
11
|
-
text-decoration: underline;
|
12
|
-
}
|
13
|
-
|
14
|
-
.external-link-icon-sm {
|
15
|
-
font-size: 0.75rem;
|
16
|
-
}
|
17
|
-
|
18
|
-
.details-preview {
|
19
|
-
font-size: 0.85rem;
|
20
|
-
color: #666;
|
21
|
-
margin-top: 0.25rem;
|
22
|
-
}
|
@@ -1,495 +0,0 @@
|
|
1
|
-
<div id="audit-details">
|
2
|
-
<%= render partial: 'solidstats/dashboard/audit/audit_summary' %>
|
3
|
-
|
4
|
-
<% results = @audit_output.dig("results") || [] %>
|
5
|
-
<% if results.empty? %>
|
6
|
-
<%= render partial: 'solidstats/dashboard/audit/no_vulnerabilities' %>
|
7
|
-
<% else %>
|
8
|
-
<%= render partial: 'solidstats/dashboard/audit/vulnerabilities_table', locals: { results: results } %>
|
9
|
-
<%= render partial: 'solidstats/dashboard/audit/vulnerability_details', locals: { results: results } %>
|
10
|
-
<% end %>
|
11
|
-
</div>
|
12
|
-
|
13
|
-
<style>
|
14
|
-
/* Audit details specific styles */
|
15
|
-
.audit-summary {
|
16
|
-
margin-bottom: 2rem;
|
17
|
-
}
|
18
|
-
|
19
|
-
.audit-summary-header {
|
20
|
-
display: flex;
|
21
|
-
justify-content: space-between;
|
22
|
-
align-items: center;
|
23
|
-
border-bottom: 1px solid #dee2e6;
|
24
|
-
padding-bottom: 1rem;
|
25
|
-
margin-bottom: 1rem;
|
26
|
-
}
|
27
|
-
|
28
|
-
.audit-summary-header h3 {
|
29
|
-
margin: 0;
|
30
|
-
font-size: 1.2rem;
|
31
|
-
font-weight: 600;
|
32
|
-
}
|
33
|
-
|
34
|
-
.audit-stats {
|
35
|
-
display: flex;
|
36
|
-
gap: 2rem;
|
37
|
-
margin-bottom: 1rem;
|
38
|
-
}
|
39
|
-
|
40
|
-
.audit-stat {
|
41
|
-
text-align: center;
|
42
|
-
}
|
43
|
-
|
44
|
-
.audit-stat .stat-value {
|
45
|
-
display: block;
|
46
|
-
font-size: 1.8rem;
|
47
|
-
font-weight: 700;
|
48
|
-
color: #333;
|
49
|
-
}
|
50
|
-
|
51
|
-
.audit-stat .stat-label {
|
52
|
-
font-size: 0.9rem;
|
53
|
-
color: #666;
|
54
|
-
}
|
55
|
-
|
56
|
-
.audit-filters {
|
57
|
-
display: flex;
|
58
|
-
gap: 0.5rem;
|
59
|
-
margin-bottom: 1rem;
|
60
|
-
}
|
61
|
-
|
62
|
-
.filter-btn {
|
63
|
-
background: #f1f1f1;
|
64
|
-
border: none;
|
65
|
-
padding: 0.4rem 0.8rem;
|
66
|
-
border-radius: 4px;
|
67
|
-
font-size: 0.85rem;
|
68
|
-
cursor: pointer;
|
69
|
-
}
|
70
|
-
|
71
|
-
.filter-btn.active {
|
72
|
-
background: #007bff;
|
73
|
-
color: white;
|
74
|
-
}
|
75
|
-
|
76
|
-
.vulnerability-row.high-severity {
|
77
|
-
background-color: #fff8f8;
|
78
|
-
}
|
79
|
-
|
80
|
-
.solution {
|
81
|
-
display: flex;
|
82
|
-
align-items: center;
|
83
|
-
gap: 0.5rem;
|
84
|
-
}
|
85
|
-
|
86
|
-
.copy-btn {
|
87
|
-
background: #e9ecef;
|
88
|
-
border: none;
|
89
|
-
border-radius: 4px;
|
90
|
-
padding: 0.3rem 0.5rem;
|
91
|
-
font-size: 0.8rem;
|
92
|
-
cursor: pointer;
|
93
|
-
display: flex;
|
94
|
-
align-items: center;
|
95
|
-
gap: 0.25rem;
|
96
|
-
}
|
97
|
-
|
98
|
-
.copy-btn:hover {
|
99
|
-
background: #dee2e6;
|
100
|
-
}
|
101
|
-
|
102
|
-
.copy-btn.copied {
|
103
|
-
background: #d4edda;
|
104
|
-
color: #155724;
|
105
|
-
}
|
106
|
-
|
107
|
-
.no-vulnerabilities {
|
108
|
-
text-align: center;
|
109
|
-
padding: 2rem;
|
110
|
-
color: #155724;
|
111
|
-
}
|
112
|
-
|
113
|
-
/* Styling for details section */
|
114
|
-
.vulnerabilities-details-section {
|
115
|
-
margin-top: 3rem;
|
116
|
-
border-top: 1px solid #dee2e6;
|
117
|
-
padding-top: 1.5rem;
|
118
|
-
}
|
119
|
-
|
120
|
-
.vulnerabilities-details-section.hidden .md-container {
|
121
|
-
display: none;
|
122
|
-
}
|
123
|
-
|
124
|
-
.vulnerabilities-details-section h3 {
|
125
|
-
display: flex;
|
126
|
-
justify-content: space-between;
|
127
|
-
align-items: center;
|
128
|
-
}
|
129
|
-
|
130
|
-
.toggle-details-btn {
|
131
|
-
background: #f1f1f1;
|
132
|
-
border: none;
|
133
|
-
padding: 0.4rem 0.8rem;
|
134
|
-
border-radius: 4px;
|
135
|
-
font-size: 0.85rem;
|
136
|
-
cursor: pointer;
|
137
|
-
transition: background-color 0.2s;
|
138
|
-
}
|
139
|
-
|
140
|
-
.toggle-details-btn:hover {
|
141
|
-
background: #e2e6ea;
|
142
|
-
}
|
143
|
-
|
144
|
-
.toggle-details-btn[aria-expanded="true"] {
|
145
|
-
background: #007bff;
|
146
|
-
color: white;
|
147
|
-
}
|
148
|
-
|
149
|
-
.vulnerability-detail {
|
150
|
-
margin-bottom: 2rem;
|
151
|
-
padding: 1.5rem;
|
152
|
-
border-radius: 8px;
|
153
|
-
background-color: #f8f9fa;
|
154
|
-
border: 1px solid #dee2e6;
|
155
|
-
}
|
156
|
-
|
157
|
-
.vulnerability-detail.high-severity {
|
158
|
-
border-left: 4px solid #dc3545;
|
159
|
-
}
|
160
|
-
|
161
|
-
|
162
|
-
.vulnerability-meta {
|
163
|
-
display: flex;
|
164
|
-
gap: 1rem;
|
165
|
-
margin-bottom: 1rem;
|
166
|
-
font-size: 0.85rem;
|
167
|
-
}
|
168
|
-
|
169
|
-
.cve, .date {
|
170
|
-
color: #666;
|
171
|
-
}
|
172
|
-
|
173
|
-
.description {
|
174
|
-
font-size: 0.9rem;
|
175
|
-
line-height: 1.5;
|
176
|
-
white-space: pre-line;
|
177
|
-
}
|
178
|
-
|
179
|
-
.advisory-link {
|
180
|
-
margin-top: 1rem;
|
181
|
-
}
|
182
|
-
|
183
|
-
.advisory-link a {
|
184
|
-
color: #007bff;
|
185
|
-
text-decoration: none;
|
186
|
-
}
|
187
|
-
|
188
|
-
.advisory-link a:hover {
|
189
|
-
text-decoration: underline;
|
190
|
-
}
|
191
|
-
|
192
|
-
.view-details-link {
|
193
|
-
margin-left: 0.5rem;
|
194
|
-
font-size: 0.85rem;
|
195
|
-
color: #007bff;
|
196
|
-
text-decoration: none;
|
197
|
-
}
|
198
|
-
|
199
|
-
.view-details-link:hover {
|
200
|
-
text-decoration: underline;
|
201
|
-
}
|
202
|
-
|
203
|
-
/* Markdown-style container */
|
204
|
-
.md-container {
|
205
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
206
|
-
color: #24292e;
|
207
|
-
background-color: #f6f8fa;
|
208
|
-
border: 1px solid #e1e4e8;
|
209
|
-
border-radius: 6px;
|
210
|
-
padding: 0;
|
211
|
-
overflow: hidden;
|
212
|
-
transition: all 0.3s ease;
|
213
|
-
}
|
214
|
-
|
215
|
-
/* Each vulnerability as a markdown entry */
|
216
|
-
.md-entry {
|
217
|
-
border-bottom: 1px solid #e1e4e8;
|
218
|
-
padding: 24px;
|
219
|
-
background-color: #ffffff;
|
220
|
-
transition: background-color 0.3s ease;
|
221
|
-
}
|
222
|
-
|
223
|
-
.md-entry:last-child {
|
224
|
-
border-bottom: none;
|
225
|
-
}
|
226
|
-
|
227
|
-
.md-entry.high-severity {
|
228
|
-
border-left: 4px solid #d73a49;
|
229
|
-
}
|
230
|
-
|
231
|
-
.md-entry.highlight-detail {
|
232
|
-
background-color: #fffbe6;
|
233
|
-
}
|
234
|
-
|
235
|
-
/* Markdown heading style */
|
236
|
-
.md-heading {
|
237
|
-
font-size: 1.5rem;
|
238
|
-
font-weight: 600;
|
239
|
-
margin: 0 0 16px 0;
|
240
|
-
color: #24292e;
|
241
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
242
|
-
}
|
243
|
-
|
244
|
-
/* Metadata tags */
|
245
|
-
.md-metadata {
|
246
|
-
display: flex;
|
247
|
-
flex-wrap: wrap;
|
248
|
-
gap: 8px;
|
249
|
-
margin-bottom: 16px;
|
250
|
-
}
|
251
|
-
|
252
|
-
.md-tag {
|
253
|
-
display: inline-block;
|
254
|
-
padding: 2px 8px;
|
255
|
-
border-radius: 12px;
|
256
|
-
font-size: 12px;
|
257
|
-
font-weight: 500;
|
258
|
-
background-color: #e1e4e8;
|
259
|
-
}
|
260
|
-
|
261
|
-
.md-tag.cve {
|
262
|
-
background-color: #0366d6;
|
263
|
-
color: white;
|
264
|
-
}
|
265
|
-
|
266
|
-
.md-tag.ghsa {
|
267
|
-
background-color: #6f42c1;
|
268
|
-
color: white;
|
269
|
-
}
|
270
|
-
|
271
|
-
.md-tag.severity.high, .md-tag.severity.critical {
|
272
|
-
background-color: #d73a49;
|
273
|
-
color: white;
|
274
|
-
}
|
275
|
-
|
276
|
-
.md-tag.severity.medium {
|
277
|
-
background-color: #f6a33e;
|
278
|
-
color: white;
|
279
|
-
}
|
280
|
-
|
281
|
-
.md-tag.severity.low {
|
282
|
-
background-color: #1b7c83;
|
283
|
-
color: white;
|
284
|
-
}
|
285
|
-
|
286
|
-
.md-tag.severity.unknown {
|
287
|
-
background-color: #6a737d;
|
288
|
-
color: white;
|
289
|
-
}
|
290
|
-
|
291
|
-
.md-date {
|
292
|
-
font-size: 12px;
|
293
|
-
color: #6a737d;
|
294
|
-
}
|
295
|
-
|
296
|
-
/* Markdown divider */
|
297
|
-
.md-divider {
|
298
|
-
height: 1px;
|
299
|
-
background-color: #e1e4e8;
|
300
|
-
margin: 16px 0;
|
301
|
-
}
|
302
|
-
|
303
|
-
/* Markdown content */
|
304
|
-
.md-content {
|
305
|
-
font-size: 14px;
|
306
|
-
line-height: 1.6;
|
307
|
-
color: #24292e;
|
308
|
-
margin-bottom: 16px;
|
309
|
-
}
|
310
|
-
|
311
|
-
.md-content p {
|
312
|
-
margin-bottom: 16px;
|
313
|
-
}
|
314
|
-
|
315
|
-
.md-content p:last-child {
|
316
|
-
margin-bottom: 0;
|
317
|
-
}
|
318
|
-
|
319
|
-
/* Code block style */
|
320
|
-
.md-code-block {
|
321
|
-
margin: 16px 0;
|
322
|
-
background-color: #f6f8fa;
|
323
|
-
border-radius: 6px;
|
324
|
-
overflow: hidden;
|
325
|
-
}
|
326
|
-
|
327
|
-
.md-code-header {
|
328
|
-
padding: 8px 16px;
|
329
|
-
background-color: #f1f8ff;
|
330
|
-
border-bottom: 1px solid #d0d7de;
|
331
|
-
color: #57606a;
|
332
|
-
font-size: 12px;
|
333
|
-
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
|
334
|
-
}
|
335
|
-
|
336
|
-
.md-code {
|
337
|
-
margin: 0;
|
338
|
-
padding: 16px;
|
339
|
-
overflow-x: auto;
|
340
|
-
font-size: 13px;
|
341
|
-
line-height: 1.45;
|
342
|
-
color: #24292e;
|
343
|
-
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
|
344
|
-
}
|
345
|
-
|
346
|
-
/* Link style */
|
347
|
-
.md-link {
|
348
|
-
display: flex;
|
349
|
-
align-items: center;
|
350
|
-
margin-top: 16px;
|
351
|
-
}
|
352
|
-
|
353
|
-
.md-link-icon {
|
354
|
-
margin-right: 4px;
|
355
|
-
font-size: 14px;
|
356
|
-
}
|
357
|
-
|
358
|
-
.md-link a {
|
359
|
-
color: #0366d6;
|
360
|
-
text-decoration: none;
|
361
|
-
font-size: 14px;
|
362
|
-
}
|
363
|
-
|
364
|
-
.md-link a:hover {
|
365
|
-
text-decoration: underline;
|
366
|
-
}
|
367
|
-
|
368
|
-
/* Back to top link */
|
369
|
-
.md-back {
|
370
|
-
margin-top: 24px;
|
371
|
-
font-size: 14px;
|
372
|
-
text-align: right;
|
373
|
-
}
|
374
|
-
|
375
|
-
.back-to-top {
|
376
|
-
color: #586069;
|
377
|
-
text-decoration: none;
|
378
|
-
}
|
379
|
-
|
380
|
-
.back-to-top:hover {
|
381
|
-
color: #0366d6;
|
382
|
-
text-decoration: underline;
|
383
|
-
}
|
384
|
-
|
385
|
-
.vulnerabilities-details-section h3 {
|
386
|
-
margin-bottom: 16px;
|
387
|
-
font-size: 1.2rem;
|
388
|
-
font-weight: 600;
|
389
|
-
color: #24292e;
|
390
|
-
}
|
391
|
-
</style>
|
392
|
-
|
393
|
-
<script>
|
394
|
-
document.addEventListener('DOMContentLoaded', function() {
|
395
|
-
// Copy functionality for solution buttons
|
396
|
-
document.querySelectorAll('.copy-btn').forEach(function(button) {
|
397
|
-
button.addEventListener('click', function() {
|
398
|
-
const solution = this.getAttribute('data-solution');
|
399
|
-
navigator.clipboard.writeText(solution).then(() => {
|
400
|
-
// Change button appearance temporarily
|
401
|
-
const originalText = this.innerHTML;
|
402
|
-
this.innerHTML = '<span class="copy-icon">✓</span> Copied!';
|
403
|
-
this.classList.add('copied');
|
404
|
-
|
405
|
-
setTimeout(() => {
|
406
|
-
this.innerHTML = originalText;
|
407
|
-
this.classList.remove('copied');
|
408
|
-
}, 2000);
|
409
|
-
});
|
410
|
-
});
|
411
|
-
});
|
412
|
-
|
413
|
-
// Toggle details section functionality
|
414
|
-
const toggleBtn = document.querySelector('.toggle-details-btn');
|
415
|
-
if (toggleBtn) {
|
416
|
-
toggleBtn.addEventListener('click', function() {
|
417
|
-
const detailsSection = document.querySelector('.md-container');
|
418
|
-
const isHidden = detailsSection.style.display === 'none';
|
419
|
-
|
420
|
-
if (isHidden) {
|
421
|
-
// Show the details
|
422
|
-
detailsSection.style.display = '';
|
423
|
-
this.textContent = 'Hide Details';
|
424
|
-
this.setAttribute('aria-expanded', 'true');
|
425
|
-
|
426
|
-
// Scroll to the details section
|
427
|
-
detailsSection.scrollIntoView({
|
428
|
-
behavior: 'smooth',
|
429
|
-
block: 'start'
|
430
|
-
});
|
431
|
-
} else {
|
432
|
-
// Hide the details
|
433
|
-
detailsSection.style.display = 'none';
|
434
|
-
this.textContent = 'Show Details';
|
435
|
-
this.setAttribute('aria-expanded', 'false');
|
436
|
-
}
|
437
|
-
});
|
438
|
-
}
|
439
|
-
|
440
|
-
// View details link functionality - simplified to avoid duplication
|
441
|
-
document.querySelectorAll('.view-details-link, .scroll-to-details').forEach(function(link) {
|
442
|
-
link.addEventListener('click', function(e) {
|
443
|
-
e.preventDefault();
|
444
|
-
const target = this.getAttribute('href');
|
445
|
-
const mdContainer = document.querySelector('.md-container');
|
446
|
-
|
447
|
-
// Show the details section if it's hidden
|
448
|
-
if (mdContainer.style.display === 'none') {
|
449
|
-
mdContainer.style.display = '';
|
450
|
-
const toggleBtn = document.querySelector('.toggle-details-btn');
|
451
|
-
if (toggleBtn) {
|
452
|
-
toggleBtn.textContent = 'Hide Details';
|
453
|
-
toggleBtn.setAttribute('aria-expanded', 'true');
|
454
|
-
}
|
455
|
-
}
|
456
|
-
|
457
|
-
// Scroll to the specific vulnerability
|
458
|
-
setTimeout(() => {
|
459
|
-
const targetElement = document.querySelector(target);
|
460
|
-
if (targetElement) {
|
461
|
-
const headerOffset = 70;
|
462
|
-
const elementPosition = targetElement.getBoundingClientRect().top;
|
463
|
-
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
|
464
|
-
|
465
|
-
window.scrollTo({
|
466
|
-
top: offsetPosition,
|
467
|
-
behavior: "smooth"
|
468
|
-
});
|
469
|
-
|
470
|
-
// Highlight the target element briefly
|
471
|
-
targetElement.classList.add('highlight-detail');
|
472
|
-
setTimeout(() => {
|
473
|
-
targetElement.classList.remove('highlight-detail');
|
474
|
-
}, 1500);
|
475
|
-
}
|
476
|
-
}, 100);
|
477
|
-
});
|
478
|
-
});
|
479
|
-
|
480
|
-
// Special case handler for copy-solutions button (if it exists)
|
481
|
-
document.querySelector('.filter-btn[data-filter="copy-solutions"]')?.addEventListener('click', function() {
|
482
|
-
const solutions = [];
|
483
|
-
document.querySelectorAll('.vulnerability-row').forEach(function(row) {
|
484
|
-
const gem = row.cells[0].textContent.trim();
|
485
|
-
const solution = row.querySelector('.solution code')?.textContent.trim() || "No patch available";
|
486
|
-
solutions.push(`${gem}: ${solution}`);
|
487
|
-
});
|
488
|
-
|
489
|
-
// Copy all solutions to clipboard
|
490
|
-
navigator.clipboard.writeText(solutions.join('\n')).then(() => {
|
491
|
-
alert('All solutions copied to clipboard!');
|
492
|
-
});
|
493
|
-
});
|
494
|
-
});
|
495
|
-
</script>
|
@@ -1,26 +0,0 @@
|
|
1
|
-
<%# filepath: /Users/mezbah/microstartup/infolily_organizer/gems/solidstats/app/views/solidstats/dashboard/_audit_summary.html.erb %>
|
2
|
-
<div class="audit-summary">
|
3
|
-
<div class="audit-summary-header">
|
4
|
-
<h2><span class="icon">🔒</span>Security Audit Summary</h2>
|
5
|
-
<% created_at = @audit_output.dig("created_at") %>
|
6
|
-
<span class="audit-date">Last updated: <%= created_at ? DateTime.parse(created_at).strftime("%B %d, %Y at %H:%M") : Time.now.strftime("%B %d, %Y at %H:%M") %></span>
|
7
|
-
</div>
|
8
|
-
<div class="audit-stats">
|
9
|
-
<div class="audit-stat">
|
10
|
-
<% results = @audit_output.dig("results") || [] %>
|
11
|
-
<% cve_count = results.map { |r| r.dig("advisory", "cve") }.compact.uniq.size %>
|
12
|
-
<span class="stat-value"><%= cve_count %></span>
|
13
|
-
<span class="stat-label">CVEs</span>
|
14
|
-
</div>
|
15
|
-
<div class="audit-stat">
|
16
|
-
<% gem_count = results.map { |r| r.dig("gem", "name") }.uniq.size %>
|
17
|
-
<span class="stat-value"><%= gem_count %></span>
|
18
|
-
<span class="stat-label">Affected Gems</span>
|
19
|
-
</div>
|
20
|
-
<div class="audit-stat">
|
21
|
-
<% high_severity = results.count { |r| %w[high critical].include?(r.dig("advisory", "criticality").to_s.downcase) } %>
|
22
|
-
<span class="stat-value"><%= high_severity %></span>
|
23
|
-
<span class="stat-label">High Severity</span>
|
24
|
-
</div>
|
25
|
-
</div>
|
26
|
-
</div>
|