solidstats 0.0.4 → 1.0.0
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 +61 -5
- data/README.md +52 -5
- data/app/controllers/solidstats/dashboard_controller.rb +43 -123
- data/app/services/solidstats/audit_service.rb +56 -0
- data/app/services/solidstats/data_collector_service.rb +83 -0
- data/app/services/solidstats/todo_service.rb +114 -0
- data/app/views/solidstats/dashboard/_todos.html.erb +6 -27
- data/app/views/solidstats/dashboard/audit/_additional_styles.css +22 -0
- data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +47 -58
- data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +1 -1
- data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -23
- data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +1092 -38
- data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +4 -3
- data/app/views/solidstats/dashboard/index.html.erb +1152 -162
- data/config/routes.rb +1 -0
- data/lib/solidstats/version.rb +1 -1
- data/lib/tasks/solidstats_release.rake +69 -0
- metadata +10 -5
- data/app/views/solidstats/dashboard/audit/_audit_filters.html.erb +0 -5
@@ -1,11 +1,10 @@
|
|
1
|
-
<div id="audit-details"
|
1
|
+
<div id="audit-details">
|
2
2
|
<%= render partial: 'solidstats/dashboard/audit/audit_summary' %>
|
3
3
|
|
4
4
|
<% results = @audit_output.dig("results") || [] %>
|
5
5
|
<% if results.empty? %>
|
6
6
|
<%= render partial: 'solidstats/dashboard/audit/no_vulnerabilities' %>
|
7
7
|
<% else %>
|
8
|
-
<%= render partial: 'solidstats/dashboard/audit/audit_filters' %>
|
9
8
|
<%= render partial: 'solidstats/dashboard/audit/vulnerabilities_table', locals: { results: results } %>
|
10
9
|
<%= render partial: 'solidstats/dashboard/audit/vulnerability_details', locals: { results: results } %>
|
11
10
|
<% end %>
|
@@ -159,12 +158,6 @@
|
|
159
158
|
border-left: 4px solid #dc3545;
|
160
159
|
}
|
161
160
|
|
162
|
-
.vulnerability-title {
|
163
|
-
margin-top: 0;
|
164
|
-
margin-bottom: 0.75rem;
|
165
|
-
font-size: 1.1rem;
|
166
|
-
font-weight: 600;
|
167
|
-
}
|
168
161
|
|
169
162
|
.vulnerability-meta {
|
170
163
|
display: flex;
|
@@ -224,6 +217,7 @@
|
|
224
217
|
border-bottom: 1px solid #e1e4e8;
|
225
218
|
padding: 24px;
|
226
219
|
background-color: #ffffff;
|
220
|
+
transition: background-color 0.3s ease;
|
227
221
|
}
|
228
222
|
|
229
223
|
.md-entry:last-child {
|
@@ -234,6 +228,10 @@
|
|
234
228
|
border-left: 4px solid #d73a49;
|
235
229
|
}
|
236
230
|
|
231
|
+
.md-entry.highlight-detail {
|
232
|
+
background-color: #fffbe6;
|
233
|
+
}
|
234
|
+
|
237
235
|
/* Markdown heading style */
|
238
236
|
.md-heading {
|
239
237
|
font-size: 1.5rem;
|
@@ -416,12 +414,12 @@
|
|
416
414
|
const toggleBtn = document.querySelector('.toggle-details-btn');
|
417
415
|
if (toggleBtn) {
|
418
416
|
toggleBtn.addEventListener('click', function() {
|
419
|
-
const detailsSection = document.querySelector('.
|
420
|
-
const isHidden = detailsSection.
|
417
|
+
const detailsSection = document.querySelector('.md-container');
|
418
|
+
const isHidden = detailsSection.style.display === 'none';
|
421
419
|
|
422
420
|
if (isHidden) {
|
423
421
|
// Show the details
|
424
|
-
detailsSection.
|
422
|
+
detailsSection.style.display = '';
|
425
423
|
this.textContent = 'Hide Details';
|
426
424
|
this.setAttribute('aria-expanded', 'true');
|
427
425
|
|
@@ -432,7 +430,7 @@
|
|
432
430
|
});
|
433
431
|
} else {
|
434
432
|
// Hide the details
|
435
|
-
detailsSection.
|
433
|
+
detailsSection.style.display = 'none';
|
436
434
|
this.textContent = 'Show Details';
|
437
435
|
this.setAttribute('aria-expanded', 'false');
|
438
436
|
}
|
@@ -440,66 +438,57 @@
|
|
440
438
|
}
|
441
439
|
|
442
440
|
// View details link functionality - simplified to avoid duplication
|
443
|
-
document.querySelectorAll('.view-details-link').forEach(function(link) {
|
441
|
+
document.querySelectorAll('.view-details-link, .scroll-to-details').forEach(function(link) {
|
444
442
|
link.addEventListener('click', function(e) {
|
445
443
|
e.preventDefault();
|
446
444
|
const target = this.getAttribute('href');
|
447
|
-
const
|
445
|
+
const mdContainer = document.querySelector('.md-container');
|
448
446
|
|
449
447
|
// Show the details section if it's hidden
|
450
|
-
if (
|
451
|
-
|
448
|
+
if (mdContainer.style.display === 'none') {
|
449
|
+
mdContainer.style.display = '';
|
452
450
|
const toggleBtn = document.querySelector('.toggle-details-btn');
|
453
|
-
toggleBtn
|
454
|
-
|
451
|
+
if (toggleBtn) {
|
452
|
+
toggleBtn.textContent = 'Hide Details';
|
453
|
+
toggleBtn.setAttribute('aria-expanded', 'true');
|
454
|
+
}
|
455
455
|
}
|
456
456
|
|
457
457
|
// Scroll to the specific vulnerability
|
458
458
|
setTimeout(() => {
|
459
|
-
document.querySelector(target)
|
460
|
-
|
461
|
-
|
462
|
-
|
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
|
+
}
|
463
476
|
}, 100);
|
464
477
|
});
|
465
478
|
});
|
466
479
|
|
467
|
-
//
|
468
|
-
document.
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
document.querySelectorAll('.vulnerability-row').forEach(function(row) {
|
480
|
-
const gem = row.cells[0].textContent.trim();
|
481
|
-
const solution = row.querySelector('.solution code')?.textContent.trim() || "No patch available";
|
482
|
-
solutions.push(`${gem}: ${solution}`);
|
483
|
-
});
|
484
|
-
|
485
|
-
// Copy all solutions to clipboard
|
486
|
-
navigator.clipboard.writeText(solutions.join('\n')).then(() => {
|
487
|
-
alert('All solutions copied to clipboard!');
|
488
|
-
});
|
489
|
-
|
490
|
-
return;
|
491
|
-
}
|
492
|
-
|
493
|
-
// Filter the vulnerability rows
|
494
|
-
document.querySelectorAll('.vulnerability-row').forEach(function(row) {
|
495
|
-
const isHigh = row.classList.contains('high-severity');
|
496
|
-
|
497
|
-
if (filter === 'all') {
|
498
|
-
row.style.display = '';
|
499
|
-
} else if (filter === 'high') {
|
500
|
-
row.style.display = isHigh ? '' : 'none';
|
501
|
-
}
|
502
|
-
});
|
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!');
|
503
492
|
});
|
504
493
|
});
|
505
494
|
});
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<%# filepath: /Users/mezbah/microstartup/infolily_organizer/gems/solidstats/app/views/solidstats/dashboard/_audit_summary.html.erb %>
|
2
2
|
<div class="audit-summary">
|
3
3
|
<div class="audit-summary-header">
|
4
|
-
<
|
4
|
+
<h2><span class="icon">🔒</span>Security Audit Summary</h2>
|
5
5
|
<% created_at = @audit_output.dig("created_at") %>
|
6
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
7
|
</div>
|
@@ -1,27 +1,4 @@
|
|
1
1
|
<div class="stat-card audit-card">
|
2
|
-
<h2><span class="icon">🔒</span> Security Audit</h2>
|
3
|
-
<div class="card-content">
|
4
|
-
<% results = @audit_output.dig("results") || [] %>
|
5
|
-
<% vulnerabilities_count = results.size %>
|
6
|
-
<% if vulnerabilities_count == 0 %>
|
7
|
-
<div class="status-badge success">All Clear</div>
|
8
|
-
<% else %>
|
9
|
-
<div class="status-badge warning"><%= vulnerabilities_count %> <%= "Vulnerability".pluralize(vulnerabilities_count) %> Found</div>
|
10
|
-
<% end %>
|
11
|
-
|
12
|
-
<div class="metrics-group">
|
13
|
-
<div class="metric">
|
14
|
-
<span class="metric-label">Affected Gems:</span>
|
15
|
-
<span class="metric-value"><%= results.map { |r| r.dig("gem", "name") }.uniq.size %></span>
|
16
|
-
</div>
|
17
|
-
<div class="metric">
|
18
|
-
<span class="metric-label">High Severity:</span>
|
19
|
-
<span class="metric-value"><%= results.count { |r| %w[high critical].include?(r.dig("advisory", "criticality").to_s.downcase) } %></span>
|
20
|
-
</div>
|
21
|
-
</div>
|
22
|
-
|
23
|
-
<a href="#" class="toggle-details" data-target="audit-details">Show Details</a>
|
24
|
-
</div>
|
25
2
|
<%= render 'solidstats/dashboard/audit/audit_details' %>
|
26
3
|
</div>
|
27
4
|
|