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.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -63
  3. data/README.md +27 -0
  4. data/Rakefile +3 -3
  5. data/app/assets/javascripts/solidstats/dashboard.js +0 -46
  6. data/app/assets/stylesheets/solidstats/dashboard.css +48 -0
  7. data/app/controllers/solidstats/dashboard_controller.rb +73 -61
  8. data/app/controllers/solidstats/logs_controller.rb +72 -0
  9. data/app/controllers/solidstats/performance_controller.rb +25 -0
  10. data/app/controllers/solidstats/productivity_controller.rb +39 -0
  11. data/app/controllers/solidstats/quality_controller.rb +152 -0
  12. data/app/controllers/solidstats/securities_controller.rb +30 -0
  13. data/app/helpers/solidstats/application_helper.rb +124 -11
  14. data/app/helpers/solidstats/performance_helper.rb +87 -0
  15. data/app/helpers/solidstats/productivity_helper.rb +38 -0
  16. data/app/services/solidstats/bundler_audit_service.rb +206 -0
  17. data/app/services/solidstats/coverage_compass_service.rb +335 -0
  18. data/app/services/solidstats/load_lens_service.rb +474 -0
  19. data/app/services/solidstats/log_size_monitor_service.rb +197 -66
  20. data/app/services/solidstats/my_todo_service.rb +242 -0
  21. data/app/services/solidstats/style_patrol_service.rb +319 -0
  22. data/app/views/layouts/solidstats/application.html.erb +8 -2
  23. data/app/views/layouts/solidstats/dashboard.html.erb +84 -0
  24. data/app/views/solidstats/dashboard/dashboard.html.erb +39 -0
  25. data/app/views/solidstats/logs/logs_size.html.erb +409 -0
  26. data/app/views/solidstats/performance/load_lens.html.erb +158 -0
  27. data/app/views/solidstats/productivity/_todo_list.html.erb +49 -0
  28. data/app/views/solidstats/productivity/my_todos.html.erb +84 -0
  29. data/app/views/solidstats/quality/coverage_compass.html.erb +420 -0
  30. data/app/views/solidstats/quality/style_patrol.html.erb +463 -0
  31. data/app/views/solidstats/securities/bundler_audit.html.erb +345 -0
  32. data/app/views/solidstats/shared/_dashboard_card.html.erb +160 -0
  33. data/app/views/solidstats/shared/_quick_actions.html.erb +26 -0
  34. data/config/routes.rb +31 -6
  35. data/lib/generators/solidstats/clean/clean_generator.rb +24 -0
  36. data/lib/generators/solidstats/clean/templates/README +8 -0
  37. data/lib/generators/solidstats/install/install_generator.rb +51 -10
  38. data/lib/generators/solidstats/install/templates/README +7 -0
  39. data/lib/solidstats/engine.rb +6 -71
  40. data/lib/solidstats/version.rb +1 -1
  41. data/lib/solidstats.rb +19 -303
  42. data/lib/tasks/solidstats.rake +67 -0
  43. data/lib/tasks/solidstats_performance.rake +61 -0
  44. data/lib/tasks/solidstats_tasks.rake +16 -4
  45. metadata +33 -95
  46. data/app/assets/javascripts/solidstats/gem_metadata.js +0 -554
  47. data/app/assets/stylesheets/solidstats/components/action_button.css +0 -99
  48. data/app/assets/stylesheets/solidstats/components/dashboard.css +0 -151
  49. data/app/assets/stylesheets/solidstats/components/dashboard_header.css +0 -93
  50. data/app/assets/stylesheets/solidstats/components/dashboard_layout.css +0 -97
  51. data/app/assets/stylesheets/solidstats/components/gem_metadata.css +0 -1403
  52. data/app/assets/stylesheets/solidstats/components/navigation.css +0 -80
  53. data/app/assets/stylesheets/solidstats/components/quick_navigation.css +0 -54
  54. data/app/assets/stylesheets/solidstats/components/security.css +0 -332
  55. data/app/assets/stylesheets/solidstats/components/status_badge.css +0 -58
  56. data/app/assets/stylesheets/solidstats/components/summary_card.css +0 -66
  57. data/app/assets/stylesheets/solidstats/components/tab_navigation.css +0 -95
  58. data/app/components/solidstats/base_component.rb +0 -88
  59. data/app/components/solidstats/code_quality/code_quality_section_component.html.erb +0 -0
  60. data/app/components/solidstats/code_quality/code_quality_section_component.rb +0 -0
  61. data/app/components/solidstats/code_quality/section_component.html.erb +0 -45
  62. data/app/components/solidstats/code_quality/section_component.rb +0 -34
  63. data/app/components/solidstats/dashboard_header_component.html.erb +0 -39
  64. data/app/components/solidstats/dashboard_header_component.rb +0 -33
  65. data/app/components/solidstats/previews/action_button_component_preview/button_vs_link.html.erb +0 -6
  66. data/app/components/solidstats/previews/action_button_component_preview/sizes.html.erb +0 -6
  67. data/app/components/solidstats/previews/action_button_component_preview/variants.html.erb +0 -6
  68. data/app/components/solidstats/previews/action_button_component_preview/with_icons.html.erb +0 -6
  69. data/app/components/solidstats/previews/action_button_component_preview.rb +0 -64
  70. data/app/components/solidstats/previews/navigation_component_preview.rb +0 -74
  71. data/app/components/solidstats/previews/stats_overview_component_preview.rb +0 -100
  72. data/app/components/solidstats/previews/status_badge_component_preview/sizes.html.erb +0 -6
  73. data/app/components/solidstats/previews/status_badge_component_preview/statuses.html.erb +0 -6
  74. data/app/components/solidstats/previews/status_badge_component_preview/with_icons.html.erb +0 -6
  75. data/app/components/solidstats/previews/status_badge_component_preview.rb +0 -49
  76. data/app/components/solidstats/previews/summary_card_component_preview/clickable.html.erb +0 -9
  77. data/app/components/solidstats/previews/summary_card_component_preview/dashboard_layout.html.erb +0 -9
  78. data/app/components/solidstats/previews/summary_card_component_preview/statuses.html.erb +0 -6
  79. data/app/components/solidstats/previews/summary_card_component_preview/value_formats.html.erb +0 -6
  80. data/app/components/solidstats/previews/summary_card_component_preview.rb +0 -67
  81. data/app/components/solidstats/quick_navigation_component.html.erb +0 -8
  82. data/app/components/solidstats/quick_navigation_component.rb +0 -21
  83. data/app/components/solidstats/security/gem_impact_analysis_component.html.erb +0 -44
  84. data/app/components/solidstats/security/gem_impact_analysis_component.rb +0 -45
  85. data/app/components/solidstats/security/overview_component.html.erb +0 -21
  86. data/app/components/solidstats/security/overview_component.rb +0 -104
  87. data/app/components/solidstats/security/section_component.html.erb +0 -26
  88. data/app/components/solidstats/security/section_component.rb +0 -52
  89. data/app/components/solidstats/security/timeline_component.html.erb +0 -39
  90. data/app/components/solidstats/security/timeline_component.rb +0 -43
  91. data/app/components/solidstats/tasks_section_component.html.erb +0 -17
  92. data/app/components/solidstats/tasks_section_component.rb +0 -22
  93. data/app/components/solidstats/ui/action_button_component.html.erb +0 -6
  94. data/app/components/solidstats/ui/action_button_component.rb +0 -71
  95. data/app/components/solidstats/ui/dashboard_layout_component.html.erb +0 -19
  96. data/app/components/solidstats/ui/dashboard_layout_component.rb +0 -85
  97. data/app/components/solidstats/ui/navigation_component.html.erb +0 -34
  98. data/app/components/solidstats/ui/navigation_component.rb +0 -72
  99. data/app/components/solidstats/ui/stats_overview_component.html.erb +0 -14
  100. data/app/components/solidstats/ui/stats_overview_component.rb +0 -78
  101. data/app/components/solidstats/ui/status_badge_component.html.erb +0 -6
  102. data/app/components/solidstats/ui/status_badge_component.rb +0 -42
  103. data/app/components/solidstats/ui/summary_card_component.html.erb +0 -12
  104. data/app/components/solidstats/ui/summary_card_component.rb +0 -63
  105. data/app/components/solidstats/ui/tab_navigation_component.html.erb +0 -22
  106. data/app/components/solidstats/ui/tab_navigation_component.rb +0 -79
  107. data/app/services/solidstats/audit_service.rb +0 -56
  108. data/app/services/solidstats/data_collector_service.rb +0 -83
  109. data/app/services/solidstats/gem_metadata/fetcher_service.rb +0 -136
  110. data/app/services/solidstats/todo_service.rb +0 -114
  111. data/app/views/solidstats/dashboard/_log_monitor.html.erb +0 -759
  112. data/app/views/solidstats/dashboard/_todos.html.erb +0 -151
  113. data/app/views/solidstats/dashboard/audit/_additional_styles.css +0 -22
  114. data/app/views/solidstats/dashboard/audit/_audit_badge.html.erb +0 -5
  115. data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +0 -495
  116. data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +0 -26
  117. data/app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb +0 -3
  118. data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -14
  119. data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +0 -1120
  120. data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +0 -63
  121. data/app/views/solidstats/dashboard/index.html.erb +0 -81
  122. data/app/views/solidstats/gem_metadata/_panel.html.erb +0 -419
  123. data/lib/generators/solidstats/feature/feature_generator.rb +0 -170
  124. data/lib/generators/solidstats/feature/templates/component.html.erb +0 -84
  125. data/lib/generators/solidstats/feature/templates/component.rb.erb +0 -103
  126. data/lib/generators/solidstats/feature/templates/component.scss +0 -243
  127. data/lib/generators/solidstats/feature/templates/component_test.rb.erb +0 -183
  128. data/lib/generators/solidstats/feature/templates/controller.rb.erb +0 -44
  129. data/lib/generators/solidstats/feature/templates/controller_test.rb.erb +0 -111
  130. data/lib/generators/solidstats/feature/templates/detail_view.html.erb +0 -755
  131. data/lib/generators/solidstats/feature/templates/preview.rb.erb +0 -107
  132. data/lib/generators/solidstats/feature/templates/service.rb.erb +0 -132
  133. data/lib/generators/solidstats/feature/templates/service_test.rb.erb +0 -109
  134. data/lib/generators/solidstats/install_generator.rb +0 -109
  135. data/lib/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#index"
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
- # Route for truncating logs - accepts filename without extension
6
- post "truncate-log(/:filename)", to: "dashboard#truncate_log", as: :truncate_log,
7
- constraints: { filename: /[^\.]+/ }
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
- post "gem_metadata/refresh", to: "gem_metadata#refresh", as: :refresh_gem_metadata
10
- get "gem_metadata/refresh", to: "gem_metadata#refresh" # Fallback for direct URL access
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`