solidstats 2.0.0 → 3.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 +12 -63
- data/README.md +27 -0
- data/Rakefile +3 -3
- data/app/assets/javascripts/solidstats/dashboard.js +0 -46
- data/app/assets/stylesheets/solidstats/dashboard.css +48 -0
- data/app/controllers/solidstats/dashboard_controller.rb +73 -61
- 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 +124 -11
- 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 +474 -0
- data/app/services/solidstats/log_size_monitor_service.rb +197 -66
- 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 +8 -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 +31 -6
- data/lib/generators/solidstats/clean/clean_generator.rb +24 -0
- data/lib/generators/solidstats/clean/templates/README +8 -0
- data/lib/generators/solidstats/install/install_generator.rb +51 -10
- data/lib/generators/solidstats/install/templates/README +7 -0
- data/lib/solidstats/engine.rb +6 -71
- data/lib/solidstats/version.rb +1 -1
- data/lib/solidstats.rb +19 -303
- data/lib/tasks/solidstats.rake +67 -0
- data/lib/tasks/solidstats_performance.rake +61 -0
- data/lib/tasks/solidstats_tasks.rake +16 -4
- metadata +33 -95
- data/app/assets/javascripts/solidstats/gem_metadata.js +0 -554
- data/app/assets/stylesheets/solidstats/components/action_button.css +0 -99
- data/app/assets/stylesheets/solidstats/components/dashboard.css +0 -151
- data/app/assets/stylesheets/solidstats/components/dashboard_header.css +0 -93
- data/app/assets/stylesheets/solidstats/components/dashboard_layout.css +0 -97
- data/app/assets/stylesheets/solidstats/components/gem_metadata.css +0 -1403
- data/app/assets/stylesheets/solidstats/components/navigation.css +0 -80
- data/app/assets/stylesheets/solidstats/components/quick_navigation.css +0 -54
- data/app/assets/stylesheets/solidstats/components/security.css +0 -332
- data/app/assets/stylesheets/solidstats/components/status_badge.css +0 -58
- data/app/assets/stylesheets/solidstats/components/summary_card.css +0 -66
- data/app/assets/stylesheets/solidstats/components/tab_navigation.css +0 -95
- data/app/components/solidstats/base_component.rb +0 -88
- data/app/components/solidstats/code_quality/code_quality_section_component.html.erb +0 -0
- data/app/components/solidstats/code_quality/code_quality_section_component.rb +0 -0
- data/app/components/solidstats/code_quality/section_component.html.erb +0 -45
- data/app/components/solidstats/code_quality/section_component.rb +0 -34
- data/app/components/solidstats/dashboard_header_component.html.erb +0 -39
- data/app/components/solidstats/dashboard_header_component.rb +0 -33
- data/app/components/solidstats/previews/action_button_component_preview/button_vs_link.html.erb +0 -6
- data/app/components/solidstats/previews/action_button_component_preview/sizes.html.erb +0 -6
- data/app/components/solidstats/previews/action_button_component_preview/variants.html.erb +0 -6
- data/app/components/solidstats/previews/action_button_component_preview/with_icons.html.erb +0 -6
- data/app/components/solidstats/previews/action_button_component_preview.rb +0 -64
- data/app/components/solidstats/previews/navigation_component_preview.rb +0 -74
- data/app/components/solidstats/previews/stats_overview_component_preview.rb +0 -100
- data/app/components/solidstats/previews/status_badge_component_preview/sizes.html.erb +0 -6
- data/app/components/solidstats/previews/status_badge_component_preview/statuses.html.erb +0 -6
- data/app/components/solidstats/previews/status_badge_component_preview/with_icons.html.erb +0 -6
- data/app/components/solidstats/previews/status_badge_component_preview.rb +0 -49
- data/app/components/solidstats/previews/summary_card_component_preview/clickable.html.erb +0 -9
- data/app/components/solidstats/previews/summary_card_component_preview/dashboard_layout.html.erb +0 -9
- data/app/components/solidstats/previews/summary_card_component_preview/statuses.html.erb +0 -6
- data/app/components/solidstats/previews/summary_card_component_preview/value_formats.html.erb +0 -6
- data/app/components/solidstats/previews/summary_card_component_preview.rb +0 -67
- data/app/components/solidstats/quick_navigation_component.html.erb +0 -8
- data/app/components/solidstats/quick_navigation_component.rb +0 -21
- data/app/components/solidstats/security/gem_impact_analysis_component.html.erb +0 -44
- data/app/components/solidstats/security/gem_impact_analysis_component.rb +0 -45
- data/app/components/solidstats/security/overview_component.html.erb +0 -21
- data/app/components/solidstats/security/overview_component.rb +0 -104
- data/app/components/solidstats/security/section_component.html.erb +0 -26
- data/app/components/solidstats/security/section_component.rb +0 -52
- data/app/components/solidstats/security/timeline_component.html.erb +0 -39
- data/app/components/solidstats/security/timeline_component.rb +0 -43
- data/app/components/solidstats/tasks_section_component.html.erb +0 -17
- data/app/components/solidstats/tasks_section_component.rb +0 -22
- data/app/components/solidstats/ui/action_button_component.html.erb +0 -6
- data/app/components/solidstats/ui/action_button_component.rb +0 -71
- data/app/components/solidstats/ui/dashboard_layout_component.html.erb +0 -19
- data/app/components/solidstats/ui/dashboard_layout_component.rb +0 -85
- data/app/components/solidstats/ui/navigation_component.html.erb +0 -34
- data/app/components/solidstats/ui/navigation_component.rb +0 -72
- data/app/components/solidstats/ui/stats_overview_component.html.erb +0 -14
- data/app/components/solidstats/ui/stats_overview_component.rb +0 -78
- data/app/components/solidstats/ui/status_badge_component.html.erb +0 -6
- data/app/components/solidstats/ui/status_badge_component.rb +0 -42
- data/app/components/solidstats/ui/summary_card_component.html.erb +0 -12
- data/app/components/solidstats/ui/summary_card_component.rb +0 -63
- data/app/components/solidstats/ui/tab_navigation_component.html.erb +0 -22
- data/app/components/solidstats/ui/tab_navigation_component.rb +0 -79
- data/app/services/solidstats/audit_service.rb +0 -56
- data/app/services/solidstats/data_collector_service.rb +0 -83
- data/app/services/solidstats/gem_metadata/fetcher_service.rb +0 -136
- 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 -81
- data/app/views/solidstats/gem_metadata/_panel.html.erb +0 -419
- data/lib/generators/solidstats/feature/feature_generator.rb +0 -170
- data/lib/generators/solidstats/feature/templates/component.html.erb +0 -84
- data/lib/generators/solidstats/feature/templates/component.rb.erb +0 -103
- data/lib/generators/solidstats/feature/templates/component.scss +0 -243
- data/lib/generators/solidstats/feature/templates/component_test.rb.erb +0 -183
- data/lib/generators/solidstats/feature/templates/controller.rb.erb +0 -44
- data/lib/generators/solidstats/feature/templates/controller_test.rb.erb +0 -111
- data/lib/generators/solidstats/feature/templates/detail_view.html.erb +0 -755
- data/lib/generators/solidstats/feature/templates/preview.rb.erb +0 -107
- data/lib/generators/solidstats/feature/templates/service.rb.erb +0 -132
- data/lib/generators/solidstats/feature/templates/service_test.rb.erb +0 -109
- data/lib/generators/solidstats/install_generator.rb +0 -109
- data/lib/tasks/solidstats_install.rake +0 -133
@@ -0,0 +1,345 @@
|
|
1
|
+
<%#
|
2
|
+
Security Vulnerabilities - Bundler Audit Page
|
3
|
+
Shows all security vulnerabilities found by bundler audit with details
|
4
|
+
%>
|
5
|
+
|
6
|
+
<div class="dashboard-enter">
|
7
|
+
<!-- Breadcrumb Navigation -->
|
8
|
+
<div class="breadcrumbs text-sm mb-6">
|
9
|
+
<ul>
|
10
|
+
<li>
|
11
|
+
<%= link_to solidstats.solidstats_dashboard_path, class: "link link-hover" do %>
|
12
|
+
<i data-feather="home" class="w-4 h-4 mr-1"></i>
|
13
|
+
Dashboard
|
14
|
+
<% end %>
|
15
|
+
</li>
|
16
|
+
<li>Security Vulnerabilities</li>
|
17
|
+
</ul>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<!-- Page Header -->
|
21
|
+
<div class="hero bg-base-200 rounded-xl mb-8">
|
22
|
+
<div class="hero-content text-center py-8">
|
23
|
+
<div class="max-w-2xl">
|
24
|
+
<h1 class="text-4xl font-bold mb-4">Security Vulnerabilities</h1>
|
25
|
+
<p class="text-base-content/70 mb-6">
|
26
|
+
Monitor and manage security vulnerabilities in your gem dependencies
|
27
|
+
</p>
|
28
|
+
|
29
|
+
<!-- Action Buttons -->
|
30
|
+
<div class="flex flex-wrap justify-center gap-3">
|
31
|
+
<%= button_to refresh_securities_bundler_audit_path,
|
32
|
+
method: :post,
|
33
|
+
class: "btn btn-primary",
|
34
|
+
data: { confirm: "Refresh security scan? This may take a moment." } do %>
|
35
|
+
<i data-feather="refresh-cw" class="w-4 h-4 mr-2"></i>
|
36
|
+
Refresh Scan
|
37
|
+
<% end %>
|
38
|
+
<button onclick="downloadVulnerabilitiesData()" class="btn btn-outline">
|
39
|
+
<i data-feather="download" class="w-4 h-4 mr-2"></i>
|
40
|
+
Export Data
|
41
|
+
</button>
|
42
|
+
<%= link_to solidstats.solidstats_dashboard_path, class: "btn btn-ghost" do %>
|
43
|
+
<i data-feather="arrow-left" class="w-4 h-4 mr-2"></i>
|
44
|
+
Back to Dashboard
|
45
|
+
<% end %>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
|
51
|
+
<% if @vulnerabilities_data.present? %>
|
52
|
+
|
53
|
+
<!-- Summary Stats -->
|
54
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
55
|
+
<div class="card bg-base-100 shadow-xl border border-base-300">
|
56
|
+
<div class="card-body p-6">
|
57
|
+
<div class="flex items-center">
|
58
|
+
<div class="flex-shrink-0">
|
59
|
+
<div class="flex items-center justify-center w-8 h-8 bg-<%= @summary[:status] == 'success' ? 'success' : @summary[:status] == 'warning' ? 'warning' : 'error' %>/20 rounded-md">
|
60
|
+
<svg class="w-5 h-5 text-<%= @summary[:status] == 'success' ? 'success' : @summary[:status] == 'warning' ? 'warning' : 'error' %>" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
61
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 0h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
62
|
+
</svg>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
<div class="ml-5 w-0 flex-1">
|
66
|
+
<dl>
|
67
|
+
<dt class="text-sm font-medium text-base-content/70 truncate">Total Vulnerabilities</dt>
|
68
|
+
<dd class="text-lg font-medium text-base-content"><%= @summary[:count] %></dd>
|
69
|
+
</dl>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
<% %w[critical high medium low].each do |severity| %>
|
76
|
+
<% count = @vulnerabilities_by_severity[severity]&.count || 0 %>
|
77
|
+
<div class="card bg-base-100 shadow-xl border border-base-300">
|
78
|
+
<div class="card-body p-6">
|
79
|
+
<div class="flex items-center">
|
80
|
+
<div class="flex-shrink-0">
|
81
|
+
<div class="flex items-center justify-center w-8 h-8 bg-<%= severity == 'critical' ? 'error' : severity == 'high' ? 'warning' : severity == 'medium' ? 'info' : 'success' %>/20 rounded-md">
|
82
|
+
<span class="text-<%= severity == 'critical' ? 'error' : severity == 'high' ? 'warning' : severity == 'medium' ? 'info' : 'success' %> font-semibold text-xs uppercase"><%= severity[0] %></span>
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
<div class="ml-5 w-0 flex-1">
|
86
|
+
<dl>
|
87
|
+
<dt class="text-sm font-medium text-base-content/70 truncate"><%= severity.capitalize %></dt>
|
88
|
+
<dd class="text-lg font-medium text-base-content"><%= count %></dd>
|
89
|
+
</dl>
|
90
|
+
</div>
|
91
|
+
</div>
|
92
|
+
</div>
|
93
|
+
</div>
|
94
|
+
<% end %>
|
95
|
+
</div>
|
96
|
+
<% else %>
|
97
|
+
<div class="alert alert-info">
|
98
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
99
|
+
<span>No vulnerability data available. Please run a security scan first.</span>
|
100
|
+
</div>
|
101
|
+
<% end %>
|
102
|
+
|
103
|
+
<!-- Last Updated Info -->
|
104
|
+
<% if @last_updated %>
|
105
|
+
<div class="mb-6 p-4 bg-gradient-to-r from-base-200 to-base-300 border border-base-300 rounded-lg shadow-sm">
|
106
|
+
<div class="flex items-center justify-between">
|
107
|
+
<div class="flex items-center space-x-3">
|
108
|
+
<div class="flex-shrink-0">
|
109
|
+
<div class="flex items-center justify-center w-8 h-8 bg-info/20 rounded-full">
|
110
|
+
<svg class="w-4 h-4 text-info" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
111
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
112
|
+
</svg>
|
113
|
+
</div>
|
114
|
+
</div>
|
115
|
+
<div>
|
116
|
+
<p class="text-sm font-medium text-base-content">Security Scan Status</p>
|
117
|
+
<p class="text-xs text-base-content/70">
|
118
|
+
Last scanned:
|
119
|
+
<span class="font-semibold text-base-content">
|
120
|
+
<%= time_ago_in_words(Time.parse(@last_updated.to_s)) %> ago
|
121
|
+
</span>
|
122
|
+
<span class="text-base-content/50 ml-2">
|
123
|
+
(<%= Time.parse(@last_updated.to_s).strftime("%B %d, %Y at %I:%M %p") %>)
|
124
|
+
</span>
|
125
|
+
</p>
|
126
|
+
</div>
|
127
|
+
</div>
|
128
|
+
<div class="flex items-center space-x-2">
|
129
|
+
<% scan_age_hours = ((Time.current - Time.parse(@last_updated.to_s)) / 1.hour).round %>
|
130
|
+
<% if scan_age_hours < 1 %>
|
131
|
+
<span class="badge badge-success badge-sm">Fresh</span>
|
132
|
+
<% elsif scan_age_hours < 24 %>
|
133
|
+
<span class="badge badge-info badge-sm">Recent</span>
|
134
|
+
<% elsif scan_age_hours < 168 %>
|
135
|
+
<span class="badge badge-warning badge-sm">Aging</span>
|
136
|
+
<% else %>
|
137
|
+
<span class="badge badge-error badge-sm">Stale</span>
|
138
|
+
<% end %>
|
139
|
+
</div>
|
140
|
+
</div>
|
141
|
+
|
142
|
+
<% if scan_age_hours >= 24 %>
|
143
|
+
<div class="mt-3 p-3 bg-warning/10 border border-warning/30 rounded-md">
|
144
|
+
<div class="flex items-start space-x-2">
|
145
|
+
<svg class="w-4 h-4 text-warning mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
146
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16c-.77.833.192 2.5 1.732 2.5z"/>
|
147
|
+
</svg>
|
148
|
+
<div>
|
149
|
+
<p class="text-sm font-medium text-warning">Consider refreshing the security scan</p>
|
150
|
+
<p class="text-xs text-warning/80 mt-1">
|
151
|
+
Data is <%= scan_age_hours >= 168 ? "over a week" : "over a day" %> old.
|
152
|
+
New vulnerabilities may have been discovered since the last scan.
|
153
|
+
</p>
|
154
|
+
</div>
|
155
|
+
</div>
|
156
|
+
</div>
|
157
|
+
<% end %>
|
158
|
+
</div>
|
159
|
+
<% else %>
|
160
|
+
<div class="mb-6 p-4 bg-warning/10 border border-warning/30 rounded-lg">
|
161
|
+
<div class="flex items-center space-x-3">
|
162
|
+
<div class="flex-shrink-0">
|
163
|
+
<div class="flex items-center justify-center w-8 h-8 bg-warning/20 rounded-full">
|
164
|
+
<svg class="w-4 h-4 text-warning" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
165
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16c-.77.833.192 2.5 1.732 2.5z"/>
|
166
|
+
</svg>
|
167
|
+
</div>
|
168
|
+
</div>
|
169
|
+
<div>
|
170
|
+
<p class="text-sm font-medium text-warning">No scan data available</p>
|
171
|
+
<p class="text-xs text-warning/80">
|
172
|
+
Please run your first security scan to check for vulnerabilities in your dependencies.
|
173
|
+
</p>
|
174
|
+
</div>
|
175
|
+
</div>
|
176
|
+
</div>
|
177
|
+
<% end %>
|
178
|
+
|
179
|
+
<!-- Vulnerabilities List -->
|
180
|
+
<% if @vulnerabilities.any? %>
|
181
|
+
<div class="card bg-base-100 shadow-xl border border-base-300">
|
182
|
+
<div class="card-header px-6 py-4 border-b border-base-300">
|
183
|
+
<h3 class="text-lg font-medium text-base-content">Security Vulnerabilities Found</h3>
|
184
|
+
<p class="mt-1 text-sm text-base-content/70">
|
185
|
+
<%= pluralize(@vulnerabilities.count, 'vulnerability') %> detected in your gem dependencies
|
186
|
+
</p>
|
187
|
+
</div>
|
188
|
+
|
189
|
+
<div class="divide-y divide-base-300">
|
190
|
+
<% @vulnerabilities.each_with_index do |vulnerability, index| %>
|
191
|
+
<div class="px-6 py-4 hover:bg-base-200/50 transition-colors">
|
192
|
+
<div class="flex items-start justify-between">
|
193
|
+
<div class="flex-1">
|
194
|
+
<div class="flex items-center space-x-3 mb-2">
|
195
|
+
<span class="badge badge-<%= vulnerability.dig('advisory', 'criticality') == 'critical' ? 'error' : vulnerability.dig('advisory', 'criticality') == 'high' ? 'warning' : vulnerability.dig('advisory', 'criticality') == 'medium' ? 'info' : 'success' %> badge-outline">
|
196
|
+
<%= vulnerability.dig('advisory', 'criticality')&.capitalize || 'Unknown' %>
|
197
|
+
</span>
|
198
|
+
<h4 class="text-lg font-medium text-base-content">
|
199
|
+
<%= vulnerability.dig('gem', 'name') %> v<%= vulnerability.dig('gem', 'version') %>
|
200
|
+
</h4>
|
201
|
+
</div>
|
202
|
+
|
203
|
+
<h5 class="text-md font-semibold text-base-content mb-2">
|
204
|
+
<%= vulnerability.dig('advisory', 'title') %>
|
205
|
+
</h5>
|
206
|
+
|
207
|
+
<p class="text-sm text-base-content/70 mb-3">
|
208
|
+
<%= vulnerability.dig('advisory', 'description') %>
|
209
|
+
</p>
|
210
|
+
|
211
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
212
|
+
<div>
|
213
|
+
<strong class="text-base-content/80">CVE ID:</strong>
|
214
|
+
<% if vulnerability.dig('advisory', 'cve') %>
|
215
|
+
<span class="text-base-content"><%= vulnerability.dig('advisory', 'cve') %></span>
|
216
|
+
<% else %>
|
217
|
+
<span class="text-base-content/50">N/A</span>
|
218
|
+
<% end %>
|
219
|
+
</div>
|
220
|
+
|
221
|
+
<div>
|
222
|
+
<strong class="text-base-content/80">Advisory Date:</strong>
|
223
|
+
<span class="text-base-content"><%= vulnerability.dig('advisory', 'date') %></span>
|
224
|
+
</div>
|
225
|
+
|
226
|
+
<div>
|
227
|
+
<strong class="text-base-content/80">Patched Versions:</strong>
|
228
|
+
<% patched_versions = vulnerability.dig('advisory', 'patched_versions') %>
|
229
|
+
<% if patched_versions&.any? %>
|
230
|
+
<div class="flex items-center gap-2 mt-1">
|
231
|
+
<span class="text-success font-medium">
|
232
|
+
<%= patched_versions.join(', ') %>
|
233
|
+
</span>
|
234
|
+
<button
|
235
|
+
onclick="copyToClipboard('<%= patched_versions.join(', ').gsub("'", "\\'") %>', this)"
|
236
|
+
class="btn btn-xs btn-ghost text-base-content/60 hover:text-base-content"
|
237
|
+
title="Copy patched versions">
|
238
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
239
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
240
|
+
</svg>
|
241
|
+
</button>
|
242
|
+
</div>
|
243
|
+
<% else %>
|
244
|
+
<span class="text-base-content/50">None available</span>
|
245
|
+
<% end %>
|
246
|
+
</div>
|
247
|
+
|
248
|
+
<div>
|
249
|
+
<strong class="text-base-content/80">GHSA ID:</strong>
|
250
|
+
<% if vulnerability.dig('advisory', 'ghsa') %>
|
251
|
+
<span class="text-base-content"><%= vulnerability.dig('advisory', 'ghsa') %></span>
|
252
|
+
<% else %>
|
253
|
+
<span class="text-base-content/50">N/A</span>
|
254
|
+
<% end %>
|
255
|
+
</div>
|
256
|
+
</div>
|
257
|
+
|
258
|
+
<% if vulnerability.dig('advisory', 'url') %>
|
259
|
+
<div class="mt-3">
|
260
|
+
<%= link_to vulnerability.dig('advisory', 'url'), target: '_blank', class: "link link-primary inline-flex items-center text-sm" do %>
|
261
|
+
View Advisory
|
262
|
+
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
263
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
264
|
+
</svg>
|
265
|
+
<% end %>
|
266
|
+
</div>
|
267
|
+
<% end %>
|
268
|
+
</div>
|
269
|
+
</div>
|
270
|
+
</div>
|
271
|
+
<% end %>
|
272
|
+
</div>
|
273
|
+
</div>
|
274
|
+
<% else %>
|
275
|
+
<!-- No Vulnerabilities Found -->
|
276
|
+
<div class="card bg-base-100 shadow-xl border border-base-300">
|
277
|
+
<div class="card-body text-center py-12">
|
278
|
+
<div class="flex items-center justify-center w-16 h-16 bg-success/20 rounded-full mx-auto mb-4">
|
279
|
+
<svg class="w-8 h-8 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
280
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
281
|
+
</svg>
|
282
|
+
</div>
|
283
|
+
<h3 class="text-lg font-medium text-base-content mb-2">No Security Vulnerabilities Found</h3>
|
284
|
+
<p class="text-base-content/70 mb-4">
|
285
|
+
Great news! Your gem dependencies don't have any known security vulnerabilities.
|
286
|
+
</p>
|
287
|
+
<p class="text-sm text-base-content/50">
|
288
|
+
Keep your gems updated regularly to maintain security.
|
289
|
+
</p>
|
290
|
+
</div>
|
291
|
+
</div>
|
292
|
+
<% end %>
|
293
|
+
</div>
|
294
|
+
|
295
|
+
<script>
|
296
|
+
function downloadVulnerabilitiesData() {
|
297
|
+
// Convert vulnerabilities data to JSON and download
|
298
|
+
const data = <%= raw @vulnerabilities.to_json %>;
|
299
|
+
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2));
|
300
|
+
const downloadAnchorNode = document.createElement('a');
|
301
|
+
downloadAnchorNode.setAttribute("href", dataStr);
|
302
|
+
downloadAnchorNode.setAttribute("download", "vulnerabilities_" + new Date().toISOString().split('T')[0] + ".json");
|
303
|
+
document.body.appendChild(downloadAnchorNode); // required for firefox
|
304
|
+
downloadAnchorNode.click();
|
305
|
+
downloadAnchorNode.remove();
|
306
|
+
}
|
307
|
+
|
308
|
+
function copyToClipboard(text, button) {
|
309
|
+
// Create a temporary input element
|
310
|
+
const tempInput = document.createElement('input');
|
311
|
+
tempInput.style.position = 'absolute';
|
312
|
+
tempInput.style.left = '-9999px';
|
313
|
+
tempInput.value = text;
|
314
|
+
document.body.appendChild(tempInput);
|
315
|
+
|
316
|
+
// Select and copy the text
|
317
|
+
tempInput.select();
|
318
|
+
tempInput.setSelectionRange(0, 99999); // For mobile devices
|
319
|
+
|
320
|
+
try {
|
321
|
+
const successful = document.execCommand('copy');
|
322
|
+
if (successful) {
|
323
|
+
// Visual feedback - change icon temporarily
|
324
|
+
const originalIcon = button.innerHTML;
|
325
|
+
button.innerHTML = `
|
326
|
+
<svg class="w-3 h-3 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
327
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
328
|
+
</svg>
|
329
|
+
`;
|
330
|
+
button.classList.add('text-success');
|
331
|
+
|
332
|
+
// Reset after 2 seconds
|
333
|
+
setTimeout(() => {
|
334
|
+
button.innerHTML = originalIcon;
|
335
|
+
button.classList.remove('text-success');
|
336
|
+
}, 2000);
|
337
|
+
}
|
338
|
+
} catch (err) {
|
339
|
+
console.error('Failed to copy text: ', err);
|
340
|
+
}
|
341
|
+
|
342
|
+
// Remove temporary input
|
343
|
+
document.body.removeChild(tempInput);
|
344
|
+
}
|
345
|
+
</script>
|
@@ -0,0 +1,160 @@
|
|
1
|
+
<%#
|
2
|
+
Dashboard Card Component with DaisyUI
|
3
|
+
Props:
|
4
|
+
- icon: string (feather icon name)
|
5
|
+
- status: string ('success', 'warning', 'error', 'info')
|
6
|
+
- value: string/number (main metric value)
|
7
|
+
- name: string (card title)
|
8
|
+
- last_updated: datetime
|
9
|
+
- url: string (link destination)
|
10
|
+
- badges: array of hashes with {text: string, color: string}
|
11
|
+
- loading: boolean (optional)
|
12
|
+
%>
|
13
|
+
|
14
|
+
<%
|
15
|
+
# Set defaults
|
16
|
+
icon ||= 'activity'
|
17
|
+
status ||= 'info'
|
18
|
+
value ||= '0'
|
19
|
+
name ||= 'Metric'
|
20
|
+
last_updated ||= Time.current
|
21
|
+
url ||= '#'
|
22
|
+
badges ||= []
|
23
|
+
loading ||= false
|
24
|
+
|
25
|
+
# Define status-based DaisyUI color mappings
|
26
|
+
status_colors = {
|
27
|
+
'success' => 'text-success',
|
28
|
+
'warning' => 'text-warning',
|
29
|
+
'error' => 'text-error',
|
30
|
+
'info' => 'text-info'
|
31
|
+
}
|
32
|
+
|
33
|
+
# Define badge color classes using DaisyUI
|
34
|
+
badge_colors = {
|
35
|
+
'success' => 'badge-success',
|
36
|
+
'warning' => 'badge-warning',
|
37
|
+
'error' => 'badge-error',
|
38
|
+
'info' => 'badge-info',
|
39
|
+
'primary' => 'badge-primary',
|
40
|
+
'secondary' => 'badge-secondary',
|
41
|
+
'accent' => 'badge-accent',
|
42
|
+
'neutral' => 'badge-neutral',
|
43
|
+
'ghost' => 'badge-ghost',
|
44
|
+
'outline' => 'badge-outline'
|
45
|
+
}
|
46
|
+
%>
|
47
|
+
|
48
|
+
<div class="card bg-base-100 shadow-lg hover:shadow-xl transition-all duration-300 cursor-pointer"
|
49
|
+
onclick="window.location.href='<%= url %>'"
|
50
|
+
data-card-id="<%= name.parameterize %>">
|
51
|
+
|
52
|
+
<!-- Loading Overlay -->
|
53
|
+
<% if defined?(loading) && loading %>
|
54
|
+
<div class="absolute inset-0 bg-base-100/75 flex items-center justify-center rounded-2xl z-10">
|
55
|
+
<span class="loading loading-spinner loading-lg <%= status_colors[status] %>"></span>
|
56
|
+
</div>
|
57
|
+
<% end %>
|
58
|
+
|
59
|
+
<div class="card-body">
|
60
|
+
<!-- Header Row: Icon, Status, Refresh Button -->
|
61
|
+
<div class="flex items-center justify-between mb-4">
|
62
|
+
<!-- Icon with Status -->
|
63
|
+
<div class="flex items-center space-x-3">
|
64
|
+
<div class="avatar">
|
65
|
+
<div class="w-12 rounded-xl bg-gradient-to-r from-primary to-secondary shadow-lg">
|
66
|
+
<div class="flex items-center justify-center w-full h-full">
|
67
|
+
<i data-feather="<%= icon %>" class="w-6 h-6 text-white"></i>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
|
72
|
+
<!-- Status Indicator -->
|
73
|
+
<div class="badge <%= badge_colors[status] %> badge-sm">
|
74
|
+
<%= status.capitalize %>
|
75
|
+
</div>
|
76
|
+
</div>
|
77
|
+
|
78
|
+
<!-- Refresh Button -->
|
79
|
+
<button class="btn btn-ghost btn-circle btn-sm hover:bg-base-200"
|
80
|
+
onclick="refreshCard('<%= name.parameterize %>')">
|
81
|
+
<i data-feather="refresh-cw" class="w-4 h-4"></i>
|
82
|
+
</button>
|
83
|
+
</div>
|
84
|
+
|
85
|
+
<!-- Value -->
|
86
|
+
<div class="mb-3">
|
87
|
+
<div class="stat-value text-3xl font-bold transition-colors duration-300">
|
88
|
+
<%= value %>
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
|
92
|
+
<!-- Name/Title -->
|
93
|
+
<div class="mb-4">
|
94
|
+
<h3 class="card-title text-lg">
|
95
|
+
<%= name %>
|
96
|
+
</h3>
|
97
|
+
</div>
|
98
|
+
|
99
|
+
<!-- Last Updated -->
|
100
|
+
<div class="mb-4">
|
101
|
+
<div class="flex items-center space-x-2 text-sm opacity-70">
|
102
|
+
<i data-feather="clock" class="w-4 h-4"></i>
|
103
|
+
<span>Updated <%= time_ago_in_words(last_updated) %> ago</span>
|
104
|
+
</div>
|
105
|
+
</div>
|
106
|
+
|
107
|
+
<!-- Badges -->
|
108
|
+
<% if badges.present? && badges.any? %>
|
109
|
+
<div class="card-actions justify-start">
|
110
|
+
<% badges.each do |badge| %>
|
111
|
+
<%
|
112
|
+
# Handle both string and symbol keys, and ensure we have a badge object
|
113
|
+
next unless badge.is_a?(Hash) || badge.respond_to?(:has_key?)
|
114
|
+
badge_color = badge[:color] || badge['color'] || 'neutral'
|
115
|
+
badge_text = badge[:text] || badge['text'] || 'Badge'
|
116
|
+
%>
|
117
|
+
<div class="badge <%= badge_colors[badge_color] || 'badge-neutral' %> badge-sm">
|
118
|
+
<%= badge_text %>
|
119
|
+
</div>
|
120
|
+
<% end %>
|
121
|
+
</div>
|
122
|
+
<% end %>
|
123
|
+
</div>
|
124
|
+
</div>
|
125
|
+
|
126
|
+
<script>
|
127
|
+
// Card refresh functionality
|
128
|
+
function refreshCard(cardId) {
|
129
|
+
const card = document.querySelector(`[data-card-id="${cardId}"]`);
|
130
|
+
const refreshBtn = card.querySelector('.btn-circle');
|
131
|
+
|
132
|
+
// Add loading state
|
133
|
+
refreshBtn.innerHTML = '<span class="loading loading-spinner loading-xs"></span>';
|
134
|
+
|
135
|
+
// Simulate API call (replace with actual refresh logic)
|
136
|
+
setTimeout(() => {
|
137
|
+
refreshBtn.innerHTML = '<i data-feather="refresh-cw" class="w-4 h-4"></i>';
|
138
|
+
feather.replace();
|
139
|
+
|
140
|
+
// Add success feedback with DaisyUI toast
|
141
|
+
const toast = document.createElement('div');
|
142
|
+
toast.className = 'toast toast-top toast-end z-50';
|
143
|
+
toast.innerHTML = `
|
144
|
+
<div class="alert alert-success">
|
145
|
+
<i data-feather="check-circle" class="w-4 h-4"></i>
|
146
|
+
<span>Card refreshed successfully!</span>
|
147
|
+
</div>
|
148
|
+
`;
|
149
|
+
document.body.appendChild(toast);
|
150
|
+
feather.replace();
|
151
|
+
|
152
|
+
setTimeout(() => {
|
153
|
+
document.body.removeChild(toast);
|
154
|
+
}, 3000);
|
155
|
+
}, 1500);
|
156
|
+
}
|
157
|
+
|
158
|
+
// Initialize feather icons for this card
|
159
|
+
feather.replace();
|
160
|
+
</script>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<%# Quick Actions Section with DaisyUI %>
|
2
|
+
|
3
|
+
<div class="card bg-base-100 shadow-lg">
|
4
|
+
<div class="card-body">
|
5
|
+
<h3 class="card-title flex items-center mb-4">
|
6
|
+
<i data-feather="settings" class="w-5 h-5 mr-2"></i>
|
7
|
+
Quick Actions
|
8
|
+
</h3>
|
9
|
+
|
10
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
11
|
+
<% quick_actions.each do |action| %>
|
12
|
+
<button class="btn btn-outline btn-sm group flex flex-col items-center p-4 h-auto min-h-16
|
13
|
+
hover:btn-<%= action[:color] %> transition-all duration-200">
|
14
|
+
<div class="w-10 h-10 bg-gradient-to-r from-<%= action[:color] %>-500 to-<%= action[:color] %>-600
|
15
|
+
rounded-lg flex items-center justify-center mb-2
|
16
|
+
group-hover:scale-110 transition-transform duration-200 shadow-lg">
|
17
|
+
<i data-feather="<%= action[:icon] %>" class="w-5 h-5 text-white stroke-2"></i>
|
18
|
+
</div>
|
19
|
+
<span class="text-sm font-medium">
|
20
|
+
<%= action[:label] %>
|
21
|
+
</span>
|
22
|
+
</button>
|
23
|
+
<% end %>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
data/config/routes.rb
CHANGED
@@ -1,11 +1,36 @@
|
|
1
1
|
Solidstats::Engine.routes.draw do
|
2
|
-
root to: "dashboard#
|
2
|
+
root to: "dashboard#dashboard"
|
3
|
+
get "dashboard", to: "dashboard#dashboard", as: :solidstats_dashboard
|
3
4
|
get "refresh", to: "dashboard#refresh", as: :refresh
|
4
5
|
|
5
|
-
#
|
6
|
-
|
7
|
-
|
6
|
+
# Log-related routes
|
7
|
+
get "logs/size", to: "logs#logs_size", as: :logs_size
|
8
|
+
post "logs/truncate/:filename", to: "logs#truncate", as: :truncate_log, constraints: { filename: /.+/ }
|
9
|
+
post "logs/refresh", to: "logs#refresh", as: :refresh_logs
|
8
10
|
|
9
|
-
|
10
|
-
get "
|
11
|
+
# Security-related routes
|
12
|
+
get "securities/bundler_audit", to: "securities#bundler_audit", as: :securities_bundler_audit
|
13
|
+
post "securities/refresh_bundler_audit", to: "securities#refresh_bundler_audit", as: :refresh_securities_bundler_audit
|
14
|
+
|
15
|
+
# Quality-related routes
|
16
|
+
get "quality/style_patrol", to: "quality#style_patrol", as: :quality_style_patrol
|
17
|
+
post "quality/refresh_style_patrol", to: "quality#refresh_style_patrol", as: :refresh_quality_style_patrol
|
18
|
+
get "quality/coverage_compass", to: "quality#coverage_compass", as: :quality_coverage_compass
|
19
|
+
post "quality/refresh_coverage_compass", to: "quality#refresh_coverage_compass", as: :refresh_quality_coverage_compass
|
20
|
+
|
21
|
+
# Productivity-related routes
|
22
|
+
resources :productivity, only: [] do
|
23
|
+
collection do
|
24
|
+
get :my_todos
|
25
|
+
post :refresh_todos
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Performance-related routes
|
30
|
+
resources :performance, only: [] do
|
31
|
+
collection do
|
32
|
+
get :load_lens
|
33
|
+
post :refresh
|
34
|
+
end
|
35
|
+
end
|
11
36
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "rails/generators/base"
|
2
|
+
|
3
|
+
module Solidstats
|
4
|
+
module Generators
|
5
|
+
# This generator cleans up Solidstats data by invoking the Rake task
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# $ rails generate solidstats:clean
|
9
|
+
# or
|
10
|
+
# $ rake solidstats:clean
|
11
|
+
class CleanGenerator < Rails::Generators::Base
|
12
|
+
source_root File.expand_path("templates", __dir__)
|
13
|
+
desc "Cleans up Solidstats data in your Rails application"
|
14
|
+
|
15
|
+
def run_clean_task
|
16
|
+
rake "solidstats:clean"
|
17
|
+
end
|
18
|
+
|
19
|
+
def show_readme
|
20
|
+
readme "README" if behavior == :invoke
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
The Solidstats cleanup task has been executed.
|
2
|
+
|
3
|
+
All generated data, including JSON files and caches in the 'solidstats' directory, have been removed.
|
4
|
+
|
5
|
+
You can run this task again at any time by executing:
|
6
|
+
`rails generate solidstats:clean`
|
7
|
+
or
|
8
|
+
`rake solidstats:clean`
|