solidstats 0.0.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +26 -0
- data/Rakefile +5 -0
- data/app/assets/stylesheets/solidstats/application.css +15 -0
- data/app/controllers/solidstats/application_controller.rb +4 -0
- data/app/controllers/solidstats/dashboard_controller.rb +55 -0
- data/app/helpers/solidstats/application_helper.rb +4 -0
- data/app/jobs/solidstats/application_job.rb +4 -0
- data/app/mailers/solidstats/application_mailer.rb +6 -0
- data/app/models/solidstats/application_record.rb +5 -0
- data/app/views/layouts/solidstats/application.html.erb +17 -0
- data/app/views/solidstats/dashboard/audit/_audit_badge.html.erb +5 -0
- data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +506 -0
- data/app/views/solidstats/dashboard/audit/_audit_filters.html.erb +5 -0
- data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +26 -0
- data/app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb +3 -0
- data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +37 -0
- data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +66 -0
- data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +62 -0
- data/app/views/solidstats/dashboard/index.html.erb +345 -0
- data/config/routes.rb +3 -0
- data/lib/solidstats/engine.rb +11 -0
- data/lib/solidstats/version.rb +3 -0
- data/lib/solidstats.rb +6 -0
- data/lib/tasks/solidstats_tasks.rake +4 -0
- metadata +96 -0
@@ -0,0 +1,26 @@
|
|
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
|
+
<h3>Security Audit Summary</h3>
|
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>
|
@@ -0,0 +1,37 @@
|
|
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
|
+
<%= render 'solidstats/dashboard/audit/audit_details' %>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
<% if Rails.env.development? %>
|
29
|
+
<!-- Debug info - hidden in production -->
|
30
|
+
<div style="margin-top: 20px; padding: 10px; border: 1px dashed #ccc; font-size: 12px; display: none;">
|
31
|
+
<h4>Debug Audit Data:</h4>
|
32
|
+
<p>Vulnerabilities count: <%= (@audit_output.dig("results") || []).size %></p>
|
33
|
+
<% if @audit_output.dig("results") %>
|
34
|
+
<p>First vulnerability gem: <%= @audit_output.dig("results", 0, "gem", "name") rescue "N/A" %></p>
|
35
|
+
<% end %>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<%# filepath: /Users/mezbah/microstartup/infolily_organizer/gems/solidstats/app/views/solidstats/audit/_vulnerabilities_table.html.erb %>
|
2
|
+
<div class="vulnerabilities-table">
|
3
|
+
<table class="table">
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th>Gem</th>
|
7
|
+
<th>Version</th>
|
8
|
+
<th>Criticality</th>
|
9
|
+
<th>CVE</th>
|
10
|
+
<th>Title</th>
|
11
|
+
<th>Solution</th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
<% results = @audit_output.dig("results") || [] %>
|
16
|
+
<% results.each_with_index do |result, index| %>
|
17
|
+
<%
|
18
|
+
gem = result.dig("gem") || {}
|
19
|
+
advisory = result.dig("advisory") || {}
|
20
|
+
criticality = advisory["criticality"] || "Unknown"
|
21
|
+
is_high = %w[high critical].include?(criticality.downcase)
|
22
|
+
%>
|
23
|
+
<tr class="vulnerability-row <%= is_high ? 'high-severity' : '' %>" data-index="<%= index %>">
|
24
|
+
<td><%= gem["name"] %></td>
|
25
|
+
<td><span class="version"><%= gem["version"] %></span></td>
|
26
|
+
<td>
|
27
|
+
<span class="severity <%= criticality.to_s.downcase %>">
|
28
|
+
<%= criticality %>
|
29
|
+
</span>
|
30
|
+
</td>
|
31
|
+
<td>
|
32
|
+
<% if advisory["cve"].present? %>
|
33
|
+
<a href="https://nvd.nist.gov/vuln/detail/CVE-<%= advisory["cve"] %>" target="_blank">
|
34
|
+
CVE-<%= advisory["cve"] %>
|
35
|
+
</a>
|
36
|
+
<% elsif advisory["ghsa"].present? %>
|
37
|
+
<a href="https://github.com/advisories/<%= advisory["ghsa"] %>" target="_blank">
|
38
|
+
GHSA-<%= advisory["ghsa"] %>
|
39
|
+
</a>
|
40
|
+
<% else %>
|
41
|
+
N/A
|
42
|
+
<% end %>
|
43
|
+
</td>
|
44
|
+
<td>
|
45
|
+
<%= advisory["title"] %>
|
46
|
+
<% if advisory["description"].present? %>
|
47
|
+
<a href="#vulnerability-<%= index %>" class="view-details-link">View details</a>
|
48
|
+
<% end %>
|
49
|
+
</td>
|
50
|
+
<td>
|
51
|
+
<div class="solution">
|
52
|
+
<% if advisory["patched_versions"].present? %>
|
53
|
+
<button class="copy-btn" data-solution="<%= "#{gem["name"]}, #{advisory["patched_versions"].join(", ")}" %>">
|
54
|
+
<span class="copy-icon">📋</span> Copy
|
55
|
+
</button>
|
56
|
+
<code><%= advisory["patched_versions"].join(", ") %></code>
|
57
|
+
<% else %>
|
58
|
+
<span class="no-patch">No patch available</span>
|
59
|
+
<% end %>
|
60
|
+
</div>
|
61
|
+
</td>
|
62
|
+
</tr>
|
63
|
+
<% end %>
|
64
|
+
</tbody>
|
65
|
+
</table>
|
66
|
+
</div>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<div class="vulnerabilities-details-section hidden">
|
2
|
+
<h3>
|
3
|
+
<span>Vulnerability Details</span>
|
4
|
+
<button class="toggle-details-btn" aria-expanded="false">Show Details</button>
|
5
|
+
</h3>
|
6
|
+
<%# TODO: Render Markdown in .erb using Redcarpet with sanitize for safe HTML output %>
|
7
|
+
<div class="md-container">
|
8
|
+
<% results.each_with_index do |result, index| %>
|
9
|
+
<%
|
10
|
+
gem = result.dig("gem") || {}
|
11
|
+
advisory = result.dig("advisory") || {}
|
12
|
+
criticality = advisory["criticality"] || "Unknown"
|
13
|
+
is_high = %w[high critical].include?(criticality.to_s.downcase)
|
14
|
+
%>
|
15
|
+
<% if advisory["description"].present? %>
|
16
|
+
<div id="vulnerability-<%= index %>" class="md-entry <%= is_high ? 'high-severity' : '' %>">
|
17
|
+
<h2 class="md-heading">
|
18
|
+
<%= gem["name"] %> <%= gem["version"] %> - <%= advisory["title"] %>
|
19
|
+
</h2>
|
20
|
+
|
21
|
+
<div class="md-metadata">
|
22
|
+
<% if advisory["cve"].present? %>
|
23
|
+
<div class="md-tag cve">CVE-<%= advisory["cve"] %></div>
|
24
|
+
<% elsif advisory["ghsa"].present? %>
|
25
|
+
<div class="md-tag ghsa">GHSA-<%= advisory["ghsa"] %></div>
|
26
|
+
<% end %>
|
27
|
+
<div class="md-tag severity <%= criticality.to_s.downcase %>"><%= criticality %></div>
|
28
|
+
<% if advisory["date"].present? %>
|
29
|
+
<div class="md-date"><%= advisory["date"] %></div>
|
30
|
+
<% end %>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div class="md-divider"></div>
|
34
|
+
|
35
|
+
<div class="md-content">
|
36
|
+
<%= simple_format(advisory["description"]) %>
|
37
|
+
</div>
|
38
|
+
|
39
|
+
<% if advisory["patched_versions"].present? %>
|
40
|
+
<div class="md-code-block">
|
41
|
+
<div class="md-code-header">Patched Versions</div>
|
42
|
+
<pre class="md-code"><%= advisory["patched_versions"].join(", ") %></pre>
|
43
|
+
</div>
|
44
|
+
<% end %>
|
45
|
+
|
46
|
+
<% if advisory["url"].present? %>
|
47
|
+
<div class="md-link">
|
48
|
+
<span class="md-link-icon">🔗</span>
|
49
|
+
<a href="<%= advisory["url"] %>" target="_blank" rel="noopener">More information</a>
|
50
|
+
</div>
|
51
|
+
<% end %>
|
52
|
+
|
53
|
+
<div class="md-back">
|
54
|
+
<a href="#" class="back-to-top" onclick="window.scrollTo({top: document.querySelector('.vulnerabilities-table').offsetTop - 20, behavior: 'smooth'}); return false;">
|
55
|
+
↑ Back to vulnerabilities table
|
56
|
+
</a>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
<% end %>
|
60
|
+
<% end %>
|
61
|
+
</div>
|
62
|
+
</div>
|
@@ -0,0 +1,345 @@
|
|
1
|
+
<div class="solidstats-dashboard">
|
2
|
+
<header class="dashboard-header">
|
3
|
+
<h1><span class="icon">🚥</span> Solidstats Dashboard</h1>
|
4
|
+
<p class="dashboard-last-updated">Last updated: <%= Time.now.strftime("%B %d, %Y at %H:%M") %></p>
|
5
|
+
</header>
|
6
|
+
|
7
|
+
<div class="stats-cards">
|
8
|
+
<%= render 'solidstats/dashboard/audit/security_audit' %>
|
9
|
+
|
10
|
+
<div class="stat-card">
|
11
|
+
<h2><span class="icon">🧹</span> Code Quality</h2>
|
12
|
+
<div class="card-content">
|
13
|
+
<div class="metric">
|
14
|
+
<!-- Your code quality metrics -->
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="stat-card">
|
20
|
+
<h2><span class="icon">📝</span> Code Health</h2>
|
21
|
+
<div class="card-content">
|
22
|
+
<!-- Your code health metrics -->
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
|
26
|
+
<div class="stat-card <%= @coverage.to_f > 80 ? 'status-ok' : (@coverage.to_f > 60 ? 'status-warning' : 'status-danger') %>">
|
27
|
+
<h2><span class="icon">🧪</span> Test Coverage</h2>
|
28
|
+
<div class="card-content">
|
29
|
+
<div class="progress-container">
|
30
|
+
<div class="progress-bar" style="width: <%= @coverage %>%"></div>
|
31
|
+
</div>
|
32
|
+
<div class="metric">
|
33
|
+
<span class="metric-value"><%= @coverage %>%</span>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
|
39
|
+
<div class="dashboard-actions">
|
40
|
+
<a href="#" class="action-button" onclick="refreshAudit(); return false;">Refresh Security Audit</a>
|
41
|
+
<a href="#" class="action-button" onclick="exportReport(); return false;">Export Report</a>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<style>
|
46
|
+
/* Base styles for dashboard */
|
47
|
+
.solidstats-dashboard {
|
48
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
49
|
+
color: #333;
|
50
|
+
max-width: 1200px;
|
51
|
+
margin: 0 auto;
|
52
|
+
padding: 20px;
|
53
|
+
}
|
54
|
+
|
55
|
+
/* Header styles */
|
56
|
+
.dashboard-header {
|
57
|
+
margin-bottom: 2rem;
|
58
|
+
}
|
59
|
+
|
60
|
+
.dashboard-header h1 {
|
61
|
+
font-size: 1.8rem;
|
62
|
+
font-weight: 600;
|
63
|
+
margin: 0 0 0.5rem 0;
|
64
|
+
}
|
65
|
+
|
66
|
+
.dashboard-last-updated {
|
67
|
+
color: #666;
|
68
|
+
margin: 0;
|
69
|
+
font-size: 0.9rem;
|
70
|
+
}
|
71
|
+
|
72
|
+
/* Card grid layout */
|
73
|
+
.stats-cards {
|
74
|
+
display: grid;
|
75
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
76
|
+
gap: 1.5rem;
|
77
|
+
margin-bottom: 2rem;
|
78
|
+
}
|
79
|
+
|
80
|
+
/* Fix for audit card to span full width when details are shown */
|
81
|
+
.audit-card {
|
82
|
+
position: relative;
|
83
|
+
transition: all 0.3s ease;
|
84
|
+
}
|
85
|
+
|
86
|
+
.audit-card.expanded {
|
87
|
+
grid-column: 1 / -1;
|
88
|
+
width: 100%;
|
89
|
+
}
|
90
|
+
|
91
|
+
/* Card styles */
|
92
|
+
.stat-card {
|
93
|
+
background: #fff;
|
94
|
+
border-radius: 8px;
|
95
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
96
|
+
padding: 1.5rem;
|
97
|
+
overflow: visible;
|
98
|
+
}
|
99
|
+
|
100
|
+
.stat-card h2 {
|
101
|
+
font-size: 1.2rem;
|
102
|
+
font-weight: 600;
|
103
|
+
margin: 0 0 1rem 0;
|
104
|
+
display: flex;
|
105
|
+
align-items: center;
|
106
|
+
gap: 0.5rem;
|
107
|
+
}
|
108
|
+
|
109
|
+
.stat-card .icon {
|
110
|
+
font-size: 1.4rem;
|
111
|
+
}
|
112
|
+
|
113
|
+
/* Metrics styling */
|
114
|
+
.metric {
|
115
|
+
display: flex;
|
116
|
+
justify-content: space-between;
|
117
|
+
align-items: center;
|
118
|
+
margin-bottom: 0.75rem;
|
119
|
+
}
|
120
|
+
|
121
|
+
.metric-label {
|
122
|
+
color: #666;
|
123
|
+
font-size: 0.9rem;
|
124
|
+
}
|
125
|
+
|
126
|
+
.metric-value {
|
127
|
+
font-weight: 600;
|
128
|
+
font-size: 1.1rem;
|
129
|
+
}
|
130
|
+
|
131
|
+
/* Status badges */
|
132
|
+
.status-badge {
|
133
|
+
display: inline-block;
|
134
|
+
padding: 0.35rem 0.75rem;
|
135
|
+
border-radius: 20px;
|
136
|
+
font-size: 0.85rem;
|
137
|
+
font-weight: 500;
|
138
|
+
margin-bottom: 1rem;
|
139
|
+
}
|
140
|
+
|
141
|
+
.status-badge.success {
|
142
|
+
background-color: #d4edda;
|
143
|
+
color: #155724;
|
144
|
+
}
|
145
|
+
|
146
|
+
.status-badge.warning {
|
147
|
+
background-color: #fff3cd;
|
148
|
+
color: #856404;
|
149
|
+
}
|
150
|
+
|
151
|
+
.status-badge.danger {
|
152
|
+
background-color: #f8d7da;
|
153
|
+
color: #721c24;
|
154
|
+
}
|
155
|
+
|
156
|
+
/* Progress bar */
|
157
|
+
.progress-container {
|
158
|
+
width: 100%;
|
159
|
+
height: 8px;
|
160
|
+
background-color: #e9ecef;
|
161
|
+
border-radius: 4px;
|
162
|
+
overflow: hidden;
|
163
|
+
margin-bottom: 0.75rem;
|
164
|
+
}
|
165
|
+
|
166
|
+
.progress-bar {
|
167
|
+
height: 100%;
|
168
|
+
background-color: #28a745;
|
169
|
+
border-radius: 4px;
|
170
|
+
}
|
171
|
+
|
172
|
+
/* Status contextual colors */
|
173
|
+
.status-ok .progress-bar {
|
174
|
+
background-color: #28a745;
|
175
|
+
}
|
176
|
+
|
177
|
+
.status-warning .progress-bar {
|
178
|
+
background-color: #ffc107;
|
179
|
+
}
|
180
|
+
|
181
|
+
.status-danger .progress-bar {
|
182
|
+
background-color: #dc3545;
|
183
|
+
}
|
184
|
+
|
185
|
+
/* Details panel styling */
|
186
|
+
.details-panel {
|
187
|
+
background: #f9f9f9;
|
188
|
+
border-top: 1px solid #eaeaea;
|
189
|
+
padding: 1.5rem;
|
190
|
+
margin-top: 1.5rem;
|
191
|
+
border-radius: 0 0 8px 8px;
|
192
|
+
overflow: visible;
|
193
|
+
}
|
194
|
+
|
195
|
+
.details-panel.hidden {
|
196
|
+
display: none;
|
197
|
+
}
|
198
|
+
|
199
|
+
.details-panel:not(.hidden) {
|
200
|
+
display: block;
|
201
|
+
}
|
202
|
+
|
203
|
+
/* Toggle details button */
|
204
|
+
.toggle-details {
|
205
|
+
display: inline-block;
|
206
|
+
color: #007bff;
|
207
|
+
cursor: pointer;
|
208
|
+
font-size: 0.9rem;
|
209
|
+
text-decoration: none;
|
210
|
+
}
|
211
|
+
|
212
|
+
.toggle-details:hover {
|
213
|
+
text-decoration: underline;
|
214
|
+
}
|
215
|
+
|
216
|
+
/* Table styling for vulnerability list */
|
217
|
+
.vulnerabilities-table {
|
218
|
+
overflow-x: auto;
|
219
|
+
margin-top: 1rem;
|
220
|
+
}
|
221
|
+
|
222
|
+
.table {
|
223
|
+
width: 100%;
|
224
|
+
border-collapse: collapse;
|
225
|
+
font-size: 0.9rem;
|
226
|
+
}
|
227
|
+
|
228
|
+
.table th {
|
229
|
+
text-align: left;
|
230
|
+
padding: 0.75rem;
|
231
|
+
background-color: #f8f9fa;
|
232
|
+
border-bottom: 2px solid #dee2e6;
|
233
|
+
}
|
234
|
+
|
235
|
+
.table td {
|
236
|
+
padding: 0.75rem;
|
237
|
+
border-bottom: 1px solid #dee2e6;
|
238
|
+
vertical-align: middle;
|
239
|
+
}
|
240
|
+
|
241
|
+
/* Severity indicators */
|
242
|
+
.severity {
|
243
|
+
display: inline-block;
|
244
|
+
padding: 0.25rem 0.5rem;
|
245
|
+
border-radius: 4px;
|
246
|
+
font-size: 0.8rem;
|
247
|
+
font-weight: 500;
|
248
|
+
}
|
249
|
+
|
250
|
+
.severity.high, .severity.critical {
|
251
|
+
background-color: #f8d7da;
|
252
|
+
color: #721c24;
|
253
|
+
}
|
254
|
+
|
255
|
+
.severity.medium {
|
256
|
+
background-color: #fff3cd;
|
257
|
+
color: #856404;
|
258
|
+
}
|
259
|
+
|
260
|
+
.severity.low {
|
261
|
+
background-color: #d1ecf1;
|
262
|
+
color: #0c5460;
|
263
|
+
}
|
264
|
+
|
265
|
+
.severity.unknown {
|
266
|
+
background-color: #e9ecef;
|
267
|
+
color: #495057;
|
268
|
+
}
|
269
|
+
|
270
|
+
/* Dashboard actions */
|
271
|
+
.dashboard-actions {
|
272
|
+
display: flex;
|
273
|
+
gap: 1rem;
|
274
|
+
margin-top: 2rem;
|
275
|
+
}
|
276
|
+
|
277
|
+
.action-button {
|
278
|
+
display: inline-block;
|
279
|
+
padding: 0.5rem 1rem;
|
280
|
+
background-color: #007bff;
|
281
|
+
color: white;
|
282
|
+
text-decoration: none;
|
283
|
+
border-radius: 4px;
|
284
|
+
font-size: 0.9rem;
|
285
|
+
cursor: pointer;
|
286
|
+
transition: background-color 0.2s;
|
287
|
+
}
|
288
|
+
|
289
|
+
.action-button:hover {
|
290
|
+
background-color: #0069d9;
|
291
|
+
}
|
292
|
+
|
293
|
+
/* Responsive adjustments */
|
294
|
+
@media (max-width: 768px) {
|
295
|
+
.stats-cards {
|
296
|
+
grid-template-columns: 1fr;
|
297
|
+
}
|
298
|
+
|
299
|
+
.dashboard-actions {
|
300
|
+
flex-direction: column;
|
301
|
+
}
|
302
|
+
|
303
|
+
.action-button {
|
304
|
+
width: 100%;
|
305
|
+
text-align: center;
|
306
|
+
}
|
307
|
+
}
|
308
|
+
</style>
|
309
|
+
|
310
|
+
<script>
|
311
|
+
document.addEventListener('DOMContentLoaded', function() {
|
312
|
+
// Toggle details panels
|
313
|
+
document.querySelectorAll('.toggle-details').forEach(function(toggle) {
|
314
|
+
toggle.addEventListener('click', function(e) {
|
315
|
+
e.preventDefault();
|
316
|
+
const targetId = this.getAttribute('data-target');
|
317
|
+
const panel = document.getElementById(targetId);
|
318
|
+
const card = this.closest('.stat-card');
|
319
|
+
|
320
|
+
panel.classList.toggle('hidden');
|
321
|
+
card.classList.toggle('expanded');
|
322
|
+
|
323
|
+
this.textContent = panel.classList.contains('hidden') ? 'Show Details' : 'Hide Details';
|
324
|
+
|
325
|
+
// If showing details, scroll to it for better visibility
|
326
|
+
if (!panel.classList.contains('hidden')) {
|
327
|
+
setTimeout(() => {
|
328
|
+
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
329
|
+
}, 100);
|
330
|
+
}
|
331
|
+
});
|
332
|
+
});
|
333
|
+
});
|
334
|
+
|
335
|
+
function refreshAudit() {
|
336
|
+
// Implement refresh functionality
|
337
|
+
alert('Refreshing audit data...');
|
338
|
+
location.reload();
|
339
|
+
}
|
340
|
+
|
341
|
+
function exportReport() {
|
342
|
+
// Implement export functionality
|
343
|
+
alert('Exporting report...');
|
344
|
+
}
|
345
|
+
</script>
|
data/config/routes.rb
ADDED
data/lib/solidstats.rb
ADDED
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: solidstats
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- MezbahAlam
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-05-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler-audit
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: 'View local project health: secuiry, lints, performance, and more.'
|
42
|
+
email:
|
43
|
+
- mezbah@infolily.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- MIT-LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- app/assets/stylesheets/solidstats/application.css
|
52
|
+
- app/controllers/solidstats/application_controller.rb
|
53
|
+
- app/controllers/solidstats/dashboard_controller.rb
|
54
|
+
- app/helpers/solidstats/application_helper.rb
|
55
|
+
- app/jobs/solidstats/application_job.rb
|
56
|
+
- app/mailers/solidstats/application_mailer.rb
|
57
|
+
- app/models/solidstats/application_record.rb
|
58
|
+
- app/views/layouts/solidstats/application.html.erb
|
59
|
+
- app/views/solidstats/dashboard/audit/_audit_badge.html.erb
|
60
|
+
- app/views/solidstats/dashboard/audit/_audit_details.html.erb
|
61
|
+
- app/views/solidstats/dashboard/audit/_audit_filters.html.erb
|
62
|
+
- app/views/solidstats/dashboard/audit/_audit_summary.html.erb
|
63
|
+
- app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb
|
64
|
+
- app/views/solidstats/dashboard/audit/_security_audit.html.erb
|
65
|
+
- app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb
|
66
|
+
- app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb
|
67
|
+
- app/views/solidstats/dashboard/index.html.erb
|
68
|
+
- config/routes.rb
|
69
|
+
- lib/solidstats.rb
|
70
|
+
- lib/solidstats/engine.rb
|
71
|
+
- lib/solidstats/version.rb
|
72
|
+
- lib/tasks/solidstats_tasks.rake
|
73
|
+
homepage: https://solidstats.infolily.com
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.4.19
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Development dashboard for Rails apps
|
96
|
+
test_files: []
|